]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
add 3.12
authorFederico Caselli <cfederico87@gmail.com>
Mon, 22 May 2023 19:54:47 +0000 (21:54 +0200)
committerFederico Caselli <cfederico87@gmail.com>
Mon, 5 Jun 2023 18:49:38 +0000 (20:49 +0200)
Initial fixes to test to accommodate py312

Here we are pulling in a current fork of greenlet that works
for python 3.12.   It works though there is one issue with aiosqlite
that might be related.

a non-voting py312 job is added to gerrit / jenkins

Fixes: #9819
Change-Id: I91a51dcbad2902f7c4c7cec88ebbf42c2417b512

12 files changed:
.github/workflows/run-test.yaml
doc/build/changelog/unreleased_20/py312.rst [new file with mode: 0644]
examples/versioned_history/history_meta.py
lib/sqlalchemy/ext/asyncio/base.py
lib/sqlalchemy/sql/sqltypes.py
lib/sqlalchemy/util/__init__.py
lib/sqlalchemy/util/compat.py
pyproject.toml
test/base/test_result.py
test/engine/test_pool.py
test/engine/test_reconnect.py
tox.ini

index 938c7c55dde06459af35f52349938914db4beab9..82818dc88b43cef78fd9d235c6d1b63abe1d6144 100644 (file)
@@ -51,12 +51,6 @@ jobs:
             pytest-args: "-k 'not test_autocommit_on and not test_turn_autocommit_off_via_default_iso_level and not test_autocommit_isolation_level'"
           - os: "ubuntu-latest"
             pytest-args: "--dbdriver pysqlite --dbdriver aiosqlite"
-          # do not run aiosqlite on python 3.11
-          # wait for https://github.com/python/cpython/issues/99205 to be fixed
-          - os: "ubuntu-latest"
-            python-version: "3.11"
-            pytest-args: "--dbdriver pysqlite"
-
 
         exclude:
           # linux and osx do not have x86 python
diff --git a/doc/build/changelog/unreleased_20/py312.rst b/doc/build/changelog/unreleased_20/py312.rst
new file mode 100644 (file)
index 0000000..330cebb
--- /dev/null
@@ -0,0 +1,4 @@
+.. change::
+    :tags: installation
+
+    Compatibility improvements to work fully with Python 3.12
index c70a5bfa1c33d9eaf030ce21e0f7282d4659ad15..806267cb41424a272f5970fccf4c713f4b633ee7 100644 (file)
@@ -113,7 +113,7 @@ def _history_mapper(local_mapper):
             Column(
                 "changed",
                 DateTime,
-                default=datetime.datetime.utcnow,
+                default=lambda: datetime.datetime.now(datetime.timezone.utc),
                 info=version_meta,
             )
         )
index 368c9b0553bc1edc04d2d2fb1c548744251d29d3..1fecf60abd7701bad4115ba9e8a2dc720d07224e 100644 (file)
@@ -184,7 +184,7 @@ class GeneratorStartableContext(StartableContext[_T_co]):
                 # tell if we get the same exception back
                 value = typ()
             try:
-                await self.gen.athrow(typ, value, traceback)
+                await util.athrow(self.gen, typ, value, traceback)
             except StopAsyncIteration as exc:
                 # Suppress StopIteration *unless* it's the same exception that
                 # was passed to throw().  This prevents a StopIteration
