]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
set default iso to None for asyncpg pep-249 wrapper
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 16 Jan 2025 17:14:02 +0000 (12:14 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 20 Jan 2025 18:12:19 +0000 (13:12 -0500)
Adjusted the asyncpg connection wrapper so that the asyncpg
``.transaction()`` call sends ``None`` for isolation_level if not otherwise
set in the SQLAlchemy dialect/wrapper, thereby allowing asyncpg to make use
of the server level setting for isolation_level in the absense of a
client-level setting. Previously, this behavior of asyncpg was blocked by a
hardcoded ``read_committed``.

Fixes: #12159
Change-Id: I2cd878a5059a8fefc9557a9b8e056fedaee2e9a4

doc/build/changelog/unreleased_20/12159.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/postgresql/asyncpg.py
test/dialect/postgresql/test_async_pg_py3k.py

diff --git a/doc/build/changelog/unreleased_20/12159.rst b/doc/build/changelog/unreleased_20/12159.rst
new file mode 100644 (file)
index 0000000..3babbf9
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, postgresql
+    :tickets: 12159
+
+    Adjusted the asyncpg connection wrapper so that the asyncpg
+    ``.transaction()`` call sends ``None`` for isolation_level if not otherwise
+    set in the SQLAlchemy dialect/wrapper, thereby allowing asyncpg to make use
+    of the server level setting for isolation_level in the absense of a
+    client-level setting. Previously, this behavior of asyncpg was blocked by a
+    hardcoded ``read_committed``.
index 3a1c7b3f71074f7efb908e0282ec7979cddb218e..65824433c3e143f5715b373538c07e6fa1021ddd 100644 (file)
@@ -748,7 +748,7 @@ class AsyncAdapt_asyncpg_connection(AsyncAdapt_dbapi_connection):
         prepared_statement_name_func=None,
     ):
         super().__init__(dbapi, connection)
-        self.isolation_level = self._isolation_setting = "read_committed"
+        self.isolation_level = self._isolation_setting = None
         self.readonly = False
         self.deferrable = False
         self._transaction = None
index feff60c5789142d973e5cdce55b59b0b7fa5f6b7..0f25097ffb0b28d1169bb50c5d4dd2f3b66db3af 100644 (file)
@@ -10,12 +10,14 @@ from sqlalchemy import select
 from sqlalchemy import String
 from sqlalchemy import Table
 from sqlalchemy import testing
+from sqlalchemy.dialects.postgresql import asyncpg as asyncpg_dialect
 from sqlalchemy.dialects.postgresql import ENUM
 from sqlalchemy.testing import async_test
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import expect_raises
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import mock
+from sqlalchemy.util import greenlet_spawn
 
 
 class AsyncPgTest(fixtures.TestBase):
@@ -166,6 +168,63 @@ class AsyncPgTest(fixtures.TestBase):
                 ],
             )
 
+    @testing.combinations(
+        None,
+        "read committed",
+        "repeatable read",
+        "serializable",
+        argnames="isolation_level",
+    )
+    @async_test
+    async def test_honor_server_level_iso_setting(
+        self, async_testing_engine, isolation_level
+    ):
+        """test for #12159"""
+
+        engine = async_testing_engine()
+
+        arg, kw = engine.dialect.create_connect_args(engine.url)
+
+        # 1. create an asyncpg.connection directly, set a session level
+        #    isolation level on it (this is similar to server default isolation
+        #    level)
+        raw_asyncpg_conn = await engine.dialect.dbapi.asyncpg.connect(
+            *arg, **kw
+        )
+
+        if isolation_level:
+            await raw_asyncpg_conn.execute(
+                f"set SESSION CHARACTERISTICS AS TRANSACTION "
+                f"isolation level {isolation_level}"
+            )
+
+        # 2. fetch it, confirm the setting took and matches
+        raw_iso_level = (
+            await raw_asyncpg_conn.fetchrow("show transaction isolation level")
+        )[0]
+        if isolation_level:
+            eq_(raw_iso_level, isolation_level.lower())
+
+        # 3.build our pep-249 wrapper around asyncpg.connection
+        dbapi_conn = asyncpg_dialect.AsyncAdapt_asyncpg_connection(
+            engine.dialect.dbapi,
+            raw_asyncpg_conn,
+        )
+
+        # 4. show the isolation level inside of a query.  this will
+        #    call asyncpg.connection.transaction() in order to run the
+        #    statement.
+        cursor = await greenlet_spawn(dbapi_conn.cursor)
+        await greenlet_spawn(
+            cursor.execute, "show transaction isolation level"
+        )
+        row = cursor.fetchone()
+
+        # 5. see that the raw iso level is maintained
+        eq_(row[0], raw_iso_level)
+
+        await greenlet_spawn(dbapi_conn.close)
+
     @testing.variation("trans", ["commit", "rollback"])
     @async_test
     async def test_dont_reset_open_transaction(