From: Mike Bayer Date: Wed, 20 May 2026 19:59:10 +0000 (-0400) Subject: implement _post_inspect for AliasedInsp X-Git-Tag: rel_2_0_50~3 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=309a6015a226974dad4d9fb14b6576a63291802e;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git implement _post_inspect for AliasedInsp Fixed issue where using :func:`_orm.with_polymorphic` on a leaf class (a subclass with no further descendants) or a non-inherited class would fail with an ``AttributeError`` when used in an ORM statement, due to :func:`_orm.configure_mappers` not being triggered implicitly. The fix ensures that :class:`.AliasedInsp` participates in the ``_post_inspect`` hook, triggering mapper configuration during ORM statement compilation. Fixes: #13319 Change-Id: Ic5910474676be41f8c815dc72c38fca8e20cdeb9 (cherry picked from commit afa94c32a91711b81e4e663cc328837354491315) --- diff --git a/doc/build/changelog/unreleased_20/13319.rst b/doc/build/changelog/unreleased_20/13319.rst new file mode 100644 index 0000000000..76b8b4c4e0 --- /dev/null +++ b/doc/build/changelog/unreleased_20/13319.rst @@ -0,0 +1,11 @@ +.. change:: + :tags: bug, orm + :tickets: 13319 + + Fixed issue where using :func:`_orm.with_polymorphic` on a leaf class (a + subclass with no further descendants) or a non-inherited class would fail + with an ``AttributeError`` when used in an ORM statement, due to + :func:`_orm.configure_mappers` not being triggered implicitly. The fix + ensures that :class:`.AliasedInsp` participates in the ``_post_inspect`` + hook, triggering mapper configuration during ORM statement compilation. + diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 090a274129..d665b1848e 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -1007,6 +1007,10 @@ class AliasedInsp( self._adapt_on_names = adapt_on_names self._target = mapped_class_or_ac + @property + def _post_inspect(self): # type: ignore[override] + self.mapper._check_configure() + @classmethod def _alias_factory( cls, diff --git a/test/orm/test_utils.py b/test/orm/test_utils.py index 41a7f113cf..a685274d09 100644 --- a/test/orm/test_utils.py +++ b/test/orm/test_utils.py @@ -14,6 +14,8 @@ from sqlalchemy.ext.hybrid import hybrid_method from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import aliased from sqlalchemy.orm import clear_mappers +from sqlalchemy.orm import Mapped +from sqlalchemy.orm import mapped_column from sqlalchemy.orm import relationship from sqlalchemy.orm import Session from sqlalchemy.orm import synonym @@ -29,6 +31,7 @@ from sqlalchemy.testing import expect_raises from sqlalchemy.testing import expect_warnings from sqlalchemy.testing import fixtures from sqlalchemy.testing import is_ +from sqlalchemy.testing.assertions import is_false from sqlalchemy.testing.assertions import is_true from sqlalchemy.testing.fixtures import fixture_session from test.orm import _fixtures @@ -1330,3 +1333,128 @@ class PathRegistryInhTest(_poly_fixtures._Polymorphic): # polymorphic AliasedClass with the "use_mapper_path" flag - # the AliasedClass acts just like the base mapper eq_(p1.path, (emapper, emapper.attrs.machines)) + + +class ConfigureAliasedOnSelectTest(fixtures.TestBase): + """Test for #13319 + + Test that select() with aliased() or with_polymorphic() entities + triggers configure_mappers implicitly for all entity types. + """ + + @testing.fixture + def non_inherited_fixture(self, decl_base): + class User(decl_base): + __tablename__ = "user" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column() + + return User, decl_base + + @testing.fixture + def inherited_with_leaves_fixture(self, decl_base): + class Employee(decl_base): + __tablename__ = "employee" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column() + type: Mapped[str] = mapped_column() + __mapper_args__ = { + "polymorphic_identity": "employee", + "polymorphic_on": "type", + } + + class Engineer(Employee): + __tablename__ = "engineer" + id: Mapped[int] = mapped_column( + ForeignKey("employee.id"), primary_key=True + ) + engineer_name: Mapped[str] = mapped_column() + __mapper_args__ = {"polymorphic_identity": "engineer"} + + return Employee, Engineer, decl_base + + @testing.fixture + def inherited_without_leaves_fixture(self, decl_base): + class Employee(decl_base): + __tablename__ = "employee" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column() + type: Mapped[str] = mapped_column() + __mapper_args__ = { + "polymorphic_identity": "employee", + "polymorphic_on": "type", + } + + return Employee, decl_base + + def test_aliased_non_inherited(self, non_inherited_fixture): + User, decl_base = non_inherited_fixture + is_false(User.__mapper__.configured) + + a1 = aliased(User) + _ = select(a1) + is_true(User.__mapper__.configured) + + def test_aliased_inherited_base_with_leaves( + self, inherited_with_leaves_fixture + ): + Employee, Engineer, decl_base = inherited_with_leaves_fixture + is_false(Employee.__mapper__.configured) + + a1 = aliased(Employee) + _ = select(a1) + is_true(Employee.__mapper__.configured) + + def test_aliased_inherited_leaf(self, inherited_with_leaves_fixture): + Employee, Engineer, decl_base = inherited_with_leaves_fixture + is_false(Engineer.__mapper__.configured) + + a1 = aliased(Engineer) + _ = select(a1) + is_true(Engineer.__mapper__.configured) + + def test_aliased_inherited_without_leaves( + self, inherited_without_leaves_fixture + ): + Employee, decl_base = inherited_without_leaves_fixture + is_false(Employee.__mapper__.configured) + + a1 = aliased(Employee) + _ = select(a1) + is_true(Employee.__mapper__.configured) + + def test_wp_inherited_base_with_leaves( + self, inherited_with_leaves_fixture + ): + Employee, Engineer, decl_base = inherited_with_leaves_fixture + is_false(Employee.__mapper__.configured) + + wp = with_polymorphic(Employee, "*") + select(wp).compile() + is_true(Employee.__mapper__.configured) + + def test_wp_inherited_leaf(self, inherited_with_leaves_fixture): + Employee, Engineer, decl_base = inherited_with_leaves_fixture + is_false(Engineer.__mapper__.configured) + + wp = with_polymorphic(Engineer, "*") + select(wp).compile() + is_true(Engineer.__mapper__.configured) + + def test_wp_inherited_without_leaves( + self, inherited_without_leaves_fixture + ): + Employee, decl_base = inherited_without_leaves_fixture + is_false(Employee.__mapper__.configured) + + wp = with_polymorphic(Employee, "*") + select(wp).compile() + is_true(Employee.__mapper__.configured) + + def test_wp_non_inherited(self, non_inherited_fixture): + User, decl_base = non_inherited_fixture + is_false(User.__mapper__.configured) + + wp = with_polymorphic(User, "*") + select(wp).compile() + is_true(User.__mapper__.configured)