Search code examples
pythonpython-3.xexceptiontraceback

Get only last traceback without using raise from None?


I have a common situation in my projects, where I use custom Exceptions to wrap built in exception scenarios, like:

# meaningless here but in other situations its useful...
try:
   5/0
except ZeroDivisionError as e:
   raise MyCustomException(msg=str(e))

along with a universal exception handler that looks like this:

@app.errorhandler(Exception)   # this decorator is flask related - ignore it
def handle_error(error):
    if isinstance(error, MyCustomException):
        # some code
        # I only want MyCustomException traceback
    else:
        # some other code    
        exc_stack = traceback.format_exc(limit=5)

The known issue here is that I get both exception tracebacks, whereas in if-case I want only the last one.

There are two solutions in this problem as far as I know.

First workaround (use from None):

try:
   5/0
except ZeroDivisionError as e:
   raise MyCustomException(msg=str(e)) from None   # python3

Second workaround (call the traceback before raising the second exception)

try:
    5/0
except ZeroDivisionError as e:
   tracb = traceback.format_exc(limit=5)
   raise MyCustomException(msg=str(e), tracb_msg=tracb)

No need to call traceback.format_exc() in exception handler, just use the tracb_msg passed to the instance. Obviously first workaround is simpler.

My Problem:

Both of these approaches re-appear (repeating code/trick) dozens of times inside the code, every time I raise MyCustomException. Has anyone come up with a trick to handle this once inside the handler function?


Solution

  • Use the __suppress_context__ attribute to disable context printing.

    According to the docs, using raise MyCustomException(foo) from bar sets __cause__ to bar, and __context__ to the original exception (the implicitly chained exception).

    An implicitly chained exception in __context__ is shown only if __cause__ is None and __suppress_context__ is false.

    Here's an example:

    # Declare an exception that never shows context.
    
    
    class MyCustomException(Exception):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.__suppress_context__ = True
    
    try:
        1/0
    except ZeroDivisionError as e:
        raise MyCustomException(str(e))
    

    Here's the output I get:

    Traceback (most recent call last):
      File "/home/don/workspace/scratch/scratch.py", line 12, in <module>
        raise MyCustomException(str(e))
    MyCustomException: division by zero
    

    Here's the output if I set __suppress_context__ to False:

    Traceback (most recent call last):
      File "/home/don/workspace/scratch/scratch.py", line 10, in <module>
        1/0
    ZeroDivisionError: division by zero
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/home/don/workspace/scratch/scratch.py", line 12, in <module>
        raise MyCustomException(str(e))
    MyCustomException: division by zero