resolved RESOLVED UNSUBMITTED PASS · None tool calls · 0 s · Textualize/rich
Fix traceback recursion Fixes https://github.com/Textualize/rich/issues/3708 Fixes https://github.com/Textualize/rich/issues/3682
| # | Tool | Arguments | Result |
|---|---|---|---|
| No trace captured. | |||
--- a/rich/traceback.py
+++ b/rich/traceback.py
@@ -14,6 +14,7 @@
List,
Optional,
Sequence,
+ Set,
Tuple,
Type,
Union,
@@ -418,6 +419,7 @@ def extract(
locals_max_string: int = LOCALS_MAX_STRING,
locals_hide_dunder: bool = True,
locals_hide_sunder: bool = False,
+ _visited_exceptions: Optional[Set[BaseException]] = None,
) -> Trace:
"""Extract traceback information.
@@ -443,6 +445,10 @@ def extract(
notes: List[str] = getattr(exc_value, "__notes__", None) or []
+ grouped_exceptions: Set[BaseException] = (
+ set() if _visited_exceptions is None else _visited_exceptions
+ )
+
def safe_str(_object: Any) -> str:
"""Don't allow exceptions from __str__ to propagate."""
try:
@@ -462,6 +468,9 @@ def safe_str(_object: Any) -> str:
if isinstance(exc_value, (BaseExceptionGroup, ExceptionGroup)):
stack.is_group = True
for exception in exc_value.exceptions:
+ if exception in grouped_exceptions:
+ continue
+ grouped_exceptions.add(exception)
stack.exceptions.append(
Traceback.extract(
type(exception),
@@ -471,6 +480,7 @@ def safe_str(_object: Any) -> str:
locals_max_length=locals_max_length,
locals_hide_dunder=locals_hide_dunder,
locals_hide_sunder=locals_hide_sunder,
+ _visited_exceptions=grouped_exceptions,
)
)
@@ -561,23 +571,26 @@ def get_locals(
if frame_summary.f_locals.get("_rich_traceback_guard", False):
del stack.frames[:]
- cause = getattr(exc_value, "__cause__", None)
- if cause:
- exc_type = cause.__class__
- exc_value = cause
- # __traceback__ can be None, e.g. for exceptions raised by the
- # 'multiprocessing' module
- traceback = cause.__traceback__
- is_cause = True
- continue
+ if not grouped_exceptions:
+ cause = getattr(exc_value, "__cause__", None)
+ if cause is not None and cause is not exc_value:
+ exc_type = cause.__class__
+ exc_value = cause
+ # __traceback__ can be None, e.g. for exceptions raised by the
+ # 'multiprocessing' module
+ traceback = cause.__traceback__
+ is_cause = True
+ continue
- cause = exc_value.__context__
- if cause and not getattr(exc_value, "__suppress_context__", False):
- exc_type = cause.__class__
- exc_value = cause
- traceback = cause.__traceback__
- is_cause = False
- continue
+ cause = exc_value.__context__
+ if cause is not None and not getattr(
+ exc_value, "__suppress_context__", False
+ ):
+ exc_type = cause.__class__
+ exc_value = cause
+ traceback = cause.__traceback__
+ is_cause = False
+ continue
# No cover, code is reached but coverage doesn't recognize it.
break # pragma: no cover
...................s... [100%] 22 passed, 1 skipped in 0.71s