.. changelog::
:version: 0.9.9
+ .. change::
+ :tags: feature, engine
+ :versions: 1.0.0
+
+ Added new user-space accessors for viewing transaction isolation
+ levels; :meth:`.Connection.get_isolation_level`,
+ :attr:`.Connection.default_isolation_level`.
+
.. change::
:tags: bug, postgresql
:versions: 1.0.0
Transaction Isolation Level
---------------------------
-:func:`.create_engine` accepts an ``isolation_level``
+:func:`.create_engine` accepts an :paramref:`.create_engine.isolation_level`
parameter which results in the command ``SET SESSION
TRANSACTION ISOLATION LEVEL <level>`` being invoked for
every new connection. Valid values for this parameter are
---------------------------
All Postgresql dialects support setting of transaction isolation level
-both via a dialect-specific parameter ``isolation_level``
+both via a dialect-specific parameter :paramref:`.create_engine.isolation_level`
accepted by :func:`.create_engine`,
as well as the ``isolation_level`` argument as passed to
:meth:`.Connection.execution_options`. When using a non-psycopg2 dialect,
file-based architecture and additionally will usually require workarounds to
work when using the pysqlite driver.
+.. _sqlite_isolation_level:
+
Transaction Isolation Level
----------------------------
Behavior here varies per backend, and
individual dialects should be consulted directly.
+ Note that the isolation level can also be set on a per-:class:`.Connection`
+ basis as well, using the
+ :paramref:`.Connection.execution_options.isolation_level`
+ feature.
+
.. seealso::
- :ref:`SQLite Concurrency <sqlite_concurrency>`
+ :attr:`.Connection.default_isolation_level` - view default level
+
+ :paramref:`.Connection.execution_options.isolation_level`
+ - set per :class:`.Connection` isolation level
+
+ :ref:`SQLite Transaction Isolation <sqlite_isolation_level>`
:ref:`Postgresql Transaction Isolation <postgresql_isolation_level>`
used by the ORM internally supersedes a cache dictionary
specified here.
- :param isolation_level: Available on: Connection.
+ :param isolation_level: Available on: :class:`.Connection`.
Set the transaction isolation level for
- the lifespan of this connection. Valid values include
- those string values accepted by the ``isolation_level``
- parameter passed to :func:`.create_engine`, and are
- database specific, including those for :ref:`sqlite_toplevel`,
- :ref:`postgresql_toplevel` - see those dialect's documentation
- for further info.
+ the lifespan of this :class:`.Connection` object (*not* the
+ underyling DBAPI connection, for which the level is reset
+ to its original setting upon termination of this
+ :class:`.Connection` object).
+
+ Valid values include
+ those string values accepted by the
+ :paramref:`.create_engine.isolation_level`
+ parameter passed to :func:`.create_engine`. These levels are
+ semi-database specific; see individual dialect documentation for
+ valid levels.
Note that this option necessarily affects the underlying
DBAPI connection for the lifespan of the originating
is returned to the connection pool, i.e.
the :meth:`.Connection.close` method is called.
+ .. seealso::
+
+ :paramref:`.create_engine.isolation_level`
+ - set per :class:`.Engine` isolation level
+
+ :meth:`.Connection.get_isolation_level` - view current level
+
+ :ref:`SQLite Transaction Isolation <sqlite_isolation_level>`
+
+ :ref:`Postgresql Transaction Isolation <postgresql_isolation_level>`
+
+ :ref:`MySQL Transaction Isolation <mysql_isolation_level>`
+
+
:param no_parameters: When ``True``, if the final parameter
list or dictionary is totally empty, will invoke the
statement on the cursor as ``cursor.execute(statement)``,
@property
def connection(self):
- "The underlying DB-API connection managed by this Connection."
+ """The underlying DB-API connection managed by this Connection.
+
+ .. seealso::
+
+
+ :ref:`dbapi_connections`
+
+ """
try:
return self.__connection
except AttributeError:
return self._revalidate_connection()
+ def get_isolation_level(self):
+ """Return the current isolation level assigned to this
+ :class:`.Connection`.
+
+ This will typically be the default isolation level as determined
+ by the dialect, unless if the
+ :paramref:`.Connection.execution_options.isolation_level`
+ feature has been used to alter the isolation level on a
+ per-:class:`.Connection` basis.
+
+ This attribute will typically perform a live SQL operation in order
+ to procure the current isolation level, so the value returned is the
+ actual level on the underlying DBAPI connection regardless of how
+ this state was set. Compare to the
+ :attr:`.Connection.default_isolation_level` accessor
+ which returns the dialect-level setting without performing a SQL
+ query.
+
+ .. versionadded:: 0.9.9
+
+ .. seealso::
+
+ :attr:`.Connection.default_isolation_level` - view default level
+
+ :paramref:`.create_engine.isolation_level`
+ - set per :class:`.Engine` isolation level
+
+ :paramref:`.Connection.execution_options.isolation_level`
+ - set per :class:`.Connection` isolation level
+
+ """
+ try:
+ return self.dialect.get_isolation_level(self.connection)
+ except Exception as e:
+ self._handle_dbapi_exception(e, None, None, None, None)
+
+ @property
+ def default_isolation_level(self):
+ """The default isolation level assigned to this :class:`.Connection`.
+
+ This is the isolation level setting that the :class:`.Connection`
+ has when first procured via the :meth:`.Engine.connect` method.
+ This level stays in place until the
+ :paramref:`.Connection.execution_options.isolation_level` is used
+ to change the setting on a per-:class:`.Connection` basis.
+
+ Unlike :meth:`.Connection.get_isolation_level`, this attribute is set
+ ahead of time from the first connection procured by the dialect,
+ so SQL query is not invoked when this accessor is called.
+
+ .. versionadded:: 0.9.9
+
+ .. seealso::
+
+ :meth:`.Connection.get_isolation_level` - view current level
+
+ :paramref:`.create_engine.isolation_level`
+ - set per :class:`.Engine` isolation level
+
+ :paramref:`.Connection.execution_options.isolation_level`
+ - set per :class:`.Connection` isolation level
+
+ """
+ return self.dialect.default_isolation_level
+
def _revalidate_connection(self):
if self.__can_reconnect and self.__invalid:
if self.__transaction is not None:
for real.
This method provides direct DBAPI connection access for
- special situations. In most situations, the :class:`.Connection`
- object should be used, which is procured using the
- :meth:`.Engine.connect` method.
+ special situations when the API provided by :class:`.Connection`
+ is not needed. When a :class:`.Connection` object is already
+ present, the DBAPI connection is available using
+ the :attr:`.Connection.connection` accessor.
+
+ .. seealso::
+
+ :ref:`dbapi_connections`
"""
return None
def reset_isolation_level(self, dbapi_conn):
- """Given a DBAPI connection, revert its isolation to the default."""
+ """Given a DBAPI connection, revert its isolation to the default.
+
+ Note that this is a dialect-level method which is used as part
+ of the implementation of the :class:`.Connection` and
+ :class:`.Engine`
+ isolation level facilities; these APIs should be preferred for
+ most typical use cases.
+
+ .. seealso::
+
+ :meth:`.Connection.get_isolation_level` - view current level
+
+ :attr:`.Connection.default_isolation_level` - view default level
+
+ :paramref:`.Connection.execution_options.isolation_level` -
+ set per :class:`.Connection` isolation level
+
+ :paramref:`.create_engine.isolation_level` -
+ set per :class:`.Engine` isolation level
+
+ """
raise NotImplementedError()
def set_isolation_level(self, dbapi_conn, level):
- """Given a DBAPI connection, set its isolation level."""
+ """Given a DBAPI connection, set its isolation level.
+
+ Note that this is a dialect-level method which is used as part
+ of the implementation of the :class:`.Connection` and
+ :class:`.Engine`
+ isolation level facilities; these APIs should be preferred for
+ most typical use cases.
+
+ .. seealso::
+
+ :meth:`.Connection.get_isolation_level` - view current level
+
+ :attr:`.Connection.default_isolation_level` - view default level
+
+ :paramref:`.Connection.execution_options.isolation_level` -
+ set per :class:`.Connection` isolation level
+
+ :paramref:`.create_engine.isolation_level` -
+ set per :class:`.Engine` isolation level
+
+ """
raise NotImplementedError()
def get_isolation_level(self, dbapi_conn):
- """Given a DBAPI connection, return its isolation level."""
+ """Given a DBAPI connection, return its isolation level.
+
+ When working with a :class:`.Connection` object, the corresponding
+ DBAPI connection may be procured using the
+ :attr:`.Connection.connection` accessor.
+
+ Note that this is a dialect-level method which is used as part
+ of the implementation of the :class:`.Connection` and
+ :class:`.Engine` isolation level facilities;
+ these APIs should be preferred for most typical use cases.
+
+
+ .. seealso::
+
+ :meth:`.Connection.get_isolation_level` - view current level
+
+ :attr:`.Connection.default_isolation_level` - view default level
+
+ :paramref:`.Connection.execution_options.isolation_level` -
+ set per :class:`.Connection` isolation level
+
+ :paramref:`.create_engine.isolation_level` -
+ set per :class:`.Engine` isolation level
+
+
+ """
raise NotImplementedError()
self._test_alter_disconnect(True, False)
self._test_alter_disconnect(False, False)
+ def test_handle_error_event_connect_isolation_level(self):
+ engine = engines.testing_engine()
+
+ class MySpecialException(Exception):
+ pass
+
+ @event.listens_for(engine, "handle_error")
+ def handle_error(ctx):
+ raise MySpecialException("failed operation")
+
+ ProgrammingError = engine.dialect.dbapi.ProgrammingError
+ with engine.connect() as conn:
+ with patch.object(
+ conn.dialect, "get_isolation_level",
+ Mock(side_effect=ProgrammingError("random error"))
+ ):
+ assert_raises(
+ MySpecialException,
+ conn.get_isolation_level
+ )
+
class ProxyConnectionTest(fixtures.TestBase):
eng.dialect.get_isolation_level(conn.connection),
self._non_default_isolation_level()
)
+
+ def test_isolation_level_accessors_connection_default(self):
+ eng = create_engine(
+ testing.db.url
+ )
+ with eng.connect() as conn:
+ eq_(conn.default_isolation_level, self._default_isolation_level())
+ with eng.connect() as conn:
+ eq_(conn.get_isolation_level(), self._default_isolation_level())
+
+ def test_isolation_level_accessors_connection_option_modified(self):
+ eng = create_engine(
+ testing.db.url
+ )
+ with eng.connect() as conn:
+ c2 = conn.execution_options(
+ isolation_level=self._non_default_isolation_level())
+ eq_(conn.default_isolation_level, self._default_isolation_level())
+ eq_(conn.get_isolation_level(),
+ self._non_default_isolation_level())
+ eq_(c2.get_isolation_level(), self._non_default_isolation_level())
+