]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
implement _post_inspect for AliasedInsp
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 20 May 2026 19:59:10 +0000 (15:59 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 20 May 2026 20:30:28 +0000 (16:30 -0400)
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

doc/build/changelog/unreleased_20/13319.rst [new file with mode: 0644]
lib/sqlalchemy/orm/util.py
test/orm/test_utils.py

diff --git a/doc/build/changelog/unreleased_20/13319.rst b/doc/build/changelog/unreleased_20/13319.rst
new file mode 100644 (file)
index 0000000..76b8b4c
--- /dev/null
@@ -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.
+
index 2abdd25ad31c12bf316febe51bdf06fe18464d5c..e99e55fe7d87a33fbaed87dd41422c908a2fa8fb 100644 (file)
@@ -1013,6 +1013,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,
index 41a7f113cf24ce2a8bd3a06ff2e6f186f23e03ca..a685274d094ae5666c2794b5205586b4b92794bd 100644 (file)
@@ -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)