Wrapping exceptions in Python

Question:

I’m working on a mail-sending library, and I want to be able to catch exceptions produced by the senders (SMTP, Google AppEngine, etc.) and wrap them in easily catchable exceptions specific to my library (ConnectionError, MessageSendError, etc.), with the original traceback intact so it can be debugged. What is the best way to do this in Python 2?

Asked By: LeafStorm

||

Answers:

The simplest way would be to reraise with the old trace object. The following example shows this:

import sys

def a():
    def b():
        raise AssertionError("1")
    b()

try:
    a()
except AssertionError: # some specific exception you want to wrap
    trace = sys.exc_info()[2]
    raise Exception("error description"), None, trace

Check the documentation of the raise statement for details of the three parameters. My example would print:

Traceback (most recent call last):
  File "C:...test.py", line 9, in <module>
    a()
  File "C:...test.py", line 6, in a
    b()
  File "C:...test.py", line 5, in b
    raise AssertionError("1")
Exception: error description

For completeness, in Python 3 you’d use the raise MyException(...) from e syntax.

Answered By: AndiDog

This answer is probably a little bit late, but you can wrap the function in a python decorator.

Here is a simple cheatsheet on how different decorators.

Here is some sample code of how to do this. Just change the decorator to catch different errors in the different ways that you need.

def decorator(wrapped_function):
    def _wrapper(*args, **kwargs):
        try:
            # do something before the function call
            result = wrapped_function(*args, **kwargs)
            # do something after the function call
        except TypeError:
            print("TypeError")
        except IndexError:
            print("IndexError")
        # return result
    return _wrapper


@decorator
def type_error():
    return 1 / 'a'

@decorator
def index_error():
    return ['foo', 'bar'][5]


type_error()
index_error()
Answered By: Aaron Lelevier

Use raise_from from the future.utils package.

Relevant example copied below:

from future.utils import raise_from

class FileDatabase:
    def __init__(self, filename):
        try:
            self.file = open(filename)
        except IOError as exc:
            raise_from(DatabaseError('failed to open'), exc)

Within that package, raise_from is implemented as follows:

def raise_from(exc, cause):
    """
    Equivalent to:

        raise EXCEPTION from CAUSE

    on Python 3. (See PEP 3134).
    """
    # Is either arg an exception class (e.g. IndexError) rather than
    # instance (e.g. IndexError('my message here')? If so, pass the
    # name of the class undisturbed through to "raise ... from ...".
    if isinstance(exc, type) and issubclass(exc, Exception):
        e = exc()
        # exc = exc.__name__
        # execstr = "e = " + _repr_strip(exc) + "()"
        # myglobals, mylocals = _get_caller_globals_and_locals()
        # exec(execstr, myglobals, mylocals)
    else:
        e = exc
    e.__suppress_context__ = False
    if isinstance(cause, type) and issubclass(cause, Exception):
        e.__cause__ = cause()
        e.__suppress_context__ = True
    elif cause is None:
        e.__cause__ = None
        e.__suppress_context__ = True
    elif isinstance(cause, BaseException):
        e.__cause__ = cause
        e.__suppress_context__ = True
    else:
        raise TypeError("exception causes must derive from BaseException")
    e.__context__ = sys.exc_info()[1]
    raise e
Answered By: Jonathan Jin
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.