Browse Source

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.
Raphaël Hertzog 9 years ago
parent
commit
9f4e031bd3
4 changed files with 13 additions and 1 deletions
  1. 2 0
      django/db/migrations/loader.py
  2. 2 0
      django/db/utils.py
  3. 2 0
      django/utils/timezone.py
  4. 7 1
      docs/ref/exceptions.txt

+ 2 - 0
django/db/migrations/loader.py

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

+ 2 - 0
django/db/utils.py

@@ -85,6 +85,8 @@ class DatabaseErrorWrapper(object):
             if issubclass(exc_type, db_exc_type):
                 dj_exc_value = dj_exc_type(*exc_value.args)
                 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
                 # the connection unusable.
                 if dj_exc_type not in (DataError, IntegrityError):

+ 2 - 0
django/utils/timezone.py

@@ -146,6 +146,8 @@ class LocalTimezone(ReferenceLocalTimezone):
             exc_value = exc_type(
                 "Unsupported value: %r. You should install pytz." % dt)
             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])
 
 utc = pytz.utc if pytz else UTC()

+ 7 - 1
docs/ref/exceptions.txt

@@ -196,7 +196,13 @@ As per :pep:`3134`, a ``__cause__`` attribute is set with the original
 (underlying) database exception, allowing access to any additional
 information provided. (Note that this attribute is available under
 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