]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed bug where some exception re-raise scenarios would attach
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 18 Jan 2016 22:35:44 +0000 (17:35 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 18 Jan 2016 22:35:44 +0000 (17:35 -0500)
the exception to itself as the "cause"; while the Python 3 interpreter
is OK with this, it could cause endless loops in iPython.
fixes #3625
- add tests for reraise, raise_from_cause
- raise_from_cause is the same on py2k/3k, use just one function

doc/build/changelog/changelog_10.rst
lib/sqlalchemy/util/compat.py
test/base/test_utils.py

index c465c73ed1c2cdc9f2406aab0ad216bb6c578843..a1e1fbe1796ab20bdabd2b7f07db2200d122de50 100644 (file)
     :version: 1.0.12
     :released:
 
+    .. change::
+        :tags: bug, py3k
+        :tickets: 3625
+
+        Fixed bug where some exception re-raise scenarios would attach
+        the exception to itself as the "cause"; while the Python 3 interpreter
+        is OK with this, it could cause endless loops in iPython.
+
     .. change::
         :tags: bug, mssql
         :tickets: 3624
index 25c88c662fa538ab1ebd08210164c9a706d75f62..737b8a08780267aedf7772dcb204c1e8672fc26e 100644 (file)
@@ -177,27 +177,27 @@ from operator import attrgetter as dottedgetter
 if py3k:
     def reraise(tp, value, tb=None, cause=None):
         if cause is not None:
+            assert cause is not value, "Same cause emitted"
             value.__cause__ = cause
         if value.__traceback__ is not tb:
             raise value.with_traceback(tb)
         raise value
 
-    def raise_from_cause(exception, exc_info=None):
-        if exc_info is None:
-            exc_info = sys.exc_info()
-        exc_type, exc_value, exc_tb = exc_info
-        reraise(type(exception), exception, tb=exc_tb, cause=exc_value)
 else:
+    # not as nice as that of Py3K, but at least preserves
+    # the code line where the issue occurred
     exec("def reraise(tp, value, tb=None, cause=None):\n"
+         "    if cause is not None:\n"
+         "        assert cause is not value, 'Same cause emitted'\n"
          "    raise tp, value, tb\n")
 
-    def raise_from_cause(exception, exc_info=None):
-        # not as nice as that of Py3K, but at least preserves
-        # the code line where the issue occurred
-        if exc_info is None:
-            exc_info = sys.exc_info()
-        exc_type, exc_value, exc_tb = exc_info
-        reraise(type(exception), exception, tb=exc_tb)
+
+def raise_from_cause(exception, exc_info=None):
+    if exc_info is None:
+        exc_info = sys.exc_info()
+    exc_type, exc_value, exc_tb = exc_info
+    cause = exc_value if exc_value is not exception else None
+    reraise(type(exception), exception, tb=exc_tb, cause=cause)
 
 if py3k:
     exec_ = getattr(builtins, 'exec')
index 4370d612b648295daf5674047576095f68088a1e..6d162ff4d71ca77df51bb11187e73ef4aae0a323 100644 (file)
@@ -1,4 +1,5 @@
 import copy
+import sys
 
 from sqlalchemy import util, sql, exc, testing
 from sqlalchemy.testing import assert_raises, assert_raises_message, fixtures
@@ -2134,6 +2135,64 @@ class TestClassHierarchy(fixtures.TestBase):
             eq_(set(util.class_hierarchy(A)), set((A, B, object)))
 
 
+class ReraiseTest(fixtures.TestBase):
+    @testing.requires.python3
+    def test_raise_from_cause_same_cause(self):
+        class MyException(Exception):
+            pass
+
+        def go():
+            try:
+                raise MyException("exc one")
+            except Exception as err:
+                util.raise_from_cause(err)
+
+        try:
+            go()
+            assert False
+        except MyException as err:
+            is_(err.__cause__, None)
+
+    def test_reraise_disallow_same_cause(self):
+        class MyException(Exception):
+            pass
+
+        def go():
+            try:
+                raise MyException("exc one")
+            except Exception as err:
+                type_, value, tb = sys.exc_info()
+                util.reraise(type_, err, tb, value)
+
+        assert_raises_message(
+            AssertionError,
+            "Same cause emitted",
+            go
+        )
+
+    def test_raise_from_cause(self):
+        class MyException(Exception):
+            pass
+
+        class MyOtherException(Exception):
+            pass
+
+        me = MyException("exc on")
+
+        def go():
+            try:
+                raise me
+            except Exception:
+                util.raise_from_cause(MyOtherException("exc two"))
+
+        try:
+            go()
+            assert False
+        except MyOtherException as moe:
+            if testing.requires.python3.enabled:
+                is_(moe.__cause__, me)
+
+
 class TestClassProperty(fixtures.TestBase):
 
     def test_simple(self):