From: Federico Caselli Date: Mon, 22 May 2023 19:54:47 +0000 (+0200) Subject: add 3.12 X-Git-Tag: rel_1_4_49~5 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cd56e873e1db4e6c8bee9e035627beba80251bea;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git add 3.12 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) --- diff --git a/doc/build/changelog/unreleased_20/py312.rst b/doc/build/changelog/unreleased_20/py312.rst new file mode 100644 index 0000000000..330cebb643 --- /dev/null +++ b/doc/build/changelog/unreleased_20/py312.rst @@ -0,0 +1,4 @@ +.. change:: + :tags: installation + + Compatibility improvements to work fully with Python 3.12 diff --git a/examples/versioned_history/history_meta.py b/examples/versioned_history/history_meta.py index 7d13f2d745..1f83cf6d4f 100644 --- a/examples/versioned_history/history_meta.py +++ b/examples/versioned_history/history_meta.py @@ -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, ) ) diff --git a/lib/sqlalchemy/cextension/resultproxy.c b/lib/sqlalchemy/cextension/resultproxy.c index 9d1f0ead48..20f1536529 100644 --- a/lib/sqlalchemy/cextension/resultproxy.c +++ b/lib/sqlalchemy/cextension/resultproxy.c @@ -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", diff --git a/lib/sqlalchemy/engine/row.py b/lib/sqlalchemy/engine/row.py index f7c00bab37..50577ffe8d 100644 --- a/lib/sqlalchemy/engine/row.py +++ b/lib/sqlalchemy/engine/row.py @@ -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]) diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index 84239c7086..eed63e5070 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -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): diff --git a/lib/sqlalchemy/testing/warnings.py b/lib/sqlalchemy/testing/warnings.py index 762b070391..7969a73ff7 100644 --- a/lib/sqlalchemy/testing/warnings.py +++ b/lib/sqlalchemy/testing/warnings.py @@ -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( diff --git a/test/base/test_result.py b/test/base/test_result.py index c7b18fed38..86874b41df 100644 --- a/test/base/test_result.py +++ b/test/base/test_result.py @@ -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"]) diff --git a/test/engine/test_pool.py b/test/engine/test_pool.py index 2e11002efc..fea65fe4c4 100644 --- a/test/engine/test_pool.py +++ b/test/engine/test_pool.py @@ -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 diff --git a/test/engine/test_reconnect.py b/test/engine/test_reconnect.py index bd597a96b7..2079fbe7df 100644 --- a/test/engine/test_reconnect.py +++ b/test/engine/test_reconnect.py @@ -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 1c95f068e4..092104a448 100644 --- 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}}