--- /dev/null
+.. change::
+ :tags: bug, engine
+ :tickets: 7272
+ :versions: 2.0.0b1
+
+ Fixed issue in future :class:`_future.Engine` where calling upon
+ :meth:`_future.Engine.begin` and entering the context manager would not
+ close the connection if the actual BEGIN operation failed for some reason,
+ such as an event handler raising an exception; this use case failed to be
+ tested for the future version of the engine. Note that the "future" context
+ managers which handle ``begin()`` blocks in Core and ORM don't actually run
+ the "BEGIN" operation until the context managers are actually entered. This
+ is different from the legacy version which runs the "BEGIN" operation up
+ front.
execution_options=legacy_engine._execution_options,
)
- class _trans_ctx(object):
- def __init__(self, conn):
- self.conn = conn
-
- def __enter__(self):
- self.transaction = self.conn.begin()
- self.transaction.__enter__()
- return self.conn
-
- def __exit__(self, type_, value, traceback):
- try:
- self.transaction.__exit__(type_, value, traceback)
- finally:
- self.conn.close()
-
+ @util.contextmanager
def begin(self):
"""Return a :class:`_future.Connection` object with a transaction
begun.
:meth:`_future.Connection.begin`
"""
- conn = self.connect()
- return self._trans_ctx(conn)
+ with self.connect() as conn:
+ with conn.begin():
+ yield conn
def connect(self):
"""Return a new :class:`_future.Connection` object.
testing.run_as_contextmanager(ctx, fn, 5, value=8)
self._assert_fn(5, value=8)
- def test_transaction_engine_ctx_begin_fails(self):
+ def test_transaction_engine_ctx_begin_fails_dont_enter_enter(self):
+ """test #7272"""
engine = engines.testing_engine()
mock_connection = Mock(
return_value=Mock(begin=Mock(side_effect=Exception("boom")))
)
- engine._connection_cls = mock_connection
- assert_raises(Exception, engine.begin)
+ with mock.patch.object(engine, "_connection_cls", mock_connection):
+ if testing.requires.legacy_engine.enabled:
+ with expect_raises_message(Exception, "boom"):
+ engine.begin()
+ else:
+ # context manager isn't entered, doesn't actually call
+ # connect() or connection.begin()
+ engine.begin()
- eq_(mock_connection.return_value.close.mock_calls, [call()])
+ if testing.requires.legacy_engine.enabled:
+ eq_(mock_connection.return_value.close.mock_calls, [call()])
+ else:
+ eq_(mock_connection.return_value.close.mock_calls, [])
+
+ def test_transaction_engine_ctx_begin_fails_include_enter(self):
+ """test #7272"""
+ engine = engines.testing_engine()
+
+ close_mock = Mock()
+ with mock.patch.object(
+ engine._connection_cls,
+ "begin",
+ Mock(side_effect=Exception("boom")),
+ ), mock.patch.object(engine._connection_cls, "close", close_mock):
+ with expect_raises_message(Exception, "boom"):
+ with engine.begin():
+ pass
+
+ eq_(close_mock.mock_calls, [call()])
def test_transaction_engine_ctx_rollback(self):
fn = self._trans_rollback_fn()
fn(conn, 5, value=8)
self._assert_fn(5, value=8)
+ @testing.requires.legacy_engine
def test_connect_as_ctx_noautocommit(self):
fn = self._trans_fn()
self._assert_no_data()
self._assert_no_data()
+class FutureConvenienceExecuteTest(
+ fixtures.FutureEngineMixin, ConvenienceExecuteTest
+):
+ __backend__ = True
+
+
class CompiledCacheTest(fixtures.TestBase):
__backend__ = True