From: Mike Bayer Date: Tue, 3 Oct 2023 17:42:35 +0000 (-0400) Subject: include WriteOnlyMapped, DynamicMapped in abs import lookup X-Git-Tag: rel_2_0_22~19 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0dc335a987fab540a079567953023d56c1178313;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git include WriteOnlyMapped, DynamicMapped in abs import lookup Fixed issue where :class:`.Mapped` symbols like :class:`.WriteOnlyMapped` and :class:`.DynamicMapped` could not be correctly resolved when referenced as an element of a sub-module in the given annotation, assuming string-based or "future annotations" style annotations. Fixes: #10412 Change-Id: I188146a6de7f6f80ec0ebf6e982b7842a78adc54 --- diff --git a/doc/build/changelog/unreleased_20/10412.rst b/doc/build/changelog/unreleased_20/10412.rst new file mode 100644 index 0000000000..d33b49b429 --- /dev/null +++ b/doc/build/changelog/unreleased_20/10412.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, orm + :tickets: 10412 + + Fixed issue where :class:`.Mapped` symbols like :class:`.WriteOnlyMapped` + and :class:`.DynamicMapped` could not be correctly resolved when referenced + as an element of a sub-module in the given annotation, assuming + string-based or "future annotations" style annotations. diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index e9a85b9ae6..573cc2c436 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -45,6 +45,7 @@ from .base import _never_set as _never_set # noqa: F401 from .base import _none_set as _none_set # noqa: F401 from .base import attribute_str as attribute_str # noqa: F401 from .base import class_mapper as class_mapper +from .base import DynamicMapped from .base import InspectionAttr as InspectionAttr from .base import instance_str as instance_str # noqa: F401 from .base import Mapped @@ -55,6 +56,7 @@ from .base import ORMDescriptor from .base import state_attribute_str as state_attribute_str # noqa: F401 from .base import state_class_str as state_class_str # noqa: F401 from .base import state_str as state_str # noqa: F401 +from .base import WriteOnlyMapped from .interfaces import CriteriaOption from .interfaces import MapperProperty as MapperProperty from .interfaces import ORMColumnsClauseRole @@ -138,7 +140,14 @@ all_cascades = frozenset( _de_stringify_partial = functools.partial( - functools.partial, locals_=util.immutabledict({"Mapped": Mapped}) + functools.partial, + locals_=util.immutabledict( + { + "Mapped": Mapped, + "WriteOnlyMapped": WriteOnlyMapped, + "DynamicMapped": DynamicMapped, + } + ), ) # partial is practically useless as we have to write out the whole diff --git a/test/orm/declarative/test_abs_import_only.py b/test/orm/declarative/test_abs_import_only.py index e700b4cc2d..e1447364e6 100644 --- a/test/orm/declarative/test_abs_import_only.py +++ b/test/orm/declarative/test_abs_import_only.py @@ -6,6 +6,8 @@ mappings while guaranteeing that the Mapped name is not locally present from __future__ import annotations +import typing + import sqlalchemy from sqlalchemy import orm import sqlalchemy.orm @@ -45,3 +47,42 @@ class MappedColumnTest( sqlalchemy.select(Foo), "SELECT foo.id, foo.data, foo.data2, foo.data3 FROM foo", ) + + @sqlalchemy.testing.variation( + "construct", ["Mapped", "WriteOnlyMapped", "DynamicMapped"] + ) + def test_fully_qualified_writeonly_mapped_name(self, decl_base, construct): + """futher variation in issue #10412""" + + class Foo(decl_base): + __tablename__ = "foo" + + id: sqlalchemy.orm.Mapped[int] = sqlalchemy.orm.mapped_column( + primary_key=True + ) + + if construct.Mapped: + bars: orm.Mapped[typing.List[Bar]] = orm.relationship() + elif construct.WriteOnlyMapped: + bars: orm.WriteOnlyMapped[ + typing.List[Bar] + ] = orm.relationship() + elif construct.DynamicMapped: + bars: orm.DynamicMapped[typing.List[Bar]] = orm.relationship() + else: + construct.fail() + + class Bar(decl_base): + __tablename__ = "bar" + + id: sqlalchemy.orm.Mapped[int] = sqlalchemy.orm.mapped_column( + primary_key=True + ) + foo_id: sqlalchemy.orm.Mapped[int] = sqlalchemy.orm.mapped_column( + sqlalchemy.ForeignKey("foo.id") + ) + + self.assert_compile( + sqlalchemy.select(Foo).join(Foo.bars), + "SELECT foo.id FROM foo JOIN bar ON foo.id = bar.foo_id", + )