From: Mike Bayer Date: Thu, 16 Jan 2025 17:14:02 +0000 (-0500) Subject: set default iso to None for asyncpg pep-249 wrapper X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=299cdf667d5af96c5db75a923d2fd15eef2dfe26;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git set default iso to None for asyncpg pep-249 wrapper 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 --- diff --git a/doc/build/changelog/unreleased_20/12159.rst b/doc/build/changelog/unreleased_20/12159.rst new file mode 100644 index 0000000000..3babbf9db7 --- /dev/null +++ b/doc/build/changelog/unreleased_20/12159.rst @@ -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``. diff --git a/lib/sqlalchemy/dialects/postgresql/asyncpg.py b/lib/sqlalchemy/dialects/postgresql/asyncpg.py index 3a1c7b3f71..65824433c3 100644 --- a/lib/sqlalchemy/dialects/postgresql/asyncpg.py +++ b/lib/sqlalchemy/dialects/postgresql/asyncpg.py @@ -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 diff --git a/test/dialect/postgresql/test_async_pg_py3k.py b/test/dialect/postgresql/test_async_pg_py3k.py index feff60c578..0f25097ffb 100644 --- a/test/dialect/postgresql/test_async_pg_py3k.py +++ b/test/dialect/postgresql/test_async_pg_py3k.py @@ -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(