Fixed #25761 -- Added __cause__.__traceback__ to reraised exceptions.

When Django reraises an exception, it sets the __cause__ attribute even
in Python 2, mimicking Python's 3 behavior for "raise Foo from Bar".
However, Python 3 also ensures that all exceptions have a __traceback__
attribute and thus the "traceback2" Python 2 module (backport of Python
3's "traceback" module) relies on the fact that whenever you have a
__cause__ attribute, the recorded exception also has a __traceback__
attribute.

This is breaking testtools which is using traceback2 (see
https://github.com/testing-cabal/testtools/issues/162).

This commit fixes this inconsistency by ensuring that Django sets
the __traceback__ attribute on any exception stored in a __cause__
attribute of a reraised exception.
This commit is contained in:
Raphaël Hertzog 2015-11-25 12:54:52 +01:00 committed by Tim Graham
parent c6ea4ed5d2
commit 9f4e031bd3
4 changed files with 13 additions and 1 deletions

View File

@ -272,6 +272,8 @@ class MigrationLoader(object):
), ),
missing) missing)
exc_value.__cause__ = exc exc_value.__cause__ = exc
if not hasattr(exc, '__traceback__'):
exc.__traceback__ = sys.exc_info()[2]
six.reraise(NodeNotFoundError, exc_value, sys.exc_info()[2]) six.reraise(NodeNotFoundError, exc_value, sys.exc_info()[2])
raise exc raise exc

View File

@ -85,6 +85,8 @@ class DatabaseErrorWrapper(object):
if issubclass(exc_type, db_exc_type): if issubclass(exc_type, db_exc_type):
dj_exc_value = dj_exc_type(*exc_value.args) dj_exc_value = dj_exc_type(*exc_value.args)
dj_exc_value.__cause__ = exc_value dj_exc_value.__cause__ = exc_value
if not hasattr(exc_value, '__traceback__'):
exc_value.__traceback__ = traceback
# Only set the 'errors_occurred' flag for errors that may make # Only set the 'errors_occurred' flag for errors that may make
# the connection unusable. # the connection unusable.
if dj_exc_type not in (DataError, IntegrityError): if dj_exc_type not in (DataError, IntegrityError):

View File

@ -146,6 +146,8 @@ class LocalTimezone(ReferenceLocalTimezone):
exc_value = exc_type( exc_value = exc_type(
"Unsupported value: %r. You should install pytz." % dt) "Unsupported value: %r. You should install pytz." % dt)
exc_value.__cause__ = exc exc_value.__cause__ = exc
if not hasattr(exc, '__traceback__'):
exc.__traceback__ = sys.exc_info()[2]
six.reraise(exc_type, exc_value, sys.exc_info()[2]) six.reraise(exc_type, exc_value, sys.exc_info()[2])
utc = pytz.utc if pytz else UTC() utc = pytz.utc if pytz else UTC()

View File

@ -196,7 +196,13 @@ As per :pep:`3134`, a ``__cause__`` attribute is set with the original
(underlying) database exception, allowing access to any additional (underlying) database exception, allowing access to any additional
information provided. (Note that this attribute is available under information provided. (Note that this attribute is available under
both Python 2 and Python 3, although :pep:`3134` normally only applies both Python 2 and Python 3, although :pep:`3134` normally only applies
to Python 3.) to Python 3. To avoid unexpected differences with Python 3, Django will also
ensure that the exception made available via ``__cause__`` has a usable
``__traceback__`` attribute.)
.. versionchanged:: 1.10
The ``__traceback__`` attribute described above was added.
.. exception:: models.ProtectedError .. exception:: models.ProtectedError