stelios
stelios

Reputation: 2845

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?

Upvotes: 3

Views: 1838

Answers (1)

Don Kirkby
Don Kirkby

Reputation: 56620

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

Upvotes: 3

Related Questions