Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/_experimental/mcp_server/faults/traversal.py: 100%

19 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 12:01 +0000

1"""Shared exception-tree traversal for fault classification. 

2 

3Failures cross the MCP SDK's anyio task groups wrapped in ``ExceptionGroup``s and chained through 

4``raise ... from`` causes, so every classifier that needs an exception buried in the tree (an 

5upstream ``httpx.Response``, a context-window overflow) has to walk the same shapes. One traversal 

6with one deliberate order keeps blame assignment consistent across classifiers: explicit links are 

7searched before incidental ones, so an exception raised while handling the real failure can never 

8shadow the failure itself. 

9""" 

10 

11from __future__ import annotations 

12 

13from collections.abc import Iterator 

14from typing import Final 

15 

16 

17def iter_exception_tree(exc: BaseException) -> Iterator[BaseException]: 

18 """Yield ``exc`` and every exception reachable from it, explicit links first: each node's 

19 ``raise ... from`` cause subtree, then ``ExceptionGroup`` members in raise order, then the 

20 incidental ``__context__`` chain last. Cycle-safe via identity tracking, and iterative so a 

21 deep chain cannot overflow the interpreter stack.""" 

22 seen: Final[set[int]] = set() 

23 stack: Final = [exc] 

24 while stack: 

25 current = stack.pop() 

26 if id(current) in seen: 

27 continue 

28 seen.add(id(current)) 

29 yield current 

30 if current.__context__ is not None: 

31 stack.append(current.__context__) 

32 exceptions = getattr(current, "exceptions", None) 

33 if isinstance(exceptions, tuple): 

34 stack.extend(reversed(exceptions)) 

35 if current.__cause__ is not None: 

36 stack.append(current.__cause__)