From: Mike Bayer Date: Sat, 9 Mar 2024 17:47:01 +0000 (-0500) Subject: add extra pep695 conversion step X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=985193c407ffb891c8eed042fac6f9547a34d694;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git add extra pep695 conversion step 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 --- diff --git a/doc/build/changelog/unreleased_20/11130.rst b/doc/build/changelog/unreleased_20/11130.rst new file mode 100644 index 0000000000..80fbe08dd2 --- /dev/null +++ b/doc/build/changelog/unreleased_20/11130.rst @@ -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. diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 7a5eb8625b..adee44a77e 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -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) diff --git a/test/orm/declarative/test_tm_future_annotations_sync.py b/test/orm/declarative/test_tm_future_annotations_sync.py index d8c170f912..4ab2657529 100644 --- a/test/orm/declarative/test_tm_future_annotations_sync.py +++ b/test/orm/declarative/test_tm_future_annotations_sync.py @@ -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) diff --git a/test/orm/declarative/test_typed_mapping.py b/test/orm/declarative/test_typed_mapping.py index ef69f9dd4f..819b671a5a 100644 --- a/test/orm/declarative/test_typed_mapping.py +++ b/test/orm/declarative/test_typed_mapping.py @@ -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)