]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
add extra pep695 conversion step
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 9 Mar 2024 17:47:01 +0000 (12:47 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 9 Mar 2024 22:10:42 +0000 (17:10 -0500)
Added support for the :pep:`695` ``TypeAliasType`` construct as well as the
python 3.12 native ``type`` keyword to work with ORM Annotated Declarative
form when using these constructs to link to a :pep:`593` ``Annotated``
container, allowing the resolution of the ``Annotated`` to proceed when
these constructs are used in a :class:`_orm.Mapped` typing container.

Fixes: #11130
Change-Id: I9a386943966de2107f15f08dfe6ed2aa84f7e86c
(cherry picked from commit 985193c407ffb891c8eed042fac6f9547a34d694)

doc/build/changelog/unreleased_20/11130.rst [new file with mode: 0644]
lib/sqlalchemy/orm/properties.py
test/orm/declarative/test_tm_future_annotations_sync.py
test/orm/declarative/test_typed_mapping.py

diff --git a/doc/build/changelog/unreleased_20/11130.rst b/doc/build/changelog/unreleased_20/11130.rst
new file mode 100644 (file)
index 0000000..80fbe08
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: usecase, orm
+    :tickets: 11130
+
+    Added support for the :pep:`695` ``TypeAliasType`` construct as well as the
+    python 3.12 native ``type`` keyword to work with ORM Annotated Declarative
+    form when using these constructs to link to a :pep:`593` ``Annotated``
+    container, allowing the resolution of the ``Annotated`` to proceed when
+    these constructs are used in a :class:`_orm.Mapped` typing container.
index 7a5eb8625b287d2e82214a5f68475a4cabd419c0..adee44a77e182e2dc9bfadb4d87b07e5e006fc5c 100644 (file)
@@ -58,6 +58,7 @@ from ..util.typing import de_optionalize_union_types
 from ..util.typing import is_fwd_ref
 from ..util.typing import is_optional_union
 from ..util.typing import is_pep593
+from ..util.typing import is_pep695
 from ..util.typing import is_union
 from ..util.typing import Self
 from ..util.typing import typing_get_args
@@ -760,6 +761,11 @@ class MappedColumn(
 
         use_args_from = None
 
+        our_original_type = our_type
+
+        if is_pep695(our_type):
+            our_type = our_type.__value__
+
         if is_pep593(our_type):
             our_type_is_pep593 = True
 
@@ -852,9 +858,9 @@ class MappedColumn(
             new_sqltype = None
 
             if our_type_is_pep593:
-                checks = [our_type, raw_pep_593_type]
+                checks = [our_original_type, raw_pep_593_type]
             else:
-                checks = [our_type]
+                checks = [our_original_type]
 
             for check_type in checks:
                 new_sqltype = registry._resolve_type(check_type)
index 6b118139178354ba7816997e6f044a6406281021..25e77811339bd293989e4d6f47574187a5a17225 100644 (file)
@@ -115,6 +115,13 @@ if compat.py312:
         """
 type _UnionPep695 = _SomeDict1 | _SomeDict2
 type _StrPep695 = str
+
+type strtypalias_keyword = Annotated[str, mapped_column(info={"hi": "there"})]
+
+strtypalias_tat: typing.TypeAliasType = Annotated[
+    str, mapped_column(info={"hi": "there"})]
+
+strtypalias_plain = Annotated[str, mapped_column(info={"hi": "there"})]
 """,
         globals(),
     )
@@ -833,6 +840,32 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         eq_(Test.__table__.c.data.type.length, 30)
         is_(Test.__table__.c.structure.type._type_affinity, JSON)
 
+    @testing.variation("alias_type", ["none", "typekeyword", "typealiastype"])
+    @testing.requires.python312
+    def test_extract_pep593_from_pep695(
+        self, decl_base: Type[DeclarativeBase], alias_type
+    ):
+        """test #11130"""
+
+        class MyClass(decl_base):
+            __tablename__ = "my_table"
+
+            id: Mapped[int] = mapped_column(primary_key=True)
+
+            if alias_type.typekeyword:
+                data_one: Mapped[strtypalias_keyword]  # noqa: F821
+            elif alias_type.typealiastype:
+                data_one: Mapped[strtypalias_tat]  # noqa: F821
+            elif alias_type.none:
+                data_one: Mapped[strtypalias_plain]  # noqa: F821
+            else:
+                alias_type.fail()
+
+        table = MyClass.__table__
+        assert table is not None
+
+        eq_(MyClass.data_one.expression.info, {"hi": "there"})
+
     @testing.requires.python310
     def test_we_got_all_attrs_test_annotated(self):
         argnames = _py_inspect.getfullargspec(mapped_column)
index 0b9f4c1acbdb085615487e73771c9a6d18ea24c5..4afa33c7316c7fd29369a2b57ce21e738a0be6ba 100644 (file)
@@ -106,6 +106,13 @@ if compat.py312:
         """
 type _UnionPep695 = _SomeDict1 | _SomeDict2
 type _StrPep695 = str
+
+type strtypalias_keyword = Annotated[str, mapped_column(info={"hi": "there"})]
+
+strtypalias_tat: typing.TypeAliasType = Annotated[
+    str, mapped_column(info={"hi": "there"})]
+
+strtypalias_plain = Annotated[str, mapped_column(info={"hi": "there"})]
 """,
         globals(),
     )
@@ -824,6 +831,32 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         eq_(Test.__table__.c.data.type.length, 30)
         is_(Test.__table__.c.structure.type._type_affinity, JSON)
 
+    @testing.variation("alias_type", ["none", "typekeyword", "typealiastype"])
+    @testing.requires.python312
+    def test_extract_pep593_from_pep695(
+        self, decl_base: Type[DeclarativeBase], alias_type
+    ):
+        """test #11130"""
+
+        class MyClass(decl_base):
+            __tablename__ = "my_table"
+
+            id: Mapped[int] = mapped_column(primary_key=True)
+
+            if alias_type.typekeyword:
+                data_one: Mapped[strtypalias_keyword]  # noqa: F821
+            elif alias_type.typealiastype:
+                data_one: Mapped[strtypalias_tat]  # noqa: F821
+            elif alias_type.none:
+                data_one: Mapped[strtypalias_plain]  # noqa: F821
+            else:
+                alias_type.fail()
+
+        table = MyClass.__table__
+        assert table is not None
+
+        eq_(MyClass.data_one.expression.info, {"hi": "there"})
+
     @testing.requires.python310
     def test_we_got_all_attrs_test_annotated(self):
         argnames = _py_inspect.getfullargspec(mapped_column)