From: Mike Bayer Date: Mon, 18 Jan 2016 22:35:44 +0000 (-0500) Subject: - Fixed bug where some exception re-raise scenarios would attach X-Git-Tag: rel_1_1_0b1~84^2~35 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d4d9a6524886eb33644e8ce42212267fa569e555;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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 --- diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index c465c73ed1..a1e1fbe179 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -19,6 +19,14 @@ :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 diff --git a/lib/sqlalchemy/util/compat.py b/lib/sqlalchemy/util/compat.py index 25c88c662f..737b8a0878 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 4370d612b6..6d162ff4d7 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):