From 9ad2223531145a19841a60006bd5ddb71486e557 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 13 Mar 2026 10:23:53 -0700 Subject: [PATCH] [Fix] Convert _has_attribute_error_in_chain from recursive to iterative The recursive implementation was flagged by the recursive function detector lint check. Converted to an iterative approach using an explicit stack and seen set, with depth capped at DEFAULT_MAX_RECURSE_DEPTH. Co-Authored-By: Claude Opus 4.6 --- litellm/proxy/common_request_processing.py | 32 ++++++++++++++-------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 7601af8f346..a9e9d519f6f 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -25,6 +25,7 @@ from litellm._logging import verbose_proxy_logger from litellm._uuid import uuid from litellm.constants import ( DD_TRACER_STREAMING_CHUNK_YIELD_RESOURCE, + DEFAULT_MAX_RECURSE_DEPTH, LITELLM_DETAILED_TIMING, MAX_PAYLOAD_SIZE_FOR_DEBUG_LOG, STREAM_SSE_DATA_PREFIX, @@ -384,22 +385,29 @@ def _get_cost_breakdown_from_logging_obj( return original_cost, discount_amount, margin_total_amount, margin_percent -def _has_attribute_error_in_chain(exc: Exception, _depth: int = 0) -> bool: +def _has_attribute_error_in_chain(exc: Exception) -> bool: """Walk the exception chain to find an AttributeError at any depth. Checks __cause__, __context__, and the litellm-specific original_exception - attribute recursively. Depth is capped to avoid infinite loops from - circular exception references. + attribute iteratively. Depth is capped at DEFAULT_MAX_RECURSE_DEPTH to + avoid infinite loops from circular exception references. """ - if _depth > 10: - return False - if isinstance(exc, AttributeError): - return True - for attr in ("__cause__", "__context__", "original_exception"): - inner = getattr(exc, attr, None) - if inner is not None and isinstance(inner, BaseException): - if _has_attribute_error_in_chain(inner, _depth + 1): - return True + stack: list[BaseException] = [exc] + seen: set[int] = set() + depth = 0 + while stack and depth < DEFAULT_MAX_RECURSE_DEPTH: + current = stack.pop() + exc_id = id(current) + if exc_id in seen: + continue + seen.add(exc_id) + if isinstance(current, AttributeError): + return True + for attr in ("__cause__", "__context__", "original_exception"): + inner = getattr(current, attr, None) + if inner is not None and isinstance(inner, BaseException): + stack.append(inner) + depth += 1 return False