@@ -435,7 +435,7 @@ def platform_is_emscripten() -> bool:
435
435
return platform .system ().lower () == 'emscripten'
436
436
437
437
438
- def canonicalize_exception_traceback (exc : BaseException ) -> str :
438
+ def canonicalize_exception_traceback (exc : BaseException , seen : set [ int ] | None = None ) -> str :
439
439
"""Return a canonical string representation of an exception traceback.
440
440
441
441
Exceptions with the same representation are considered the same for fingerprinting purposes.
@@ -459,23 +459,28 @@ def canonicalize_exception_traceback(exc: BaseException) -> str:
459
459
if frame_summary not in visited : # ignore repeated frames
460
460
visited .add (frame_summary )
461
461
parts .append (frame_summary )
462
- if isinstance (exc , BaseExceptionGroup ):
463
- sub_exceptions : tuple [BaseException ] = exc .exceptions # type: ignore
464
- parts += [
465
- '\n <ExceptionGroup>' ,
466
- * sorted ({canonicalize_exception_traceback (nested_exc ) for nested_exc in sub_exceptions }),
467
- '\n </ExceptionGroup>\n ' ,
468
- ]
469
- if exc .__cause__ is not None :
470
- parts += [
471
- '\n __cause__:' ,
472
- canonicalize_exception_traceback (exc .__cause__ ),
473
- ]
474
- if exc .__context__ is not None and not exc .__suppress_context__ :
475
- parts += [
476
- '\n __context__:' ,
477
- canonicalize_exception_traceback (exc .__context__ ),
478
- ]
462
+ seen = seen or set ()
463
+ if id (exc ) in seen :
464
+ parts .append ('\n <repeated exception>' )
465
+ else :
466
+ seen .add (id (exc ))
467
+ if isinstance (exc , BaseExceptionGroup ):
468
+ sub_exceptions : tuple [BaseException ] = exc .exceptions # type: ignore
469
+ parts += [
470
+ '\n <ExceptionGroup>' ,
471
+ * sorted ({canonicalize_exception_traceback (nested_exc , seen ) for nested_exc in sub_exceptions }),
472
+ '\n </ExceptionGroup>\n ' ,
473
+ ]
474
+ if exc .__cause__ is not None :
475
+ parts += [
476
+ '\n __cause__:' ,
477
+ canonicalize_exception_traceback (exc .__cause__ , seen ),
478
+ ]
479
+ if exc .__context__ is not None and not exc .__suppress_context__ :
480
+ parts += [
481
+ '\n __context__:' ,
482
+ canonicalize_exception_traceback (exc .__context__ , seen ),
483
+ ]
479
484
return '\n ' .join (parts )
480
485
except Exception : # pragma: no cover
481
486
log_internal_error ()
0 commit comments