From: Mike Bayer Date: Mon, 3 Aug 2026 17:58:31 +0000 (-0400) Subject: add Python 3.15 support X-Git-Tag: rel_2_0_52~9^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=985a3d9b91ea113645e5a43d8f9305dc06b6b28a;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git add Python 3.15 support Backport to 2.0 of the 3.15 support added for 2.1, as the issue is milestoned to 2.0.x. Add 3.15 to the noxfile Python matrix, and add both the 3.14 and the 3.15 trove classifiers; 3.14 has been in the noxfile matrix here for some time but was never added to the classifier list. Repair the auto-generated docstring for MappedAsDataclass subclasses under 3.15. Python 3.15 changed dataclasses so that the class docstring is no longer rendered when the dataclass is created, and is instead produced by a descriptor the first time __doc__ is read. The dataclass setup swaps a constructed __annotations__ onto the class for the duration of the dataclass call and restores the original in a finally block, so by the time that deferred render runs the annotations are gone and the docstring omits them entirely. Read __doc__ while the swapped annotations are still in place, so that the same docstring is produced on every supported Python. Also port the docstring assertion from the 2.1 test suite; there was no equivalent coverage here, so this defect would otherwise go unnoticed on this branch. As Python 3.8 and earlier still define typing.Generic.__new__, and declarative classes are Generic subclasses, the inspect.signature() call dataclasses uses to render this docstring reports that __new__ rather than the generated __init__ on those versions; assert the version-appropriate form in each case. py315: yes Fixes: #13477 Change-Id: I452a8bb1392a4417194594bd2e1e37b9e56fee26 --- diff --git a/doc/build/changelog/unreleased_20/13477.rst b/doc/build/changelog/unreleased_20/13477.rst new file mode 100644 index 0000000000..3fbcfc4483 --- /dev/null +++ b/doc/build/changelog/unreleased_20/13477.rst @@ -0,0 +1,6 @@ +.. change:: + :tags: bug, platform + :tickets: 13477 + + Python 3.15 support has been added and tested, including minimal changes + for full compatibility. diff --git a/lib/sqlalchemy/orm/decl_base.py b/lib/sqlalchemy/orm/decl_base.py index d4f35414cf..ced1c2f3de 100644 --- a/lib/sqlalchemy/orm/decl_base.py +++ b/lib/sqlalchemy/orm/decl_base.py @@ -1240,6 +1240,20 @@ class _ClassScanMapperConfig(_MapperConfig): "documentation for additional information.", code="dcte", ) from ex + else: + # as of Python 3.15, dataclasses no longer renders the + # auto-generated class docstring immediately; it instead installs + # a descriptor that renders the ``__init__`` signature the first + # time ``__doc__`` is accessed (see + # ``dataclasses._AutoDocstring``). As the ``finally:`` block + # below puts the class' original annotations back, that deferred + # render would no longer see the dataclass-oriented annotations + # applied above, and would omit them from the docstring entirely. + # Read the attribute now, while those annotations are still in + # place, so the docstring we generate is the same on every Python + # version. + klass.__doc__ + finally: # restore original annotations outside of the dataclasses # process; for mixins and __abstract__ superclasses, SQLAlchemy diff --git a/noxfile.py b/noxfile.py index 8dba5596a9..29d6295809 100644 --- a/noxfile.py +++ b/noxfile.py @@ -33,6 +33,7 @@ PYTHON_VERSIONS = [ "3.13", "3.14", "3.14t", + "3.15", ] DATABASES = ["sqlite", "sqlite_file", "postgresql", "mysql", "oracle", "mssql"] CEXT = ["_auto", "cext", "nocext"] diff --git a/setup.cfg b/setup.cfg index 8604d7e0df..61d82ea5ca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,8 @@ classifiers = Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 + Programming Language :: Python :: 3.14 + Programming Language :: Python :: 3.15 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Database :: Front-Ends diff --git a/test/orm/declarative/test_dc_transforms.py b/test/orm/declarative/test_dc_transforms.py index ed29992058..aaf50267c4 100644 --- a/test/orm/declarative/test_dc_transforms.py +++ b/test/orm/declarative/test_dc_transforms.py @@ -149,6 +149,33 @@ class DCTransformsTest(AssertsCompiledSQL, fixtures.TestBase): ), ) + # the docstring dataclasses generates for us should carry the + # annotation for every field. the exact rendering of each annotation + # varies by Python version and by whether or not future annotations + # mode is in use, so match only on their presence. as of Python 3.15 + # this docstring is rendered lazily upon first access rather than when + # the dataclass is created, which is the case #13477 addresses. + # + # this assertion is present in the 2.1 series in this same test, as + # DCTransformsTest.test_basic_constructor_repr_base_cls; it is + # written more specifically there, as that series supports Python + # 3.10 and above only and renders the annotations differently due to + # #12168 and #13021. it's included here in a version-agnostic form + # so that the fix for #13477 has coverage on this branch as well + if compat.py39: + eq_regex( + A.__doc__, r"A\(data: .+, x: .+ = None, bs: .+ = \)" + ) + eq_regex(B.__doc__, r"B\(data: .+, x: .+ = None\)") + else: + # on Python 3.8 and earlier, ``typing.Generic`` still defines + # ``__new__``; as declarative classes are ``Generic`` subclasses, + # the ``inspect.signature()`` call dataclasses uses to render + # this docstring reports that ``__new__`` rather than the + # generated ``__init__``, so there are no annotations to test + eq_(A.__doc__, "A(*args, **kwds)") + eq_(B.__doc__, "B(*args, **kwds)") + a2 = A("10", x=5, bs=[B("data1"), B("data2", x=12)]) eq_( repr(a2), diff --git a/test/orm/declarative/test_dc_transforms_future_anno_sync.py b/test/orm/declarative/test_dc_transforms_future_anno_sync.py index 52a66430cb..6de81650d1 100644 --- a/test/orm/declarative/test_dc_transforms_future_anno_sync.py +++ b/test/orm/declarative/test_dc_transforms_future_anno_sync.py @@ -158,6 +158,33 @@ class DCTransformsTest(AssertsCompiledSQL, fixtures.TestBase): ), ) + # the docstring dataclasses generates for us should carry the + # annotation for every field. the exact rendering of each annotation + # varies by Python version and by whether or not future annotations + # mode is in use, so match only on their presence. as of Python 3.15 + # this docstring is rendered lazily upon first access rather than when + # the dataclass is created, which is the case #13477 addresses. + # + # this assertion is present in the 2.1 series in this same test, as + # DCTransformsTest.test_basic_constructor_repr_base_cls; it is + # written more specifically there, as that series supports Python + # 3.10 and above only and renders the annotations differently due to + # #12168 and #13021. it's included here in a version-agnostic form + # so that the fix for #13477 has coverage on this branch as well + if compat.py39: + eq_regex( + A.__doc__, r"A\(data: .+, x: .+ = None, bs: .+ = \)" + ) + eq_regex(B.__doc__, r"B\(data: .+, x: .+ = None\)") + else: + # on Python 3.8 and earlier, ``typing.Generic`` still defines + # ``__new__``; as declarative classes are ``Generic`` subclasses, + # the ``inspect.signature()`` call dataclasses uses to render + # this docstring reports that ``__new__`` rather than the + # generated ``__init__``, so there are no annotations to test + eq_(A.__doc__, "A(*args, **kwds)") + eq_(B.__doc__, "B(*args, **kwds)") + a2 = A("10", x=5, bs=[B("data1"), B("data2", x=12)]) eq_( repr(a2),