34

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?

LeafStorm
  • 3,057
  • 4
  • 24
  • 28

3 Answers3

30

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.

AndiDog
  • 68,631
  • 21
  • 159
  • 205
  • 1
    `raise Exception("error description"), None, trace` has the side effect of losing the original error message. Anyone know a way around this? – James McMahon Jul 23 '13 at 17:25
  • 1
    I ended up using the message from original exception and concatenate it into the new message. – James McMahon Jul 23 '13 at 18:27
  • 3
    I do it like this: `raise Exception("error description\nCaused by: {}: {}".format(type(e).__name__, str(e))), None, sys.exc_info()[2]` – Dan Fabulich Mar 03 '16 at 17:30
7

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
Jonathan Jin
  • 485
  • 1
  • 3
  • 15
6

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()
Aaron Lelevier
  • 19,850
  • 11
  • 76
  • 111