]> 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)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 3 Jul 2023 04:18:21 +0000 (00:18 -0400)
Initial fixes to test to accommodate py312

this is a backport of 59521abcc0676e936b31a523bd968fc157fef0c2
however includes greenlet>=3.0.0a1 that now builds and/or installs
on Python 3.12.

result row handling is also different in 1.4.

Fixes: #9819
Change-Id: I91a51dcbad2902f7c4c7cec88ebbf42c2417b512
(cherry picked from commit 59521abcc0676e936b31a523bd968fc157fef0c2)

doc/build/changelog/unreleased_20/py312.rst [new file with mode: 0644]
examples/versioned_history/history_meta.py
lib/sqlalchemy/cextension/resultproxy.c
lib/sqlalchemy/engine/row.py
lib/sqlalchemy/sql/sqltypes.py
lib/sqlalchemy/testing/warnings.py
test/base/test_result.py
test/engine/test_pool.py
test/engine/test_reconnect.py
tox.ini

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 7d13f2d7456a7851a319d278ea0a520307d9ae3e..1f83cf6d4fa37cc2d89ec8020b0f8de5eb6b7862 100644 (file)
@@ -116,7 +116,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 9d1f0ead48022c775a0bad312782dc20babac468..20f1536529f6b15ad3ee9eccb65248d9c71aa292 100644 (file)
@@ -21,6 +21,12 @@ typedef Py_ssize_t (*lenfunc)(PyObject *);
 typedef intargfunc ssizeargfunc;
 #endif
 
+#if PY_VERSION_HEX > 0x030c0000
+#    define PY_RAISE_SLICE_FOR_MAPPING PyExc_KeyError
+#else
+#    define PY_RAISE_SLICE_FOR_MAPPING PyExc_TypeError
+#endif
+
 #if PY_MAJOR_VERSION < 3
 
 // new typedef in Python 3
