summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-01-20 11:37:13 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2015-01-20 11:42:02 -0500
commitc3d898e8d06c7e549bb273fc8654f5d24fab2204 (patch)
tree3b8999b9458fe59804a4a6985694fd9c077bc189
parent4032aaf097a9268bc331e4b4815d77b19ba3febb (diff)
downloadsqlalchemy-c3d898e8d06c7e549bb273fc8654f5d24fab2204.tar.gz
- Added new user-space accessors for viewing transaction isolation
levels; :meth:`.Connection.get_isolation_level`, :attr:`.Connection.default_isolation_level`. - enhance documentation inter-linkage between new accessors, existing isolation_level parameters, as well as in the dialect-level methods which should be fully covered by Engine/Connection level APIs now.
-rw-r--r--doc/build/changelog/changelog_09.rst8
-rw-r--r--lib/sqlalchemy/dialects/mysql/base.py2
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py2
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py2
-rw-r--r--lib/sqlalchemy/engine/__init__.py12
-rw-r--r--lib/sqlalchemy/engine/base.py118
-rw-r--r--lib/sqlalchemy/engine/interfaces.py71
-rw-r--r--test/engine/test_execute.py21
-rw-r--r--test/engine/test_transaction.py22
9 files changed, 241 insertions, 17 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst
index d9cbd5032..b1ec9cbec 100644
--- a/doc/build/changelog/changelog_09.rst
+++ b/doc/build/changelog/changelog_09.rst
@@ -15,6 +15,14 @@
: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
:tickets: 3174
diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py
index ca56a4d23..c8e33bfb2 100644
--- a/lib/sqlalchemy/dialects/mysql/base.py
+++ b/lib/sqlalchemy/dialects/mysql/base.py
@@ -106,7 +106,7 @@ to be used.
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
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index 89bea100e..1935d0cad 100644
--- a/lib/sqlalchemy/dialects/postgresql/base.py
+++ b/lib/sqlalchemy/dialects/postgresql/base.py
@@ -48,7 +48,7 @@ Transaction Isolation Level
---------------------------
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,
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index f74421967..1ed89bacb 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -107,6 +107,8 @@ The following subsections introduce areas that are impacted by SQLite's
file-based architecture and additionally will usually require workarounds to
work when using the pysqlite driver.
+.. _sqlite_isolation_level:
+
Transaction Isolation Level
----------------------------
diff --git a/lib/sqlalchemy/engine/__init__.py b/lib/sqlalchemy/engine/__init__.py
index 3857bdf1e..f512e260a 100644
--- a/lib/sqlalchemy/engine/__init__.py
+++ b/lib/sqlalchemy/engine/__init__.py
@@ -257,9 +257,19 @@ def create_engine(*args, **kwargs):
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>`
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index ee8267c5c..fa5dfca9a 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -201,14 +201,19 @@ class Connection(Connectable):
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
@@ -217,6 +222,20 @@ class Connection(Connectable):
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)``,
@@ -260,7 +279,14 @@ class Connection(Connectable):
@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
@@ -270,6 +296,71 @@ class Connection(Connectable):
except Exception as e:
self._handle_dbapi_exception(e, None, None, None, None)
+ 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.__branch_from:
return self.__branch_from._revalidate_connection()
@@ -1982,9 +2073,14 @@ class Engine(Connectable, log.Identified):
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 self._wrap_pool_connect(
diff --git a/lib/sqlalchemy/engine/interfaces.py b/lib/sqlalchemy/engine/interfaces.py
index 5f66e54b5..5f0d74328 100644
--- a/lib/sqlalchemy/engine/interfaces.py
+++ b/lib/sqlalchemy/engine/interfaces.py
@@ -654,17 +654,82 @@ class Dialect(object):
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()
diff --git a/test/engine/test_execute.py b/test/engine/test_execute.py
index 8e58d202d..725dcebe0 100644
--- a/test/engine/test_execute.py
+++ b/test/engine/test_execute.py
@@ -1900,6 +1900,27 @@ class HandleErrorTest(fixtures.TestBase):
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 HandleInvalidatedOnConnectTest(fixtures.TestBase):
__requires__ = ('sqlite', )
diff --git a/test/engine/test_transaction.py b/test/engine/test_transaction.py
index b7ad01408..0f5bb4cb5 100644
--- a/test/engine/test_transaction.py
+++ b/test/engine/test_transaction.py
@@ -1385,3 +1385,25 @@ class IsolationLevelTest(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())
+