@@ -800,14 +800,16 @@ def exceptions_from_error(
800800):
801801 # type: (...) -> Tuple[int, List[Dict[str, Any]]]
802802 """
803- Creates the list of exceptions.
804- This can include chained exceptions and exceptions from an ExceptionGroup.
805-
806- See the Exception Interface documentation for more details:
807- https://develop.sentry.dev/sdk/event-payloads/exception/
803+ Converts the given exception information into the Sentry structured "exception" format.
804+ This will return a list of exceptions in the format of the Exception Interface documentation:
805+ https://develop.sentry.dev/sdk/data-model/event-payloads/exception/
806+
807+ This function can handle:
808+ - simple exceptions
809+ - chained exceptions (raise .. from ..)
810+ - exception groups
808811 """
809-
810- parent = single_exception_from_error_tuple (
812+ base_exception = single_exception_from_error_tuple (
811813 exc_type = exc_type ,
812814 exc_value = exc_value ,
813815 tb = tb ,
@@ -818,64 +820,70 @@ def exceptions_from_error(
818820 source = source ,
819821 full_stack = full_stack ,
820822 )
821- exceptions = [parent ]
823+ exceptions = [base_exception ]
822824
823825 parent_id = exception_id
824826 exception_id += 1
825827
826- should_supress_context = hasattr (exc_value , "__suppress_context__" ) and exc_value .__suppress_context__ # type: ignore
827- if should_supress_context :
828- # Add direct cause.
829- # The field `__cause__` is set when raised with the exception (using the `from` keyword).
830- exception_has_cause = (
828+ # Note: __suppress_context__ is True if the exception is raised with the `from` keyword.
829+ should_suppress_context = hasattr (exc_value , "__suppress_context__" ) and exc_value .__suppress_context__ # type: ignore
830+ if should_suppress_context :
831+ # Explicitly chained exceptions (Like: raise NewException() from OriginalException())
832+ # The field `__cause__` is set to OriginalException
833+ exception_has_explicit_causing_exception = (
831834 exc_value
832835 and hasattr (exc_value , "__cause__" )
833836 and exc_value .__cause__ is not None
834837 )
835- if exception_has_cause :
836- cause = exc_value .__cause__ # type: ignore
838+ if exception_has_explicit_causing_exception :
839+ causing_exception = exc_value .__cause__ # type: ignore
840+
837841 (exception_id , child_exceptions ) = exceptions_from_error (
838- exc_type = type (cause ),
839- exc_value = cause ,
840- tb = getattr (cause , "__traceback__" , None ),
842+ exc_type = type (causing_exception ),
843+ exc_value = causing_exception ,
844+ tb = getattr (causing_exception , "__traceback__" , None ),
841845 client_options = client_options ,
842846 mechanism = mechanism ,
843847 exception_id = exception_id ,
848+ # parent_id=parent_id, TODO: why is this not set?
844849 source = "__cause__" ,
845850 full_stack = full_stack ,
846851 )
847852 exceptions .extend (child_exceptions )
848853
849854 else :
850- # Add indirect cause.
851- # The field `__context__` is assigned if another exception occurs while handling the exception.
852- exception_has_content = (
855+ # Implicitly chained exceptions (when an exception occurs while handling another exception)
856+ # The field `__context__` is set in the exception that occurs while handling another exception,
857+ # to the other exception.
858+ exception_has_implicit_causing_exception = (
853859 exc_value
854860 and hasattr (exc_value , "__context__" )
855861 and exc_value .__context__ is not None
856862 )
857- if exception_has_content :
858- context = exc_value .__context__ # type: ignore
863+ if exception_has_implicit_causing_exception :
864+ causing_exception = exc_value .__context__ # type: ignore
865+
859866 (exception_id , child_exceptions ) = exceptions_from_error (
860- exc_type = type (context ),
861- exc_value = context ,
862- tb = getattr (context , "__traceback__" , None ),
867+ exc_type = type (causing_exception ),
868+ exc_value = causing_exception ,
869+ tb = getattr (causing_exception , "__traceback__" , None ),
863870 client_options = client_options ,
864871 mechanism = mechanism ,
865872 exception_id = exception_id ,
873+ # parent_id=parent_id, TODO: why is this not set?
866874 source = "__context__" ,
867875 full_stack = full_stack ,
868876 )
869877 exceptions .extend (child_exceptions )
870878
871- # Add exceptions from an ExceptionGroup.
879+ # Add child exceptions from an ExceptionGroup.
872880 is_exception_group = exc_value and hasattr (exc_value , "exceptions" )
873881 if is_exception_group :
874- for idx , e in enumerate (exc_value .exceptions ): # type: ignore
882+ for idx , causing_exception in enumerate (exc_value .exceptions ): # type: ignore
875883 (exception_id , child_exceptions ) = exceptions_from_error (
876- exc_type = type (e ),
877- exc_value = e ,
878- tb = getattr (e , "__traceback__" , None ),
884+ exc_type = type (causing_exception ),
885+ exc_value = causing_exception ,
886+ tb = getattr (causing_exception , "__traceback__" , None ),
879887 client_options = client_options ,
880888 mechanism = mechanism ,
881889 exception_id = exception_id ,
@@ -895,8 +903,15 @@ def exceptions_from_error_tuple(
895903 full_stack = None , # type: Optional[list[dict[str, Any]]]
896904):
897905 # type: (...) -> List[Dict[str, Any]]
906+ """
907+ Convert Python's exception information into Sentry's structured "exception" format in the event.
908+ See https://develop.sentry.dev/sdk/data-model/event-payloads/exception/
909+ This is the entry point for the exception handling.
910+ """
911+ # unpack the exception info tuple
898912 exc_type , exc_value , tb = exc_info
899913
914+ # let exceptions_from_error do the actual work
900915 _ , exceptions = exceptions_from_error (
901916 exc_type = exc_type ,
902917 exc_value = exc_value ,
@@ -908,6 +923,9 @@ def exceptions_from_error_tuple(
908923 full_stack = full_stack ,
909924 )
910925
926+ # make sure the exceptions are sorted
927+ # from the innermost (oldest)
928+ # to the outermost (newest) exception
911929 exceptions .reverse ()
912930
913931 return exceptions
0 commit comments