index f4ce48fcb264040158049a1801e312956d5f56aa..5fdca7d0a7bf0d63e4f1c970af02008af2afbd9a 100644 (file)
@@ -2049,7 +2049,7 @@ class Interval(Emulated, _AbstractInterval, TypeDecorator[dt.timedelta]):
     """
 
     impl = DateTime
-    epoch = dt.datetime.utcfromtimestamp(0)
+    epoch = dt.datetime.fromtimestamp(0, dt.timezone.utc).replace(tzinfo=None)
     cache_ok = True
 
     def __init__(
index 6f409c9e29312becdfe9b19eaddf3dae74a9b2ce..c804f96887805eed518c99aa1a280bd6038305d1 100644 (file)
@@ -49,6 +49,7 @@ from ._collections import WeakPopulateDict as WeakPopulateDict
 from ._collections import WeakSequence as WeakSequence
 from .compat import anext_ as anext_
 from .compat import arm as arm
+from .compat import athrow as athrow
 from .compat import b as b
 from .compat import b64decode as b64decode
 from .compat import b64encode as b64encode
index ab346083e569bb2126372cc29c67e17c18b3aede..da653b84d2c2e02452ff5b000b2ab1a510dcbd2b 100644 (file)
@@ -18,6 +18,8 @@ import platform
 import sys
 import typing
 from typing import Any
+from typing import AsyncGenerator
+from typing import Awaitable
 from typing import Callable
 from typing import Dict
 from typing import Iterable
@@ -28,6 +30,7 @@ from typing import Sequence
 from typing import Set
 from typing import Tuple
 from typing import Type
+from typing import TypeVar
 
 
 py312 = sys.version_info >= (3, 12)
@@ -47,6 +50,8 @@ has_refcount_gc = bool(cpython)
 
 dottedgetter = operator.attrgetter
 
+_T_co = TypeVar("_T_co", covariant=True)
+
 
 class FullArgSpec(typing.NamedTuple):
     args: List[str]
@@ -96,6 +101,24 @@ def inspect_getfullargspec(func: Callable[..., Any]) -> FullArgSpec:
     )
 
 
+if py312:
+    # we are 95% certain this form of athrow works in former Python
+    # versions, however we are unable to get confirmation;
+    # see https://github.com/python/cpython/issues/105269 where have
+    # been unable to get a straight answer so far
+    def athrow(  # noqa
+        gen: AsyncGenerator[_T_co, Any], typ: Any, value: Any, traceback: Any
+    ) -> Awaitable[_T_co]:
+        return gen.athrow(value)
+
+else:
+
+    def athrow(  # noqa
+        gen: AsyncGenerator[_T_co, Any], typ: Any, value: Any, traceback: Any
+    ) -> Awaitable[_T_co]:
+        return gen.athrow(typ, value, traceback)
+
+
 if typing.TYPE_CHECKING or py38:
     from importlib import metadata as importlib_metadata
 else:
index ae4c1fb136be5767240c0a23964bf34b40e421cc..a0c198c05d9aebf47d68c4541dfdc5da45369b06 100644 (file)
@@ -34,7 +34,11 @@ filterwarnings = [
     # here as pytest loads them immediately, which breaks coverage as well
     # as sys.path adjustments in conftest.py
     "error::DeprecationWarning:test",
-    "error::DeprecationWarning:sqlalchemy"
+    "error::DeprecationWarning:sqlalchemy",
+
+    # sqlite3 warnings due to test/dialect/test_sqlite.py->test_native_datetime,
+    # which is asserting that these deprecated-in-py312 handlers are functional
+    "ignore:The default (date)?(time)?(stamp)? (adapter|converter):DeprecationWarning",
 ]
 markers = [
     "memory_intensive: memory / CPU intensive suite tests",
index b3f519bbefcf2390abcfeaee0cadaeb620c849d4..78117e32280347973211aad233386e9b72325c12 100644 (file)
@@ -7,7 +7,9 @@ from sqlalchemy.testing import eq_
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import is_false
 from sqlalchemy.testing import is_true
+from sqlalchemy.testing.assertions import expect_raises
 from sqlalchemy.testing.util import picklers
+from sqlalchemy.util import compat
 
 
 class ResultTupleTest(fixtures.TestBase):
@@ -64,7 +66,12 @@ class ResultTupleTest(fixtures.TestBase):
     def test_slices_arent_in_mappings(self):
         keyed_tuple = self._fixture([1, 2], ["a", "b"])
 
-        assert_raises(TypeError, lambda: keyed_tuple._mapping[0:2])
+        if compat.py312:
+            with expect_raises(KeyError):
+                keyed_tuple._mapping[0:2]
+        else:
+            with expect_raises(TypeError):
+                keyed_tuple._mapping[0:2]
 
     def test_integers_arent_in_mappings(self):
         keyed_tuple = self._fixture([1, 2], ["a", "b"])
index cca6e258928725b79b5ab3dfdf66a47867971e26..e2e484174085cf24dfecbd4d57ae422aed641b6e 100644 (file)
@@ -736,7 +736,7 @@ class PoolEventsTest(PoolTestBase):
         assert canary.call_args_list[0][0][0] is dbapi_con
         assert canary.call_args_list[0][0][2] is exc
 
-    @testing.variation("is_asyncio", [True, False])
+    @testing.variation("is_asyncio", [(True, testing.requires.asyncio), False])
     @testing.variation("has_terminate", [True, False])
     def test_checkin_event_gc(self, is_asyncio, has_terminate):
         """tests for #8419, which have been modified for 2.0 in #9237"""
@@ -1704,7 +1704,10 @@ class QueuePoolTest(PoolTestBase):
             exc_cls=TimeoutThing if exc_type.base_exception else Exception,
         )
 
