]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- The exception wrapping system for DBAPI errors can now accommodate
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 29 Jul 2014 18:06:43 +0000 (14:06 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 29 Jul 2014 18:06:43 +0000 (14:06 -0400)
non-standard DBAPI exceptions, such as the psycopg2
TransactionRollbackError.  These exceptions will now be raised
using the closest available subclass in ``sqlalchemy.exc``, in the
case of TransactionRollbackError, ``sqlalchemy.exc.OperationalError``.
fixes #3075

doc/build/changelog/changelog_09.rst
lib/sqlalchemy/exc.py
test/dialect/postgresql/test_dialect.py
test/engine/test_execute.py

index c63ed7fbb0c91ad3e2f9da5aca2dc2332d0ce448..afc51f240065406de194a09113ff9bed5a0d664e 100644 (file)
 .. changelog::
     :version: 0.9.8
 
+    .. change::
+        :tags: bug, postgresql
+        :versions: 1.0.0
+        :tickets: 3075
+
+        The exception wrapping system for DBAPI errors can now accommodate
+        non-standard DBAPI exceptions, such as the psycopg2
+        TransactionRollbackError.  These exceptions will now be raised
+        using the closest available subclass in ``sqlalchemy.exc``, in the
+        case of TransactionRollbackError, ``sqlalchemy.exc.OperationalError``.
+
     .. change::
         :tags: bug, sql
         :versions: 1.0.0
index 7d333fc01efd62e11f769199545aab9640ed92a8..a82bae33fdf06972ad71a2696b038c4f09bb8632 100644 (file)
@@ -294,9 +294,12 @@ class DBAPIError(StatementError):
                     statement, params, orig
                 )
 
-            name, glob = orig.__class__.__name__, globals()
-            if name in glob and issubclass(glob[name], DBAPIError):
-                cls = glob[name]
+            glob = globals()
+            for super_ in orig.__class__.__mro__:
+                name = super_.__name__
+                if name in glob and issubclass(glob[name], DBAPIError):
+                    cls = glob[name]
+                    break
 
         return cls(statement, params, orig, connection_invalidated)
 
index 11b277b66d08d2931ff04f14a03f1b11c1c7edc3..b751bbcdd7e63636abbe05c6c8c1c29bc9928551 100644 (file)
@@ -65,6 +65,16 @@ class MiscTest(fixtures.TestBase, AssertsExecutionResults, AssertsCompiledSQL):
         assert testing.db.dialect.dbapi.__version__.\
             startswith(".".join(str(x) for x in v))
 
+    @testing.only_on('postgresql+psycopg2', 'psycopg2-specific feature')
+    def test_psycopg2_non_standard_err(self):
+        from psycopg2.extensions import TransactionRollbackError
+        import psycopg2
+
+        exception = exc.DBAPIError.instance(
+            "some statement", {}, TransactionRollbackError("foo"),
+            psycopg2.Error)
+        assert isinstance(exception, exc.OperationalError)
+
     # currently not passing with pg 9.3 that does not seem to generate
     # any notices here, would rather find a way to mock this
     @testing.requires.no_coverage
index efce371a543a84f5607f6cabf23253411b81a770..36f3fbf72991c9902fa7b79ebca04f98eb6e71a1 100644 (file)
@@ -20,7 +20,7 @@ from sqlalchemy.engine import result as _result, default
 from sqlalchemy.engine.base import Engine
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing.mock import Mock, call, patch
-from contextlib import contextmanager
+from contextlib import contextmanager, nested
 
 users, metadata, users_autoinc = None, None, None
 class ExecuteTest(fixtures.TestBase):
@@ -236,6 +236,31 @@ class ExecuteTest(fixtures.TestBase):
             )
             eq_(is_disconnect.call_count, 0)
 
+    def test_exception_wrapping_non_standard_dbapi_error(self):
+        class DBAPIError(Exception):
+            pass
+
+        class OperationalError(DBAPIError):
+            pass
+
+        class NonStandardException(OperationalError):
+            pass
+
+        with nested(
+            patch.object(testing.db.dialect, "dbapi", Mock(Error=DBAPIError)),
+            patch.object(
+                testing.db.dialect, "is_disconnect",
+                lambda *arg: False),
+            patch.object(
+                testing.db.dialect, "do_execute",
+                Mock(side_effect=NonStandardException)),
+        ):
+            with testing.db.connect() as conn:
+                assert_raises(
+                    tsa.exc.OperationalError,
+                    conn.execute, "select 1"
+                )
+
 
     def test_exception_wrapping_non_dbapi_statement(self):
         class MyType(TypeDecorator):