From 2f48773061694d9b02044751e6a63a478ac24bd3 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 28 Apr 2024 13:39:08 -0400 Subject: [PATCH] only consider column / relationship attrs for subclass IN Fixed issue in :func:`_orm.selectin_polymorhpic` loader option where attributes defined with :func:`_orm.composite` on a superclass would cause an internal exception on load. Define the prop for :class:`.PropRegistry` as a :class:`.StrategizedProperty`; we dont make path registries for descriptor props like synonyms, composites, etc. Fixes: #11291 Change-Id: I6f16844d2483dc86ab402b0b8b1f09561498aa1f (cherry picked from commit f4a0ff730cc753d4d6f947959c6551fd10d7d699) --- doc/build/changelog/unreleased_20/11291.rst | 8 +++++ lib/sqlalchemy/orm/mapper.py | 2 +- lib/sqlalchemy/orm/path_registry.py | 25 ++++++++------- test/orm/inheritance/test_poly_loading.py | 35 ++++++++++++--------- 4 files changed, 44 insertions(+), 26 deletions(-) create mode 100644 doc/build/changelog/unreleased_20/11291.rst diff --git a/doc/build/changelog/unreleased_20/11291.rst b/doc/build/changelog/unreleased_20/11291.rst new file mode 100644 index 0000000000..e341ff8aff --- /dev/null +++ b/doc/build/changelog/unreleased_20/11291.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, orm + :tickets: 11291 + + Fixed issue in :func:`_orm.selectin_polymorhpic` loader option where + attributes defined with :func:`_orm.composite` on a superclass would cause + an internal exception on load. + diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 3052710f82..06e3884be6 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -3829,7 +3829,7 @@ class Mapper( classes_to_include.add(m) m = m.inherits - for prop in self.attrs: + for prop in self.column_attrs + self.relationships: # skip prop keys that are not instrumented on the mapped class. # this is primarily the "_sa_polymorphic_on" property that gets # created for an ad-hoc polymorphic_on SQL expression, issue #8704 diff --git a/lib/sqlalchemy/orm/path_registry.py b/lib/sqlalchemy/orm/path_registry.py index 76484b3e68..4ee8ac71b8 100644 --- a/lib/sqlalchemy/orm/path_registry.py +++ b/lib/sqlalchemy/orm/path_registry.py @@ -35,7 +35,7 @@ from ..sql.cache_key import HasCacheKey if TYPE_CHECKING: from ._typing import _InternalEntityType - from .interfaces import MapperProperty + from .interfaces import StrategizedProperty from .mapper import Mapper from .relationships import RelationshipProperty from .util import AliasedInsp @@ -57,13 +57,13 @@ else: _SerializedPath = List[Any] _StrPathToken = str _PathElementType = Union[ - _StrPathToken, "_InternalEntityType[Any]", "MapperProperty[Any]" + _StrPathToken, "_InternalEntityType[Any]", "StrategizedProperty[Any]" ] # the representation is in fact # a tuple with alternating: -# [_InternalEntityType[Any], Union[str, MapperProperty[Any]], -# _InternalEntityType[Any], Union[str, MapperProperty[Any]], ...] +# [_InternalEntityType[Any], Union[str, StrategizedProperty[Any]], +# _InternalEntityType[Any], Union[str, StrategizedProperty[Any]], ...] # this might someday be a tuple of 2-tuples instead, but paths can be # chopped at odd intervals as well so this is less flexible _PathRepresentation = Tuple[_PathElementType, ...] @@ -71,7 +71,7 @@ _PathRepresentation = Tuple[_PathElementType, ...] # NOTE: these names are weird since the array is 0-indexed, # the "_Odd" entries are at 0, 2, 4, etc _OddPathRepresentation = Sequence["_InternalEntityType[Any]"] -_EvenPathRepresentation = Sequence[Union["MapperProperty[Any]", str]] +_EvenPathRepresentation = Sequence[Union["StrategizedProperty[Any]", str]] log = logging.getLogger(__name__) @@ -197,7 +197,9 @@ class PathRegistry(HasCacheKey): ) -> AbstractEntityRegistry: ... @overload - def __getitem__(self, entity: MapperProperty[Any]) -> PropRegistry: ... + def __getitem__( + self, entity: StrategizedProperty[Any] + ) -> PropRegistry: ... def __getitem__( self, @@ -206,7 +208,7 @@ class PathRegistry(HasCacheKey): int, slice, _InternalEntityType[Any], - MapperProperty[Any], + StrategizedProperty[Any], ], ) -> Union[ TokenRegistry, @@ -225,7 +227,7 @@ class PathRegistry(HasCacheKey): def pairs( self, ) -> Iterator[ - Tuple[_InternalEntityType[Any], Union[str, MapperProperty[Any]]] + Tuple[_InternalEntityType[Any], Union[str, StrategizedProperty[Any]]] ]: odd_path = cast(_OddPathRepresentation, self.path) even_path = cast(_EvenPathRepresentation, odd_path) @@ -531,15 +533,16 @@ class PropRegistry(PathRegistry): inherit_cache = True is_property = True - prop: MapperProperty[Any] + prop: StrategizedProperty[Any] mapper: Optional[Mapper[Any]] entity: Optional[_InternalEntityType[Any]] def __init__( - self, parent: AbstractEntityRegistry, prop: MapperProperty[Any] + self, parent: AbstractEntityRegistry, prop: StrategizedProperty[Any] ): + # restate this path in terms of the - # given MapperProperty's parent. + # given StrategizedProperty's parent. insp = cast("_InternalEntityType[Any]", parent[-1]) natural_parent: AbstractEntityRegistry = parent diff --git a/test/orm/inheritance/test_poly_loading.py b/test/orm/inheritance/test_poly_loading.py index a768c32754..58cf7b5427 100644 --- a/test/orm/inheritance/test_poly_loading.py +++ b/test/orm/inheritance/test_poly_loading.py @@ -1470,18 +1470,10 @@ class NoBaseWPPlusAliasedTest( class CompositeAttributesTest(fixtures.TestBase): - @testing.fixture - def mapping_fixture(self, registry, connection): - Base = registry.generate_base() - class BaseCls(Base): - __tablename__ = "base" - id = Column( - Integer, primary_key=True, test_needs_autoincrement=True - ) - type = Column(String(50)) - - __mapper_args__ = {"polymorphic_on": type} + @testing.fixture(params=("base", "sub")) + def mapping_fixture(self, request, registry, connection): + Base = registry.generate_base() class XYThing: def __init__(self, x, y): @@ -1501,13 +1493,28 @@ class CompositeAttributesTest(fixtures.TestBase): def __ne__(self, other): return not self.__eq__(other) + class BaseCls(Base): + __tablename__ = "base" + id = Column( + Integer, primary_key=True, test_needs_autoincrement=True + ) + type = Column(String(50)) + + if request.param == "base": + comp1 = composite( + XYThing, Column("x1", Integer), Column("y1", Integer) + ) + + __mapper_args__ = {"polymorphic_on": type} + class A(ComparableEntity, BaseCls): __tablename__ = "a" id = Column(ForeignKey(BaseCls.id), primary_key=True) thing1 = Column(String(50)) - comp1 = composite( - XYThing, Column("x1", Integer), Column("y1", Integer) - ) + if request.param == "sub": + comp1 = composite( + XYThing, Column("x1", Integer), Column("y1", Integer) + ) __mapper_args__ = { "polymorphic_identity": "a", -- 2.47.2