diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2016-01-18 17:35:44 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2016-01-18 17:35:44 -0500 |
commit | d4d9a6524886eb33644e8ce42212267fa569e555 (patch) | |
tree | a0c6b35b7073975204e8534d4724539a4178f89f | |
parent | b985483c594c463c02045dd7bbaf10e8b1e83d8c (diff) | |
download | sqlalchemy-d4d9a6524886eb33644e8ce42212267fa569e555.tar.gz |
- 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.
fixes #3625
- add tests for reraise, raise_from_cause
- raise_from_cause is the same on py2k/3k, use just one function
-rw-r--r-- | doc/build/changelog/changelog_10.rst | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/util/compat.py | 24 | ||||
-rw-r--r-- | test/base/test_utils.py | 59 |
3 files changed, 79 insertions, 12 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index c465c73ed..a1e1fbe17 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -20,6 +20,14 @@ :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 :pullreq: bitbucket:70 diff --git a/lib/sqlalchemy/util/compat.py b/lib/sqlalchemy/util/compat.py index 25c88c662..737b8a087 100644 --- a/lib/sqlalchemy/util/compat.py +++ b/lib/sqlalchemy/util/compat.py @@ -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') diff --git a/test/base/test_utils.py b/test/base/test_utils.py index 4370d612b..6d162ff4d 100644 --- a/test/base/test_utils.py +++ b/test/base/test_utils.py @@ -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): |