--- /dev/null
+.. change::
+ :tags: bug, engine, regression
+ :tickets: 8717
+
+ Fixed issue where the :meth:`.PoolEvents.reset` event hook would not be
+ called when a :class:`.Connection` were closed which already called
+ ``.rollback()`` on its own transaction, due to an enhancement in the 1.4
+ series that ensures ``.rollback()`` is only called once in this scenario,
+ rather than twice. This would prevent custom pool reset schemes from being
+ used within this hook. This was a regression that appeared in version 1.4.
+
+ For version 1.4, the :meth:`.PoolEvents.checkin` likely remains a better
+ event to use for custom "reset" implementations. Version 2.0 will feature
+ an improved version of :meth:`.PoolEvents.reset` which is called for
+ additional scenarios such as termination of asyncio connections, and is
+ also passed contextual information about the reset, to allow for "custom
+ connection reset" schemes which can respond to different reset scenarios in
+ different ways.
+
changelog_render_changeset = "https://www.sqlalchemy.org/trac/changeset/%s"
-exclude_patterns = ["build", "**/unreleased*/*", "**/*_include.rst"]
+exclude_patterns = ["build", "**/unreleased*/*", "**/*_include.rst", ".venv"]
# zzzeeksphinx makes these conversions when it is rendering the
# docstrings classes, methods, and functions within the scope of
Reset On Return
---------------
-The pool also includes the a "reset on return" feature which will call the
-``rollback()`` method of the DBAPI connection when the connection is returned
-to the pool. This is so that any existing
-transaction on the connection is removed, not only ensuring that no existing
-state remains on next usage, but also so that table and row locks are released
-as well as that any isolated data snapshots are removed. This ``rollback()``
-occurs in most cases even when using an :class:`_engine.Engine` object,
-except in the case when the :class:`_engine.Connection` can guarantee
-that a ``rollback()`` has been called immediately before the connection
-is returned to the pool.
-
-For most DBAPIs, the call to ``rollback()`` is very inexpensive and if the
+The pool includes "reset on return" behavior which will call the ``rollback()``
+method of the DBAPI connection when the connection is returned to the pool.
+This is so that any existing transactional state is removed from the
+connection, which includes not just uncommitted data but table and row locks as
+well. For most DBAPIs, the call to ``rollback()`` is inexpensive, and if the
DBAPI has already completed a transaction, the method should be a no-op.
-However, for DBAPIs that incur performance issues with ``rollback()`` even if
-there's no state on the connection, this behavior can be disabled using the
-``reset_on_return`` option of :class:`_pool.Pool`. The behavior is safe
-to disable under the following conditions:
-
-* If the database does not support transactions at all, such as using
- MySQL with the MyISAM engine, or the DBAPI is used in autocommit
- mode only, the behavior can be disabled.
-* If the pool itself doesn't maintain a connection after it's checked in,
- such as when using :class:`.NullPool`, the behavior can be disabled.
-* Otherwise, it must be ensured that:
-
- * the application ensures that all :class:`_engine.Connection`
- objects are explicitly closed out using a context manager (i.e. ``with``
- block) or a ``try/finally`` style block
- * connections are never allowed to be garbage collected before being explicitly
- closed.
- * the DBAPI connection itself, e.g. ``connection.connection``, is not used
- directly, or the application ensures that ``.rollback()`` is called
- on this connection before releasing it back to the connection pool.
-
-The "reset on return" step may be logged using the ``logging.DEBUG``
+
+Disabling Reset on Return for non-transactional connections
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For very specific cases where this ``rollback()`` is not useful, such as when
+using a connection that is configured for
+:ref:`autocommit <dbapi_autocommit_understanding>` or when using a database
+that has no ACID capabilities such as the MyISAM engine of MySQL, the
+reset-on-return behavior can be disabled, which is typically done for
+performance reasons. This can be affected by using the
+:paramref:`_pool.Pool.reset_on_return` parameter of :class:`_pool.Pool`, which
+is also available from :func:`_sa.create_engine` as
+:paramref:`_sa.create_engine.pool_reset_on_return`, passing a value of ``None``.
+This is illustrated in the example below, in conjunction with the
+:paramref:`.create_engine.isolation_level` parameter setting of
+``AUTOCOMMIT``::
+
+ non_acid_engine = create_engine(
+ "mysql://scott:tiger@host/db",
+ pool_reset_on_return=None,
+ isolation_level="AUTOCOMMIT",
+ )
+
+The above engine won't actually perform ROLLBACK when connections are returned
+to the pool; since AUTOCOMMIT is enabled, the driver will also not perform
+any BEGIN operation.
+
+Custom Reset-on-Return Schemes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+"reset on return" consisting of a single ``rollback()`` may not be sufficient
+for some use cases; in particular, applications which make use of temporary
+tables may wish for these tables to be automatically removed on connection
+checkin. Some (but notably not all) backends include features that can "reset"
+such tables within the scope of a database connection, which may be a desirable
+behavior for connection pool reset. Other server resources such as prepared
+statement handles and server-side statement caches may persist beyond the
+checkin process, which may or may not be desirable, depending on specifics.
+Again, some (but again not all) backends may provide for a means of resetting
+this state. The two SQLAlchemy included dialects which are known to have
+such reset schemes include Microsoft SQL Server, where an undocumented but
+widely known stored procedure called ``sp_reset_connection`` is often used,
+and PostgreSQL, which has a well-documented series of commands including
+``DISCARD`` ``RESET``, ``DEALLOCATE``, and ``UNLISTEN``.
+
+.. note: next paragraph + example should match mssql/base.py example
+
+The following example illustrates how to replace reset on return with the
+Microsoft SQL Server ``sp_reset_connection`` stored procedure, using the
+:meth:`.PoolEvents.reset` event hook (**requires SQLAlchemy 1.4.43 or greater**).
+The :paramref:`_sa.create_engine.pool_reset_on_return` parameter is set to
+``None`` so that the custom scheme can replace the default behavior completely.
+The custom hook implementation calls ``.rollback()`` in any case, as it's
+usually important that the DBAPI's own tracking of commit/rollback will remain
+consistent with the state of the transaction::
+
+ from sqlalchemy import create_engine
+ from sqlalchemy import event
+
+ mssql_engine = create_engine(
+ "mssql+pyodbc://scott:tiger^5HHH@mssql2017:1433/test?driver=ODBC+Driver+17+for+SQL+Server",
+ # disable default reset-on-return scheme
+ pool_reset_on_return=None,
+ )
+
+
+ @event.listens_for(mssql_engine, "reset")
+ def _reset_mssql(dbapi_connection, connection_record, reset_state):
+ dbapi_connection.execute("{call sys.sp_reset_connection}")
+
+ # so that the DBAPI itself knows that the connection has been
+ # reset
+ dbapi_connection.rollback()
+
+.. versionchanged:: 1.4.43 Ensured the :meth:`.PoolEvents.reset` event
+ is invoked for all "reset" occurrences, so that it's appropriate
+ as a place for custom "reset" handlers. Previous schemes which
+ use the :meth:`.PoolEvents.checkin` handler remain usable as well.
+
+.. seealso::
+ * :ref:`mssql_reset_on_return` - in the :ref:`mssql_toplevel` documentation
+ * :ref:`postgresql_reset_on_return` in the :ref:`postgresql_toplevel` documentation
+
+Logging reset-on-return events
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Logging for pool events including reset on return can be set
+``logging.DEBUG``
log level along with the ``sqlalchemy.pool`` logger, or by setting
-``echo_pool='debug'`` with :func:`_sa.create_engine`.
+:paramref:`_sa.create_engine.echo_pool` to ``"debug"`` when using
+:func:`_sa.create_engine`::
+
+ >>> from sqlalchemy import create_engine
+ >>> engine = create_engine("postgresql://scott:tiger@localhost/test", echo_pool="debug")
+
+The above pool will show verbose logging including reset on return::
+
+ >>> c1 = engine.connect()
+ DEBUG sqlalchemy.pool.impl.QueuePool Created new connection <connection object ...>
+ DEBUG sqlalchemy.pool.impl.QueuePool Connection <connection object ...> checked out from pool
+ >>> c1.close()
+ DEBUG sqlalchemy.pool.impl.QueuePool Connection <connection object ...> being returned to pool
+ DEBUG sqlalchemy.pool.impl.QueuePool Connection <connection object ...> rollback-on-return
+
Pool Events
-----------
--------------------------------------------------
.. autoclass:: sqlalchemy.pool.Pool
-
- .. automethod:: __init__
- .. automethod:: connect
- .. automethod:: dispose
- .. automethod:: recreate
+ :members:
.. autoclass:: sqlalchemy.pool.QueuePool
-
- .. automethod:: __init__
- .. automethod:: connect
+ :members:
.. autoclass:: SingletonThreadPool
-
- .. automethod:: __init__
+ :members:
.. autoclass:: AssertionPool
-
- .. automethod:: __init__
+ :members:
.. autoclass:: NullPool
-
- .. automethod:: __init__
+ :members:
.. autoclass:: StaticPool
-
- .. automethod:: __init__
+ :members:
.. autoclass:: _ConnectionFairy
:members:
:ref:`dbapi_autocommit`
+.. _mssql_reset_on_return:
+
+Temporary Table / Resource Reset for Connection Pooling
+-------------------------------------------------------
+
+The :class:`.QueuePool` connection pool implementation used
+by the SQLAlchemy :class:`_sa.Engine` object includes
+:ref:`reset on return <pool_reset_on_return>` behavior that will invoke
+the DBAPI ``.rollback()`` method when connections are returned to the pool.
+While this rollback will clear out the immediate state used by the previous
+transaction, it does not cover a wider range of session-level state, including
+temporary tables as well as other server state such as prepared statement
+handles and statement caches. An undocumented SQL Server procedure known
+as ``sp_reset_connection`` is known to be a workaround for this issue which
+will reset most of the session state that builds up on a connection, including
+temporary tables.
+
+To install ``sp_reset_connection`` as the means of performing reset-on-return,
+the :meth:`.PoolEvents.reset` event hook may be used, as demonstrated in the
+example below (**requires SQLAlchemy 1.4.43 or greater**). The
+:paramref:`_sa.create_engine.pool_reset_on_return` parameter is set to ``None``
+so that the custom scheme can replace the default behavior completely. The
+custom hook implementation calls ``.rollback()`` in any case, as it's usually
+important that the DBAPI's own tracking of commit/rollback will remain
+consistent with the state of the transaction::
+
+ from sqlalchemy import create_engine
+ from sqlalchemy import event
+
+ mssql_engine = create_engine(
+ "mssql+pyodbc://scott:tiger^5HHH@mssql2017:1433/test?driver=ODBC+Driver+17+for+SQL+Server",
+
+ # disable default reset-on-return scheme
+ pool_reset_on_return=None,
+ )
+
+
+ @event.listens_for(mssql_engine, "reset")
+ def _reset_mssql(dbapi_connection, connection_record, reset_state):
+ dbapi_connection.execute("{call sys.sp_reset_connection}")
+
+ # so that the DBAPI itself knows that the connection has been
+ # reset
+ dbapi_connection.rollback()
+
+.. versionchanged:: 1.4.43 Ensured the :meth:`.PoolEvents.reset` event
+ is invoked for all "reset" occurrences, so that it's appropriate
+ as a place for custom "reset" handlers. Previous schemes which
+ use the :meth:`.PoolEvents.checkin` handler remain usable as well.
+
+.. seealso::
+
+ :ref:`pool_reset_on_return` - in the :ref:`pooling_toplevel` documentation
+
Nullability
-----------
MSSQL has support for three levels of column nullability. The default
.. versionadded:: 1.4 added support for the ``postgresql_readonly``
and ``postgresql_deferrable`` execution options.
+.. _postgresql_reset_on_return:
+
+Temporary Table / Resource Reset for Connection Pooling
+-------------------------------------------------------
+
+The :class:`.QueuePool` connection pool implementation used
+by the SQLAlchemy :class:`_sa.Engine` object includes
+:ref:`reset on return <pool_reset_on_return>` behavior that will invoke
+the DBAPI ``.rollback()`` method when connections are returned to the pool.
+While this rollback will clear out the immediate state used by the previous
+transaction, it does not cover a wider range of session-level state, including
+temporary tables as well as other server state such as prepared statement
+handles and statement caches. The PostgreSQL database includes a variety
+of commands which may be used to reset this state, including
+``DISCARD``, ``RESET``, ``DEALLOCATE``, and ``UNLISTEN``.
+
+
+To install
+one or more of these commands as the means of performing reset-on-return,
+the :meth:`.PoolEvents.reset` event hook may be used, as demonstrated
+in the example below (**requires SQLAlchemy 1.4.43 or greater**). The implementation
+will end transactions in progress as well as discard temporary tables
+using the ``CLOSE``, ``RESET`` and ``DISCARD`` commands; see the PostgreSQL
+documentation for background on what each of these statements do.
+
+The :paramref:`_sa.create_engine.pool_reset_on_return` parameter
+is set to ``None`` so that the custom scheme can replace the default behavior
+completely. The custom hook implementation calls ``.rollback()`` in any case,
+as it's usually important that the DBAPI's own tracking of commit/rollback
+will remain consistent with the state of the transaction::
+
+
+ from sqlalchemy import create_engine
+ from sqlalchemy import event
+
+ postgresql_engine = create_engine(
+ "postgresql+pyscopg2://scott:tiger@hostname/dbname",
+
+ # disable default reset-on-return scheme
+ pool_reset_on_return=None,
+ )
+
+
+ @event.listens_for(postgresql_engine, "reset")
+ def _reset_mssql(dbapi_connection, connection_record, reset_state):
+ dbapi_connection.execute("CLOSE ALL")
+ dbapi_connection.execute("RESET ALL")
+ dbapi_connection.execute("DISCARD TEMP")
+
+ # so that the DBAPI itself knows that the connection has been
+ # reset
+ dbapi_connection.rollback()
+
+.. versionchanged:: 1.4.43 Ensured the :meth:`.PoolEvents.reset` event
+ is invoked for all "reset" occurrences, so that it's appropriate
+ as a place for custom "reset" handlers. Previous schemes which
+ use the :meth:`.PoolEvents.checkin` handler remain usable as well.
+
+.. seealso::
+
+ :ref:`pool_reset_on_return` - in the :ref:`pooling_toplevel` documentation
+
.. _postgresql_alternate_search_path:
Setting Alternate Search Paths on Connect
# as we just closed the transaction, close the connection
# pool connection without doing an additional reset
if skip_reset:
- conn._close_no_reset()
+ conn._close_special(transaction_reset=True)
else:
conn.close()
.. seealso::
- :paramref:`_pool.Pool.reset_on_return`
+ :ref:`pool_reset_on_return`
:param pool_timeout=30: number of seconds to wait before giving
up on getting a connection from the pool. This is only used
def _version_signature_changes(parent_dispatch_cls, dispatch_collection):
since, args, conv = dispatch_collection.legacy_signatures[0]
return (
- "\n.. deprecated:: %(since)s\n"
- " The :class:`.%(clsname)s.%(event_name)s` event now accepts the \n"
- " arguments ``%(named_event_arguments)s%(has_kw_arguments)s``.\n"
+ "\n.. versionchanged:: %(since)s\n"
+ " The :meth:`.%(clsname)s.%(event_name)s` event now accepts the \n"
+ " arguments %(named_event_arguments)s%(has_kw_arguments)s.\n"
" Support for listener functions which accept the previous \n"
' argument signature(s) listed above as "deprecated" will be \n'
" removed in a future release."
"since": since,
"clsname": parent_dispatch_cls.__name__,
"event_name": dispatch_collection.name,
- "named_event_arguments": ", ".join(dispatch_collection.arg_names),
+ "named_event_arguments": ", ".join(
+ ":paramref:`.%(clsname)s.%(event_name)s.%(param_name)s`"
+ % {
+ "clsname": parent_dispatch_cls.__name__,
+ "event_name": dispatch_collection.name,
+ "param_name": param_name,
+ }
+ for param_name in dispatch_collection.arg_names
+ ),
"has_kw_arguments": ", **kw" if dispatch_collection.has_kw else "",
}
)
logging.
:param reset_on_return: Determine steps to take on
- connections as they are returned to the pool, which were
- not otherwise handled by a :class:`_engine.Connection`.
-
- reset_on_return can have any of these values:
-
- * ``"rollback"`` - call rollback() on the connection,
- to release locks and transaction resources.
- This is the default value. The vast majority
- of use cases should leave this value set.
- * ``True`` - same as 'rollback', this is here for
- backwards compatibility.
- * ``"commit"`` - call commit() on the connection,
- to release locks and transaction resources.
- A commit here may be desirable for databases that
- cache query plans if a commit is emitted,
- such as Microsoft SQL Server. However, this
- value is more dangerous than 'rollback' because
- any data changes present on the transaction
- are committed unconditionally.
- * ``None`` - don't do anything on the connection.
- This setting is only appropriate if the database / DBAPI
- works in pure "autocommit" mode at all times, or if the
- application uses the :class:`_engine.Engine` with consistent
- connectivity patterns. See the section
- :ref:`pool_reset_on_return` for more details.
-
- * ``False`` - same as None, this is here for
- backwards compatibility.
+ connections as they are returned to the pool, which were
+ not otherwise handled by a :class:`_engine.Connection`.
+ Available from :func:`_sa.create_engine` via the
+ :paramref:`_sa.create_engine.pool_reset_on_return` parameter.
+
+ :paramref:`_pool.Pool.reset_on_return` can have any of these values:
+
+ * ``"rollback"`` - call rollback() on the connection,
+ to release locks and transaction resources.
+ This is the default value. The vast majority
+ of use cases should leave this value set.
+ * ``"commit"`` - call commit() on the connection,
+ to release locks and transaction resources.
+ A commit here may be desirable for databases that
+ cache query plans if a commit is emitted,
+ such as Microsoft SQL Server. However, this
+ value is more dangerous than 'rollback' because
+ any data changes present on the transaction
+ are committed unconditionally.
+ * ``None`` - don't do anything on the connection.
+ This setting may be appropriate if the database / DBAPI
+ works in pure "autocommit" mode at all times, or if
+ a custom reset handler is established using the
+ :meth:`.PoolEvents.reset` event handler.
+ * ``True`` - same as 'rollback', this is here for
+ backwards compatibility.
+ * ``False`` - same as None, this is here for
+ backwards compatibility.
+
+ For further customization of reset on return, the
+ :meth:`.PoolEvents.reset` event hook may be used which can perform
+ any connection activity desired on reset. (requires version 1.4.43
+ or greater)
.. seealso::
rec.fairy_ref = ref = weakref.ref(
fairy,
lambda ref: _finalize_fairy
- and _finalize_fairy(None, rec, pool, ref, echo, True),
+ and _finalize_fairy(
+ None, rec, pool, ref, echo, transaction_was_reset=False
+ ),
)
_strong_ref_connection_records[ref] = rec
if echo:
pool,
ref, # this is None when called directly, not by the gc
echo,
- reset=True,
+ transaction_was_reset=False,
fairy=None,
):
"""Cleanup for a :class:`._ConnectionFairy` whether or not it's already
if dbapi_connection is not None:
if connection_record and echo:
pool.logger.debug(
- "Connection %r being returned to pool%s",
+ "Connection %r being returned to pool",
dbapi_connection,
- ", transaction state was already reset by caller"
- if not reset
- else "",
)
try:
echo,
)
assert fairy.dbapi_connection is dbapi_connection
- if reset and can_manipulate_connection:
- fairy._reset(pool)
+ if can_manipulate_connection:
+ fairy._reset(pool, transaction_was_reset)
if detach:
if connection_record:
def _checkout_existing(self):
return _ConnectionFairy._checkout(self._pool, fairy=self)
- def _checkin(self, reset=True):
+ def _checkin(self, transaction_was_reset=False):
_finalize_fairy(
self.dbapi_connection,
self._connection_record,
self._pool,
None,
self._echo,
- reset=reset,
+ transaction_was_reset=transaction_was_reset,
fairy=self,
)
self.dbapi_connection = None
_close = _checkin
- def _reset(self, pool):
+ def _reset(self, pool, transaction_was_reset=False):
if pool.dispatch.reset:
pool.dispatch.reset(self, self._connection_record)
if pool._reset_on_return is reset_rollback:
- if self._echo:
- pool.logger.debug(
- "Connection %s rollback-on-return", self.dbapi_connection
- )
- pool._dialect.do_rollback(self)
+ if transaction_was_reset:
+ if self._echo:
+ pool.logger.debug(
+ "Connection %s reset, transaction already reset",
+ self.dbapi_connection,
+ )
+ else:
+ if self._echo:
+ pool.logger.debug(
+ "Connection %s rollback-on-return",
+ self.dbapi_connection,
+ )
+ pool._dialect.do_rollback(self)
elif pool._reset_on_return is reset_commit:
if self._echo:
pool.logger.debug(
if self._counter == 0:
self._checkin()
- def _close_no_reset(self):
+ def _close_special(self, transaction_reset=False):
self._counter -= 1
if self._counter == 0:
- self._checkin(reset=False)
+ self._checkin(transaction_was_reset=transaction_reset)
def reset(self, dbapi_connection, connection_record):
"""Called before the "reset" action occurs for a pooled connection.
- This event represents
- when the ``rollback()`` method is called on the DBAPI connection
- before it is returned to the pool. The behavior of "reset" can
- be controlled, including disabled, using the ``reset_on_return``
- pool argument.
-
-
+ This event represents when the ``rollback()`` method is called on the
+ DBAPI connection before it is returned to the pool or discarded. A
+ custom "reset" strategy may be implemented using this event hook, which
+ may also be combined with disabling the default "reset" behavior using
+ the :paramref:`_pool.Pool.reset_on_return` parameter.
+
+ The primary difference between the :meth:`_events.PoolEvents.reset` and
+ :meth:`_events.PoolEvents.checkin` events are that
+ :meth:`_events.PoolEvents.reset` is called not just for pooled
+ connections that are being returned to the pool, but also for
+ connections that were detached using the
+ :meth:`_engine.Connection.detach` method.
+
+ Note that the event **is not** invoked for connections that were
+ invalidated using :meth:`_engine.Connection.invalidate`. These
+ events may be intercepted using the :meth:`.PoolEvents.soft_invalidate`
+ and :meth:`.PoolEvents.invalidate` event hooks, and all "connection
+ close" events may be intercepted using :meth:`.PoolEvents.close`.
The :meth:`_events.PoolEvents.reset` event is usually followed by the
- :meth:`_events.PoolEvents.checkin` event is called, except in those
+ :meth:`_events.PoolEvents.checkin` event, except in those
cases where the connection is discarded immediately after reset.
+ In the 1.4 series, the event is also not invoked for asyncio
+ connections that are being garbage collected without their being
+ explicitly returned to the pool. This is due to the lack of an event
+ loop which prevents "reset" operations from taking place. Version 2.0
+ will feature an enhanced version of :meth:`.PoolEvents.reset` which is
+ invoked in this scenario while passing additional contextual
+ information indicating that an event loop is not guaranteed
+ to be present.
+
:param dbapi_connection: a DBAPI connection.
The :attr:`._ConnectionRecord.dbapi_connection` attribute.
conn.close()
conn = None
+ conn = q.connect()
+ conn._close_special(transaction_reset=True)
+ conn = None
+
+ conn = q.connect()
+ conn._close_special(transaction_reset=False)
+ conn = None
+
conn = q.connect()
conn = None
del conn
[
"Created new connection %r",
"Connection %r checked out from pool",
- "Connection %r being returned to pool%s",
+ "Connection %r being returned to pool",
+ "Connection %s rollback-on-return",
+ "Connection %r checked out from pool",
+ "Connection %r being returned to pool",
"Connection %s rollback-on-return",
"Connection %r checked out from pool",
- "Connection %r being returned to pool%s",
+ "Connection %r being returned to pool",
+ "Connection %s reset, transaction already reset",
+ "Connection %r checked out from pool",
+ "Connection %r being returned to pool",
"Connection %s rollback-on-return",
"Connection %r checked out from pool",
- "Connection %r being returned to pool%s",
+ "Connection %r being returned to pool",
"Connection %s rollback-on-return",
"%s connection %r",
]
import weakref
import sqlalchemy as tsa
+from sqlalchemy import create_engine
from sqlalchemy import event
from sqlalchemy import pool
from sqlalchemy import select
pool.QueuePool(creator=lambda: dbapi.connect("foo.db"), **kw),
)
- def test_plain_rollback(self):
+ def _engine_fixture(self, **kw):
+ dbapi = Mock()
+
+ return dbapi, create_engine(
+ "postgresql://",
+ module=dbapi,
+ creator=lambda: dbapi.connect("foo.db"),
+ _initialize=False,
+ )
+
+ def test_custom(self):
+ dbapi, p = self._fixture(reset_on_return=None)
+
+ @event.listens_for(p, "reset")
+ def custom_reset(dbapi_conn, record):
+ dbapi_conn.special_reset_method()
+
+ c1 = p.connect()
+ c1.close()
+
+ assert dbapi.connect().special_reset_method.called
+ assert not dbapi.connect().rollback.called
+ assert not dbapi.connect().commit.called
+
+ @testing.combinations(True, False, argnames="assert_w_event")
+ @testing.combinations(True, False, argnames="use_engine_transaction")
+ def test_custom_via_engine(self, assert_w_event, use_engine_transaction):
+ dbapi, engine = self._engine_fixture(reset_on_return=None)
+
+ if assert_w_event:
+
+ @event.listens_for(engine, "reset")
+ def custom_reset(dbapi_conn, record):
+ dbapi_conn.special_reset_method()
+
+ c1 = engine.connect()
+ if use_engine_transaction:
+ c1.begin()
+ c1.close()
+ assert dbapi.connect().rollback.called
+
+ if assert_w_event:
+ assert dbapi.connect().special_reset_method.called
+
+ @testing.combinations(True, False, argnames="assert_w_event")
+ def test_plain_rollback(self, assert_w_event):
dbapi, p = self._fixture(reset_on_return="rollback")
+ if assert_w_event:
+
+ @event.listens_for(p, "reset")
+ def custom_reset(dbapi_conn, record):
+ dbapi_conn.special_reset_method()
+
c1 = p.connect()
c1.close()
assert dbapi.connect().rollback.called
assert not dbapi.connect().commit.called
+ if assert_w_event:
+ assert dbapi.connect().special_reset_method.called
+
+ @testing.combinations(True, False, argnames="assert_w_event")
+ @testing.combinations(True, False, argnames="use_engine_transaction")
+ def test_plain_rollback_via_engine(
+ self, assert_w_event, use_engine_transaction
+ ):
+ dbapi, engine = self._engine_fixture(reset_on_return="rollback")
+
+ if assert_w_event:
+
+ @event.listens_for(engine, "reset")
+ def custom_reset(dbapi_conn, record):
+ dbapi_conn.special_reset_method()
+
+ c1 = engine.connect()
+ if use_engine_transaction:
+ c1.begin()
+ c1.close()
+ assert dbapi.connect().rollback.called
+
+ if assert_w_event:
+ assert dbapi.connect().special_reset_method.called
+
def test_plain_commit(self):
dbapi, p = self._fixture(reset_on_return="commit")