]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Added new user-space accessors for viewing transaction isolation
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 20 Jan 2015 16:37:13 +0000 (11:37 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 20 Jan 2015 16:43:27 +0000 (11:43 -0500)
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.

(cherry picked from commit c3d898e8d06c7e549bb273fc8654f5d24fab2204)

doc/build/changelog/changelog_09.rst
lib/sqlalchemy/dialects/mysql/base.py
lib/sqlalchemy/dialects/postgresql/base.py
lib/sqlalchemy/dialects/sqlite/base.py
lib/sqlalchemy/engine/__init__.py
lib/sqlalchemy/engine/base.py
lib/sqlalchemy/engine/interfaces.py
test/engine/test_execute.py
test/engine/test_transaction.py

index d9cbd5032e7a15eae65dc698ff22fa2a2da14aa6..b1ec9cbece5c5cd420343f60b242e8269e9674a4 100644 (file)
 .. 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
index d76c46a51ebac6aee13852797b43da04f72c9d32..686f21b7e014c7c86b3946fe08717b33d7037001 100644 (file)
@@ -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
index 9c2ce038bc2e53ecd0555668801d73e84f904795..36df5c05ae84220913ed489f42028f125463b678 100644 (file)
@@ -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,
index 666b9ed128f0928d8958ef492983d04b9a23e056..a30de3a978a0b25abd504b166384227a62ed0ed4 100644 (file)
@@ -92,6 +92,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
 ----------------------------
 
index 3857bdf1e72622bc2521c1ac14f65880f485c7ab..f512e260a40198c029dd41e76ef17a6d38cb04dc 100644 (file)
@@ -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>`
 
index e691bb6209a00be439f98a182838393419a4e8c7..ac3c00b31e46f926aa1d752b47ef7bdbf3e998f8 100644 (file)
@@ -169,14 +169,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
@@ -185,6 +190,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)``,
@@ -228,13 +247,85 @@ 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
         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:
@@ -1838,9 +1929,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`
 
         """
 
index 71df29cac16eef02512354209b0b3de2c4206390..b53089157294329b4c487e0b1faea1ea1c28c693 100644 (file)
@@ -638,17 +638,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()
 
index d712fed278aa327e067de28ca6b5879bb712decf..8039c691dee8a7f895ad406e15c4b80f574de3e8 100644 (file)
@@ -1853,6 +1853,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 ProxyConnectionTest(fixtures.TestBase):
 
index 1e6300b229fc8e5f10278ea045f653edf0a8da96..a3d03b0837ccc9dc29d8b02142a90c4f7bbfebba 100644 (file)
@@ -1299,3 +1299,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())
+