.. changelog::
:version: 1.1.7
+ .. change::
+ :tags: bug, engine
+ :tickets: 3946
+ :versions: 1.2.0b1
+
+ Added an exception handler that will warn for the "cause" exception on
+ Py2K when the "autorollback" feature of :class:`.Connection` itself
+ raises an exception. In Py3K, the two exceptions are naturally reported
+ by the interpreter as one occurring during the handling of the other.
+ This is continuing with the series of changes for rollback failure
+ handling that were last visited as part of :ticket:`2696` in 1.0.12.
+
.. change::
:tags: bug, orm
:tickets: 3947
if not self._is_disconnect:
if cursor:
self._safe_close_cursor(cursor)
- self._autorollback()
+ with util.safe_reraise(warn_only=True):
+ self._autorollback()
if newraise:
util.raise_from_cause(newraise, exc_info)
from . import util as testutil
from sqlalchemy import pool, orm, util
from sqlalchemy.engine import default, url
-from sqlalchemy.util import decorator
+from sqlalchemy.util import decorator, compat
from sqlalchemy import types as sqltypes, schema, exc as sa_exc
import warnings
import re
-from .exclusions import db_spec, _is_excluded
+from .exclusions import db_spec
from . import assertsql
from . import config
from .util import fail
@contextlib.contextmanager
-def _expect_warnings(exc_cls, messages, regex=True, assert_=True):
+def _expect_warnings(exc_cls, messages, regex=True, assert_=True,
+ py2konly=False):
if regex:
filters = [re.compile(msg, re.I | re.S) for msg in messages]
with mock.patch("warnings.warn", our_warn):
yield
- if assert_:
+ if assert_ and (not py2konly or not compat.py3k):
assert not seen, "Warnings were not seen: %s" % \
", ".join("%r" % (s.pattern if regex else s) for s in seen)
"""
+ __slots__ = ('warn_only', '_exc_info')
+
+ def __init__(self, warn_only=False):
+ self.warn_only = warn_only
+
def __enter__(self):
self._exc_info = sys.exc_info()
if type_ is None:
exc_type, exc_value, exc_tb = self._exc_info
self._exc_info = None # remove potential circular references
- compat.reraise(exc_type, exc_value, exc_tb)
+ if not self.warn_only:
+ compat.reraise(exc_type, exc_value, exc_tb)
else:
if not compat.py3k and self._exc_info and self._exc_info[1]:
# emulate Py3K's behavior of telling us when an exception
# coding: utf-8
from sqlalchemy.testing import eq_, assert_raises, assert_raises_message, \
- config, is_, is_not_, le_
+ config, is_, is_not_, le_, expect_warnings
import re
from sqlalchemy.testing.util import picklers
from sqlalchemy.interfaces import ConnectionProxy
)
eq_(patched.call_count, 1)
+ def test_exception_autorollback_fails(self):
+ engine = engines.testing_engine()
+ conn = engine.connect()
+
+ def boom(connection):
+ raise engine.dialect.dbapi.OperationalError("rollback failed")
+
+ with expect_warnings(
+ r"An exception has occurred during handling of a previous "
+ r"exception. The previous exception is.*i_dont_exist",
+ py2konly=True
+ ):
+ with patch.object(conn.dialect, "do_rollback", boom) as patched:
+ assert_raises_message(
+ tsa.exc.OperationalError,
+ "rollback failed",
+ conn.execute,
+ "insert into i_dont_exist (x) values ('y')"
+ )
+
def test_exception_event_ad_hoc_context(self):
"""test that handle_error is called with a context in
cases where _handle_dbapi_error() is normally called without
-from sqlalchemy.testing import eq_, ne_, assert_raises, assert_raises_message
+from sqlalchemy.testing import eq_, ne_, assert_raises, \
+ expect_warnings, assert_raises_message
import time
from sqlalchemy import (
select, MetaData, Integer, String, create_engine, pool, exc, util)
self.dbapi.shutdown("rollback_no_disconnect")
# raises error
- assert_raises_message(
- tsa.exc.DBAPIError,
- "something broke on rollback but we didn't lose the connection",
- conn.execute, select([1])
- )
+ with expect_warnings(
+ "An exception has occurred during handling .*"
+ "something broke on execute but we didn't lose the connection",
+ py2konly=True
+ ):
+ assert_raises_message(
+ tsa.exc.DBAPIError,
+ "something broke on rollback but we didn't "
+ "lose the connection",
+ conn.execute, select([1])
+ )
assert conn.closed
assert not conn.invalidated
self.dbapi.shutdown("rollback")
# raises error
- assert_raises_message(
- tsa.exc.DBAPIError,
- "Lost the DB connection on rollback",
- conn.execute, select([1])
- )
+ with expect_warnings(
+ "An exception has occurred during handling .*"
+ "something broke on execute but we didn't lose the connection",
+ py2konly=True
+ ):
+ assert_raises_message(
+ tsa.exc.DBAPIError,
+ "Lost the DB connection on rollback",
+ conn.execute, select([1])
+ )
assert not conn.closed
assert conn.invalidated
self.dbapi.shutdown("rollback")
# raises error
- assert_raises_message(
- tsa.exc.DBAPIError,
- "Lost the DB connection on rollback",
- conn.execute, select([1])
- )
+ with expect_warnings(
+ "An exception has occurred during handling .*"
+ "something broke on execute but we didn't lose the connection",
+ py2konly=True
+ ):
+ assert_raises_message(
+ tsa.exc.DBAPIError,
+ "Lost the DB connection on rollback",
+ conn.execute, select([1])
+ )
assert conn.closed
assert conn.invalidated
self.engine.dialect.is_disconnect = is_disconnect
conn = self.engine.connect()
self.engine.test_shutdown()
- assert_raises(
- tsa.exc.DBAPIError,
- conn.execute, select([1])
- )
+ with expect_warnings(
+ "An exception has occurred during handling .*",
+ py2konly=True
+ ):
+ assert_raises(
+ tsa.exc.DBAPIError,
+ conn.execute, select([1])
+ )
def test_rollback_on_invalid_plain(self):
conn = self.engine.connect()