-    @testing.combinations((True,), (False,))
+    @testing.variation(
+        "detach_gced",
+        [("detached_gc", testing.requires.asyncio), "normal_gc"],
+    )
     @testing.emits_warning("The garbage collector")
     def test_userspace_disconnectionerror_weakref_finalizer(self, detach_gced):
         dbapi, pool = self._queuepool_dbapi_fixture(
index 48c5af8df7e51fd755e89b3a09c792d45f12738d..00984b5a4d7044dcc2a58d8e62623841431d887a 100644 (file)
@@ -1510,27 +1510,25 @@ class InvalidateDuringResultTest(fixtures.TestBase):
             self.meta.drop_all(conn)
         self.engine.dispose()
 
-    @testing.fails_if(
-        [
-            "+mysqlconnector",
-            "+mysqldb",
-            "+cymysql",
-            "+pymysql",
-            "+pg8000",
-            "+asyncpg",
-            "+aiosqlite",
-            "+aiomysql",
-            "+asyncmy",
-            "+psycopg",
-        ],
-        "Buffers the result set and doesn't check for connection close",
-    )
     def test_invalidate_on_results(self):
         conn = self.engine.connect()
-        result = conn.exec_driver_sql("select * from sometable")
+        result = conn.exec_driver_sql(
+            "select * from sometable",
+        )
         for x in range(20):
             result.fetchone()
+
+        real_cursor = result.cursor
         self.engine.test_shutdown()
+
+        def produce_side_effect():
+            # will fail because connection was closed, with an exception
+            # that should trigger disconnect routines
+            real_cursor.execute("select * from sometable")
+
+        result.cursor = Mock(
+            fetchone=mock.Mock(side_effect=produce_side_effect)
+        )
         try:
             _assert_invalidated(result.fetchone)
             assert conn.invalidated
diff --git a/tox.ini b/tox.ini
index d2d77353abe1b24738166e94d3a44388e2226668..31fc62aa1ca383b79fe9e674047498aefdc9fdd1 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -18,7 +18,7 @@ extras=
      sqlite_file: aiosqlite
      sqlite_file: sqlcipher; python_version < '3.10'
      postgresql: postgresql
-     postgresql: postgresql_asyncpg
+     py3{,7,8,9,10,11}-postgresql: postgresql_asyncpg
      postgresql: postgresql_pg8000
      postgresql: postgresql_psycopg
 
@@ -26,18 +26,19 @@ extras=
      mysql: pymysql
      mysql: asyncmy
 
-#     mysql: mariadb_connector
-
      oracle: oracle
      oracle: oracle_oracledb
-     mssql: mssql
-     mssql: mssql_pymssql
+     py3{,7,8,9,10,11}-mssql: mssql
+     py3{,7,8,9,10,11}-mssql: mssql_pymssql
+
 
 deps=
      pytest>=7.0.0rc1,<8
      # tracked by https://github.com/pytest-dev/pytest-xdist/issues/907
      pytest-xdist!=3.3.0
 
+     py312: git+https://github.com/mdboom/greenlet.git@python-312#egg=greenlet
+
      dbapimain-sqlite: git+https://github.com/omnilib/aiosqlite.git#egg=aiosqlite
      dbapimain-sqlite: git+https://github.com/coleifer/sqlcipher3.git#egg=sqlcipher3
 
@@ -82,10 +83,16 @@ setenv=
     PYTEST_COLOR={tty:--color=yes}
     MYPY_COLOR={tty:--color-output}
 
-    BASECOMMAND=python -m pytest {env:PYTEST_COLOR} --rootdir {toxinidir} --log-info=sqlalchemy.testing
+    # pytest 'rewrite' is hitting lots of deprecation warnings under py312 and
+    # i can't find any way to ignore those warnings, so this turns it off
+    py312: PYTEST_ARGS=--assert plain
+
+    BASECOMMAND=python -m pytest {env:PYTEST_ARGS} {env:PYTEST_COLOR} --rootdir {toxinidir} --log-info=sqlalchemy.testing
 
     WORKERS={env:TOX_WORKERS:-n4  --max-worker-restart=5}
 
+
+
     nocext: DISABLE_SQLALCHEMY_CEXT=1
     cext: REQUIRE_SQLALCHEMY_CEXT=1
     cov: COVERAGE={[testenv]cov_args}
@@ -107,7 +114,9 @@ setenv=
     py3{,10,11}-sqlite_file: EXTRA_SQLITE_DRIVERS={env:EXTRA_SQLITE_DRIVERS:--dbdriver sqlite --dbdriver aiosqlite}
 
     postgresql: POSTGRESQL={env:TOX_POSTGRESQL:--db postgresql}
+
     py3{,7,8,9,10,11}-postgresql: EXTRA_PG_DRIVERS={env:EXTRA_PG_DRIVERS:--dbdriver psycopg2 --dbdriver asyncpg --dbdriver pg8000 --dbdriver psycopg --dbdriver psycopg_async}
+    py312-postgresql: EXTRA_PG_DRIVERS={env:EXTRA_PG_DRIVERS:--dbdriver psycopg2 --dbdriver pg8000 --dbdriver psycopg --dbdriver psycopg_async}
 
     mysql: MYSQL={env:TOX_MYSQL:--db mysql}
     py2{,7}-mysql: MYSQL={env:TOX_MYSQL_PY2K:{env:TOX_MYSQL:--db mysql}}