@@ -369,7 +375,7 @@ BaseRow_getitem_by_object(BaseRow *self, PyObject *key, int asmapping)
 
     if (record == NULL) {
         if (PySlice_Check(key)) {
-            PyErr_Format(PyExc_TypeError, "can't use slices for mapping access");
+            PyErr_Format(PY_RAISE_SLICE_FOR_MAPPING, "can't use slices for mapping access");
             return NULL;
         }
         record = PyObject_CallMethod(self->parent, "_key_fallback",
index f7c00bab37f210f1b01e99786cff80d8710fbc49..50577ffe8d7e02a67217f438f9b2290d4f2ef358 100644 (file)
@@ -130,7 +130,10 @@ except ImportError:
             try:
                 rec = self._keymap[key]
             except KeyError as ke:
-                rec = self._parent._key_fallback(key, ke)
+                if isinstance(key, slice):
+                    return tuple(self._data[key])
+                else:
+                    rec = self._parent._key_fallback(key, ke)
             except TypeError:
                 if isinstance(key, slice):
                     return tuple(self._data[key])
index 84239c70869aa61845c1509071553d2470643d04..eed63e5070df3d6ac70aee1b6558c8e502b8290b 100644 (file)
@@ -2058,7 +2058,12 @@ class Interval(Emulated, _AbstractInterval, TypeDecorator):
     """
 
     impl = DateTime
-    epoch = dt.datetime.utcfromtimestamp(0)
+    if compat.py2k:
+        epoch = dt.datetime.utcfromtimestamp(0)
+    else:
+        epoch = dt.datetime.fromtimestamp(0, dt.timezone.utc).replace(
+            tzinfo=None
+        )
     cache_ok = True
 
     def __init__(self, native=True, second_precision=None, day_precision=None):
index 762b0703919f3d50e73f847d6c477ed9b853d47f..7969a73ff7b82c59e61b1d36962f53dcf5f1a714 100644 (file)
@@ -46,6 +46,12 @@ def setup_filters():
     warnings.filterwarnings(
         "error", category=DeprecationWarning, module=origin
     )
+    warnings.filterwarnings(
+        "ignore",
+        category=DeprecationWarning,
+        message=r".*The default (?:date)?(?:time)?(?:stamp)? "
+        r"(adapter|converter) is deprecated",
+    )
 
     # ignore things that are deprecated *as of* 2.0 :)
     warnings.filterwarnings(
index c7b18fed3845daabd736603fa44a9a27734e37ce..86874b41df0fa9578551f01f7a84551a49b16070 100644 (file)
@@ -9,7 +9,9 @@ from sqlalchemy.testing import expect_deprecated
 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):
@@ -66,7 +68,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 2e11002efcccf5c1cf142e4cac77f50c3b8f87a1..fea65fe4c4a97a8646dd5d4f2c7cc98330c8266d 100644 (file)
@@ -750,8 +750,8 @@ 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.combinations((True,), (False,), argnames="is_asyncio")
-    @testing.combinations((True,), (False,), argnames="has_terminate")
+    @testing.variation("is_asyncio", [(True, testing.requires.asyncio), False])
+    @testing.variation("has_terminate", [True, False])
     @testing.requires.python3
     def test_checkin_event_gc(self, is_asyncio, has_terminate):
         p, canary = self._checkin_event_fixture(
@@ -1684,7 +1684,12 @@ class QueuePoolTest(PoolTestBase):
             exc_cls=TimeoutThing if exc_type.base_exception else Exception,
         )
 
-    @testing.combinations((True, testing.requires.python3), (False,))
+    @testing.variation(
+        "detach_gced",
+        [("detached_gc", testing.requires.asyncio), "normal_gc"],
+    )
+    @testing.emits_warning("The garbage collector")
+    @testing.requires.python3
     def test_userspace_disconnectionerror_weakref_finalizer(self, detach_gced):
         dbapi, pool = self._queuepool_dbapi_fixture(
             pool_size=1, max_overflow=2, _is_asyncio=detach_gced
index bd597a96b799d4441aa1240ce0c7ebe039ce9c22..2079fbe7df932e3b4206761a83244b9e1eeffd6d 100644 (file)
@@ -1353,6 +1353,9 @@ class PrePingRealTest(fixtures.TestBase):
 class InvalidateDuringResultTest(fixtures.TestBase):
     __backend__ = True
 
+    # test locks SQLite file databases due to unconsumed results
+    __requires__ = ("ad_hoc_engines",)
+
     def setup_test(self):
         self.engine = engines.reconnecting_engine()
         self.meta = MetaData()
@@ -1375,30 +1378,25 @@ class InvalidateDuringResultTest(fixtures.TestBase):
             self.meta.drop_all(conn)
         self.engine.dispose()
 
-    @testing.crashes(
-        "oracle",
-        "cx_oracle 6 doesn't allow a close like this due to open cursors",
-    )
-    @testing.fails_if(
-        [
-            "+mysqlconnector",
-            "+mysqldb",
-            "+cymysql",
-            "+pymysql",
-            "+pg8000",
-            "+asyncpg",
-            "+aiosqlite",
-            "+aiomysql",
-            "+asyncmy",
-        ],
-        "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 1c95f068e460b1158949eb492208bddd25a50de2..092104a44858671a8c6780c1ce6bdaf89442049f 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -28,7 +28,7 @@ deps=
      sqlite_file: .[aiosqlite]
      sqlite_file: .[sqlcipher]; python_version >= '3' and python_version < '3.10'
      postgresql: .[postgresql]
-     postgresql: .[postgresql_asyncpg]; python_version >= '3'
+     py3{,7,8,9,10,11}-postgresql: .[postgresql_asyncpg]; python_version >= '3'
      postgresql: .[postgresql_pg8000]; python_version >= '3'
 
      mysql: .[mysql]
@@ -40,6 +40,8 @@ deps=
 
      mssql: .[mssql]
 
+     py312: greenlet>=3.0.0a1
+
      dbapimain-sqlite: git+https://github.com/omnilib/aiosqlite.git#egg=aiosqlite
      dbapimain-sqlite: git+https://github.com/coleifer/sqlcipher3.git#egg=sqlcipher3
 
@@ -79,11 +81,17 @@ setenv=
 
     PYTEST_COLOR={tty:--color=yes}
 
+    # 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
+
     MEMUSAGE=--nomemory
     BASECOMMAND=python -m pytest {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}
@@ -103,6 +111,7 @@ setenv=
     postgresql: POSTGRESQL={env:TOX_POSTGRESQL:--db postgresql}
     py2{,7}-postgresql: POSTGRESQL={env:TOX_POSTGRESQL_PY2K:{env:TOX_POSTGRESQL:--db postgresql}}
     py3{,5,6,7,8,9,10,11}-postgresql: EXTRA_PG_DRIVERS={env:EXTRA_PG_DRIVERS:--dbdriver psycopg2 --dbdriver asyncpg --dbdriver pg8000}
+    py312-postgresql: EXTRA_PG_DRIVERS={env:EXTRA_PG_DRIVERS:--dbdriver psycopg2 --dbdriver pg8000}
 
     mysql: MYSQL={env:TOX_MYSQL:--db mysql}
     py2{,7}-mysql: MYSQL={env:TOX_MYSQL_PY2K:{env:TOX_MYSQL:--db mysql}}