Is there any way to access nested or re-raised exceptions in python?
Question:
A common pattern in python is to catch an error in an upstream module and re-raise that error as something more useful.
try:
config_file = open('config.ini', 'r')
except IOError:
raise ConfigError('Give me my config, user!')
This will generate a stack trace of the form
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
__main__.ConfigError: Give me my config, user!
Is there any way to access the wrapped exception in order to generate a stack trace more like this?
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
__builtin__.IOError: File Does not exist.
Exception wrapped by:
File "<stdin>", line 4, in <module>
__main__.ConfigError: Give me my config, user!
EDIT:
The problem i’m trying to defeat is that some 3rd party code can wrap exceptions up to 3 times and I want to be able to determine the root cause, i.e. a generic way to inspect the exception stack and determine the root cause of an exception without having to add any extra code to 3rd party modules.
Answers:
Use the traceback
module. It will allow you to access the most recent traceback and store it in a string. For example,
import traceback
try:
config_file = open('config.ini', 'r')
except OSError:
tb = traceback.format_exc()
raise ConfigError('Give me my config, user!',tb)
The “nested” traceback will be stored in tb and passed to ConfigError, where you can work with it however you want.
This is known as Exception Chaining and is suported in Python 3.
PEP 3134: http://www.python.org/dev/peps/pep-3134/
In Python 2, the old exception is lost when you raise a new one, unless you save it in the except
block.
Here is an example of how to unwind PEP-3134 exception chains.
Note that due to legacy reasons some Python frameworks may not use exception chaining, but instead of wrap exceptions in their own way. For example. SQLALchemy DBABIError uses orig
attribute.
class Foobar(Exception):
pass
class Dummy(Exception):
pass
def func1():
raise Foobar("func1() argh")
def func2():
try:
func1()
except Exception as e:
raise Dummy("func2 vyaaarrg!") from e
try:
func2()
except Exception as e:
print(f"Current {e.__class__}: {e}")
print(f"Nested {e.__cause__.__class__}:{e.__cause__}")
Prints
Current <class '__main__.Dummy'>: func2 vyaaarrg!
Nested <class '__main__.Foobar'>:func1() argh
A common pattern in python is to catch an error in an upstream module and re-raise that error as something more useful.
try:
config_file = open('config.ini', 'r')
except IOError:
raise ConfigError('Give me my config, user!')
This will generate a stack trace of the form
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
__main__.ConfigError: Give me my config, user!
Is there any way to access the wrapped exception in order to generate a stack trace more like this?
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
__builtin__.IOError: File Does not exist.
Exception wrapped by:
File "<stdin>", line 4, in <module>
__main__.ConfigError: Give me my config, user!
EDIT:
The problem i’m trying to defeat is that some 3rd party code can wrap exceptions up to 3 times and I want to be able to determine the root cause, i.e. a generic way to inspect the exception stack and determine the root cause of an exception without having to add any extra code to 3rd party modules.
Use the traceback
module. It will allow you to access the most recent traceback and store it in a string. For example,
import traceback
try:
config_file = open('config.ini', 'r')
except OSError:
tb = traceback.format_exc()
raise ConfigError('Give me my config, user!',tb)
The “nested” traceback will be stored in tb and passed to ConfigError, where you can work with it however you want.
This is known as Exception Chaining and is suported in Python 3.
PEP 3134: http://www.python.org/dev/peps/pep-3134/
In Python 2, the old exception is lost when you raise a new one, unless you save it in the except
block.
Here is an example of how to unwind PEP-3134 exception chains.
Note that due to legacy reasons some Python frameworks may not use exception chaining, but instead of wrap exceptions in their own way. For example. SQLALchemy DBABIError uses orig
attribute.
class Foobar(Exception):
pass
class Dummy(Exception):
pass
def func1():
raise Foobar("func1() argh")
def func2():
try:
func1()
except Exception as e:
raise Dummy("func2 vyaaarrg!") from e
try:
func2()
except Exception as e:
print(f"Current {e.__class__}: {e}")
print(f"Nested {e.__cause__.__class__}:{e.__cause__}")
Prints
Current <class '__main__.Dummy'>: func2 vyaaarrg!
Nested <class '__main__.Foobar'>:func1() argh