]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
update oracledb async dialect for oracledb __aenter__()
authorAVRC26 <AVRC26@gmail.com>
Tue, 7 Jul 2026 19:01:23 +0000 (15:01 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 9 Jul 2026 01:13:39 +0000 (21:13 -0400)
Updated the oracledb async dialect where the async cursor adapter invoked
``__enter__()`` rather than ``__aenter__()`` on the underlying cursor.
While these are equivalent in oracledb itself, the correct async form is
now used for correctness. As ``AsyncCursor.__aenter__()`` was added in
oracledb 2.0.1, the minimum supported oracledb version is now 2.0.1,
declared via the ``oracle-oracledb`` extra.  Pull request courtesy AVRC26.

Fixes: #13420
Closes: #13421
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13421
Pull-request-sha: 20582e2eaf1cdf9cd8af94ecf216f5d5d9fb3d62

Change-Id: I04f0e6cf3f753a1bfc664c4f10c290093526b4cb

doc/build/changelog/unreleased_21/13420.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/oracle/oracledb.py
pyproject.toml
test/dialect/oracle/test_dialect.py

diff --git a/doc/build/changelog/unreleased_21/13420.rst b/doc/build/changelog/unreleased_21/13420.rst
new file mode 100644 (file)
index 0000000..01b75d6
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, oracle
+    :tickets: 13420
+
+    Updated the oracledb async dialect where the async cursor adapter invoked
+    ``__enter__()`` rather than ``__aenter__()`` on the underlying cursor.
+    While these are equivalent in oracledb itself, the correct async form is
+    now used for correctness. As ``AsyncCursor.__aenter__()`` was added in
+    oracledb 2.0.1, the minimum supported oracledb version is now 2.0.1,
+    declared via the ``oracle-oracledb`` extra.  Pull request courtesy AVRC26.
index e76ca13c69c859e7c156afb0f1fa4bcbe9c33f21..cf1597690688c5a4b923b6f590620fffdabd912a 100644 (file)
@@ -747,12 +747,6 @@ class AsyncAdapt_oracledb_cursor(AsyncAdapt_dbapi_cursor):
     def setinputsizes(self, *args: Any, **kwargs: Any) -> Any:
         return self._cursor.setinputsizes(*args, **kwargs)
 
-    def _aenter_cursor(self, cursor: AsyncCursor) -> AsyncCursor:
-        try:
-            return cursor.__enter__()
-        except Exception as error:
-            self._adapt_connection._handle_exception(error)
-
     async def _execute_async(self, operation, parameters):
         # override to not use mutex, oracledb already has a mutex
 
@@ -893,7 +887,7 @@ class OracleDialectAsync_oracledb(OracleDialect_oracledb):
     supports_statement_cache = True
     execution_ctx_cls = OracleExecutionContextAsync_oracledb
 
-    _min_version = (2,)
+    _min_version = (2, 0, 1)
 
     # thick_mode mode is not supported by asyncio, oracledb will raise
     @classmethod
index 08cfe57dfb7d3fdb74b259439b91f1cd6e860d80..1c5837b711d178d302ca7511149c9eeee52fd594 100644 (file)
@@ -55,9 +55,9 @@ mssql-python = ["mssql-python>=1.9.0"]
 mysql = ["mysqlclient>=1.4.0"]
 mysql-connector = ["mysql-connector-python"]
 mariadb-connector = ["mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10"]
-oracle = ["oracledb>=1.0.1"]
+oracle = ["oracledb>=2.0.1"]
 oracle-cxoracle = ["cx_oracle>=8"]
-oracle-oracledb = ["oracledb>=1.0.1"]
+oracle-oracledb = ["oracledb>=2.0.1"]
 postgresql = ["psycopg>=3.0.7,!=3.1.15"]
 postgresql-pg8000 = ["pg8000>=1.29.3"]
 postgresql-asyncpg = [
index 1cc41e453f104a7bf9f777d874f94fecbfc20a9c..6ea6fe0c3c0c3c33b82aaf971eef70a7ff5153dd 100644 (file)
@@ -29,6 +29,7 @@ from sqlalchemy.testing import assert_raises
 from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import AssertsCompiledSQL
 from sqlalchemy.testing import AssertsExecutionResults
+from sqlalchemy.testing import async_test
 from sqlalchemy.testing import config
 from sqlalchemy.testing import engines
 from sqlalchemy.testing import eq_
@@ -41,6 +42,7 @@ from sqlalchemy.testing.schema import Column
 from sqlalchemy.testing.schema import pep435_enum
 from sqlalchemy.testing.schema import Table
 from sqlalchemy.testing.suite import test_select
+from sqlalchemy.util import greenlet_spawn
 
 
 class CxOracleDialectTest(fixtures.TestBase):
@@ -94,6 +96,18 @@ class OracleDbDialectTest(fixtures.TestBase):
         dialect = oracledb.OracleDialect_oracledb(dbapi=Mock(version="7.1.0"))
         eq_(dialect.oracledb_ver, (7, 1, 0))
 
+    def test_async_minimum_version(self):
+        with expect_raises_message(
+            exc.InvalidRequestError,
+            r"oracledb version \(2, 0, 1\) and above are supported",
+        ):
+            oracledb.OracleDialectAsync_oracledb(dbapi=Mock(version="2.0.0"))
+
+        dialect = oracledb.OracleDialectAsync_oracledb(
+            dbapi=Mock(version="2.0.1")
+        )
+        eq_(dialect.oracledb_ver, (2, 0, 1))
+
     def test_get_dialect(self):
         u = url.URL.create("oracle://")
         d = oracledb.OracleDialect_oracledb.get_dialect_cls(u)
@@ -109,6 +123,34 @@ class OracleDbDialectTest(fixtures.TestBase):
         e = create_engine("oracle+oracledb_async://")
         is_true(isinstance(e.dialect, oracledb.OracleDialectAsync_oracledb))
 
+    @async_test
+    async def test_async_cursor_enters_asynchronously(self):
+        """test #13420"""
+
+        adapt_connection = Mock()
+
+        underlying_cursor = Mock()
+
+        async def aenter(*arg, **kw):
+            return underlying_cursor
+
+        underlying_cursor.__aenter__ = Mock(side_effect=aenter)
+        underlying_cursor.__enter__ = Mock(
+            side_effect=AssertionError("sync __enter__ should not be called")
+        )
+
+        adapt_connection._connection.cursor = Mock(
+            return_value=underlying_cursor
+        )
+
+        cursor = await greenlet_spawn(
+            oracledb.AsyncAdapt_oracledb_cursor, adapt_connection
+        )
+
+        is_(cursor._cursor, underlying_cursor)
+        underlying_cursor.__aenter__.assert_called_once()
+        underlying_cursor.__enter__.assert_not_called()
+
 
 class OracledbMode(fixtures.TestBase):
     __backend__ = True