]> 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:42:02 +0000 (11:42 -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.

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 ca56a4d232026325e6d1d6e5aff7cefffeac220e..c8e33bfb2895306afa4a60fe5652209dda18cccc 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 89bea100e4e3a210c32da398cec92d5adc36f507..1935d0cadcf4b6903cf788c446f6511ed0411421 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 f7442196750abf489b79502f6c2b45ad0489d521..1ed89bacb36c89acb63964c1be008a550b5198b3 100644 (file)
@@ -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
 ----------------------------
 
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 ee8267c5cdbbc5956f55ca6908f7cab73de56f27..fa5dfca9a3ce39b9e8afb4239047d29fe7a25400 100644 (file)
@@ -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(
index 5f66e54b57169238859f8125f1da7b7ca2f303dd..5f0d74328fe098c33ecbdcdbd58f68736cd442da 100644 (file)
@@ -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()
 
index 8e58d202d02b0129faac77ae47531842d1666d56..725dcebe062757210552c9e205b9a68c2f686c1e 100644 (file)
@@ -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', )
index b7ad014082445502c19e22843273a2c8a3f7a89c..0f5bb4cb52686220c931c585042d4cf952a643f4 100644 (file)
@@ -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())
+