1# orm/path_registry.py 
    2# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors 
    3# <see AUTHORS file> 
    4# 
    5# This module is part of SQLAlchemy and is released under 
    6# the MIT License: https://www.opensource.org/licenses/mit-license.php 
    7"""Path tracking utilities, representing mapper graph traversals.""" 
    8 
    9from __future__ import annotations 
    10 
    11from functools import reduce 
    12from itertools import chain 
    13import logging 
    14import operator 
    15from typing import Any 
    16from typing import cast 
    17from typing import Dict 
    18from typing import Iterator 
    19from typing import List 
    20from typing import Optional 
    21from typing import overload 
    22from typing import Sequence 
    23from typing import Tuple 
    24from typing import TYPE_CHECKING 
    25from typing import Union 
    26 
    27from . import base as orm_base 
    28from ._typing import insp_is_mapper_property 
    29from .. import exc 
    30from .. import util 
    31from ..sql import visitors 
    32from ..sql.cache_key import HasCacheKey 
    33 
    34if TYPE_CHECKING: 
    35    from typing import TypeGuard 
    36 
    37    from ._typing import _InternalEntityType 
    38    from .interfaces import StrategizedProperty 
    39    from .mapper import Mapper 
    40    from .relationships import RelationshipProperty 
    41    from .util import AliasedInsp 
    42    from ..sql.cache_key import _CacheKeyTraversalType 
    43    from ..sql.elements import BindParameter 
    44    from ..sql.visitors import anon_map 
    45    from ..util.typing import _LiteralStar 
    46 
    47    def is_root(path: PathRegistry) -> TypeGuard[RootRegistry]: ... 
    48 
    49    def is_entity( 
    50        path: PathRegistry, 
    51    ) -> TypeGuard[_AbstractEntityRegistry]: ... 
    52 
    53else: 
    54    is_root = operator.attrgetter("is_root") 
    55    is_entity = operator.attrgetter("is_entity") 
    56 
    57 
    58_SerializedPath = List[Any] 
    59_StrPathToken = str 
    60_PathElementType = Union[ 
    61    _StrPathToken, "_InternalEntityType[Any]", "StrategizedProperty[Any]" 
    62] 
    63 
    64# the representation is in fact 
    65# a tuple with alternating: 
    66# [_InternalEntityType[Any], Union[str, StrategizedProperty[Any]], 
    67# _InternalEntityType[Any], Union[str, StrategizedProperty[Any]], ...] 
    68# this might someday be a tuple of 2-tuples instead, but paths can be 
    69# chopped at odd intervals as well so this is less flexible 
    70_PathRepresentation = Tuple[_PathElementType, ...] 
    71 
    72# NOTE: these names are weird since the array is 0-indexed, 
    73# the "_Odd" entries are at 0, 2, 4, etc 
    74_OddPathRepresentation = Sequence["_InternalEntityType[Any]"] 
    75_EvenPathRepresentation = Sequence[Union["StrategizedProperty[Any]", str]] 
    76 
    77 
    78log = logging.getLogger(__name__) 
    79 
    80 
    81def _unreduce_path(path: _SerializedPath) -> PathRegistry: 
    82    return PathRegistry.deserialize(path) 
    83 
    84 
    85_WILDCARD_TOKEN: _LiteralStar = "*" 
    86_DEFAULT_TOKEN = "_sa_default" 
    87 
    88 
    89class PathRegistry(HasCacheKey): 
    90    """Represent query load paths and registry functions. 
    91 
    92    Basically represents structures like: 
    93 
    94    (<User mapper>, "orders", <Order mapper>, "items", <Item mapper>) 
    95 
    96    These structures are generated by things like 
    97    query options (joinedload(), subqueryload(), etc.) and are 
    98    used to compose keys stored in the query._attributes dictionary 
    99    for various options. 
    100 
    101    They are then re-composed at query compile/result row time as 
    102    the query is formed and as rows are fetched, where they again 
    103    serve to compose keys to look up options in the context.attributes 
    104    dictionary, which is copied from query._attributes. 
    105 
    106    The path structure has a limited amount of caching, where each 
    107    "root" ultimately pulls from a fixed registry associated with 
    108    the first mapper, that also contains elements for each of its 
    109    property keys.  However paths longer than two elements, which 
    110    are the exception rather than the rule, are generated on an 
    111    as-needed basis. 
    112 
    113    """ 
    114 
    115    __slots__ = () 
    116 
    117    is_token = False 
    118    is_root = False 
    119    has_entity = False 
    120    is_property = False 
    121    is_entity = False 
    122 
    123    is_unnatural: bool 
    124 
    125    path: _PathRepresentation 
    126    natural_path: _PathRepresentation 
    127    parent: Optional[PathRegistry] 
    128    root: RootRegistry 
    129 
    130    _cache_key_traversal: _CacheKeyTraversalType = [ 
    131        ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key_list) 
    132    ] 
    133 
    134    def __eq__(self, other: Any) -> bool: 
    135        try: 
    136            return other is not None and self.path == other._path_for_compare 
    137        except AttributeError: 
    138            util.warn( 
    139                "Comparison of PathRegistry to %r is not supported" 
    140                % (type(other)) 
    141            ) 
    142            return False 
    143 
    144    def __ne__(self, other: Any) -> bool: 
    145        try: 
    146            return other is None or self.path != other._path_for_compare 
    147        except AttributeError: 
    148            util.warn( 
    149                "Comparison of PathRegistry to %r is not supported" 
    150                % (type(other)) 
    151            ) 
    152            return True 
    153 
    154    @property 
    155    def _path_for_compare(self) -> Optional[_PathRepresentation]: 
    156        return self.path 
    157 
    158    def odd_element(self, index: int) -> _InternalEntityType[Any]: 
    159        return self.path[index]  # type: ignore 
    160 
    161    def set(self, attributes: Dict[Any, Any], key: Any, value: Any) -> None: 
    162        log.debug("set '%s' on path '%s' to '%s'", key, self, value) 
    163        attributes[(key, self.natural_path)] = value 
    164 
    165    def setdefault( 
    166        self, attributes: Dict[Any, Any], key: Any, value: Any 
    167    ) -> None: 
    168        log.debug("setdefault '%s' on path '%s' to '%s'", key, self, value) 
    169        attributes.setdefault((key, self.natural_path), value) 
    170 
    171    def get( 
    172        self, attributes: Dict[Any, Any], key: Any, value: Optional[Any] = None 
    173    ) -> Any: 
    174        key = (key, self.natural_path) 
    175        if key in attributes: 
    176            return attributes[key] 
    177        else: 
    178            return value 
    179 
    180    def __len__(self) -> int: 
    181        return len(self.path) 
    182 
    183    def __hash__(self) -> int: 
    184        return id(self) 
    185 
    186    @overload 
    187    def __getitem__(self, entity: _StrPathToken) -> _TokenRegistry: ... 
    188 
    189    @overload 
    190    def __getitem__(self, entity: int) -> _PathElementType: ... 
    191 
    192    @overload 
    193    def __getitem__(self, entity: slice) -> _PathRepresentation: ... 
    194 
    195    @overload 
    196    def __getitem__( 
    197        self, entity: _InternalEntityType[Any] 
    198    ) -> _AbstractEntityRegistry: ... 
    199 
    200    @overload 
    201    def __getitem__( 
    202        self, entity: StrategizedProperty[Any] 
    203    ) -> _PropRegistry: ... 
    204 
    205    def __getitem__( 
    206        self, 
    207        entity: Union[ 
    208            _StrPathToken, 
    209            int, 
    210            slice, 
    211            _InternalEntityType[Any], 
    212            StrategizedProperty[Any], 
    213        ], 
    214    ) -> Union[ 
    215        _TokenRegistry, 
    216        _PathElementType, 
    217        _PathRepresentation, 
    218        _PropRegistry, 
    219        _AbstractEntityRegistry, 
    220    ]: 
    221        raise NotImplementedError() 
    222 
    223    # TODO: what are we using this for? 
    224    @property 
    225    def length(self) -> int: 
    226        return len(self.path) 
    227 
    228    def pairs( 
    229        self, 
    230    ) -> Iterator[ 
    231        Tuple[_InternalEntityType[Any], Union[str, StrategizedProperty[Any]]] 
    232    ]: 
    233        odd_path = cast(_OddPathRepresentation, self.path) 
    234        even_path = cast(_EvenPathRepresentation, odd_path) 
    235        for i in range(0, len(odd_path), 2): 
    236            yield odd_path[i], even_path[i + 1] 
    237 
    238    def contains_mapper(self, mapper: Mapper[Any]) -> bool: 
    239        _m_path = cast(_OddPathRepresentation, self.path) 
    240        for path_mapper in [_m_path[i] for i in range(0, len(_m_path), 2)]: 
    241            if path_mapper.mapper.isa(mapper): 
    242                return True 
    243        else: 
    244            return False 
    245 
    246    def contains(self, attributes: Dict[Any, Any], key: Any) -> bool: 
    247        return (key, self.path) in attributes 
    248 
    249    def __reduce__(self) -> Any: 
    250        return _unreduce_path, (self.serialize(),) 
    251 
    252    @classmethod 
    253    def _serialize_path(cls, path: _PathRepresentation) -> _SerializedPath: 
    254        _m_path = cast(_OddPathRepresentation, path) 
    255        _p_path = cast(_EvenPathRepresentation, path) 
    256 
    257        return list( 
    258            zip( 
    259                tuple( 
    260                    m.class_ if (m.is_mapper or m.is_aliased_class) else str(m) 
    261                    for m in [_m_path[i] for i in range(0, len(_m_path), 2)] 
    262                ), 
    263                tuple( 
    264                    p.key if insp_is_mapper_property(p) else str(p) 
    265                    for p in [_p_path[i] for i in range(1, len(_p_path), 2)] 
    266                ) 
    267                + (None,), 
    268            ) 
    269        ) 
    270 
    271    @classmethod 
    272    def _deserialize_path(cls, path: _SerializedPath) -> _PathRepresentation: 
    273        def _deserialize_mapper_token(mcls: Any) -> Any: 
    274            return ( 
    275                # note: we likely dont want configure=True here however 
    276                # this is maintained at the moment for backwards compatibility 
    277                orm_base._inspect_mapped_class(mcls, configure=True) 
    278                if mcls not in PathToken._intern 
    279                else PathToken._intern[mcls] 
    280            ) 
    281 
    282        def _deserialize_key_token(mcls: Any, key: Any) -> Any: 
    283            if key is None: 
    284                return None 
    285            elif key in PathToken._intern: 
    286                return PathToken._intern[key] 
    287            else: 
    288                mp = orm_base._inspect_mapped_class(mcls, configure=True) 
    289                assert mp is not None 
    290                return mp.attrs[key] 
    291 
    292        p = tuple( 
    293            chain( 
    294                *[ 
    295                    ( 
    296                        _deserialize_mapper_token(mcls), 
    297                        _deserialize_key_token(mcls, key), 
    298                    ) 
    299                    for mcls, key in path 
    300                ] 
    301            ) 
    302        ) 
    303        if p and p[-1] is None: 
    304            p = p[0:-1] 
    305        return p 
    306 
    307    def serialize(self) -> _SerializedPath: 
    308        path = self.path 
    309        return self._serialize_path(path) 
    310 
    311    @classmethod 
    312    def deserialize(cls, path: _SerializedPath) -> PathRegistry: 
    313        assert path is not None 
    314        p = cls._deserialize_path(path) 
    315        return cls.coerce(p) 
    316 
    317    @overload 
    318    @classmethod 
    319    def per_mapper(cls, mapper: Mapper[Any]) -> _CachingEntityRegistry: ... 
    320 
    321    @overload 
    322    @classmethod 
    323    def per_mapper(cls, mapper: AliasedInsp[Any]) -> _SlotsEntityRegistry: ... 
    324 
    325    @classmethod 
    326    def per_mapper( 
    327        cls, mapper: _InternalEntityType[Any] 
    328    ) -> _AbstractEntityRegistry: 
    329        if mapper.is_mapper: 
    330            return _CachingEntityRegistry(cls.root, mapper) 
    331        else: 
    332            return _SlotsEntityRegistry(cls.root, mapper) 
    333 
    334    @classmethod 
    335    def coerce(cls, raw: _PathRepresentation) -> PathRegistry: 
    336        def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry: 
    337            return prev[next_] 
    338 
    339        # can't quite get mypy to appreciate this one :) 
    340        return reduce(_red, raw, cls.root)  # type: ignore 
    341 
    342    def __add__(self, other: PathRegistry) -> PathRegistry: 
    343        def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry: 
    344            return prev[next_] 
    345 
    346        return reduce(_red, other.path, self) 
    347 
    348    def __str__(self) -> str: 
    349        return f"ORM Path[{' -> '.join(str(elem) for elem in self.path)}]" 
    350 
    351    def __repr__(self) -> str: 
    352        return f"{self.__class__.__name__}({self.path!r})" 
    353 
    354 
    355class _CreatesToken(PathRegistry): 
    356    __slots__ = () 
    357 
    358    is_aliased_class: bool 
    359    is_root: bool 
    360 
    361    def token(self, token: _StrPathToken) -> _TokenRegistry: 
    362        if token.endswith(f":{_WILDCARD_TOKEN}"): 
    363            return _TokenRegistry(self, token) 
    364        elif token.endswith(f":{_DEFAULT_TOKEN}"): 
    365            return _TokenRegistry(self.root, token) 
    366        else: 
    367            raise exc.ArgumentError(f"invalid token: {token}") 
    368 
    369 
    370class RootRegistry(_CreatesToken): 
    371    """Root registry, defers to mappers so that 
    372    paths are maintained per-root-mapper. 
    373 
    374    """ 
    375 
    376    __slots__ = () 
    377 
    378    inherit_cache = True 
    379 
    380    path = natural_path = () 
    381    has_entity = False 
    382    is_aliased_class = False 
    383    is_root = True 
    384    is_unnatural = False 
    385 
    386    def _getitem( 
    387        self, entity: Any 
    388    ) -> Union[_TokenRegistry, _AbstractEntityRegistry]: 
    389        if entity in PathToken._intern: 
    390            if TYPE_CHECKING: 
    391                assert isinstance(entity, _StrPathToken) 
    392            return _TokenRegistry(self, PathToken._intern[entity]) 
    393        else: 
    394            try: 
    395                return entity._path_registry  # type: ignore 
    396            except AttributeError: 
    397                raise IndexError( 
    398                    f"invalid argument for RootRegistry.__getitem__: {entity}" 
    399                ) 
    400 
    401    def _truncate_recursive(self) -> RootRegistry: 
    402        return self 
    403 
    404    if not TYPE_CHECKING: 
    405        __getitem__ = _getitem 
    406 
    407 
    408PathRegistry.root = RootRegistry() 
    409 
    410 
    411class PathToken(orm_base.InspectionAttr, HasCacheKey, str): 
    412    """cacheable string token""" 
    413 
    414    _intern: Dict[str, PathToken] = {} 
    415 
    416    def _gen_cache_key( 
    417        self, anon_map: anon_map, bindparams: List[BindParameter[Any]] 
    418    ) -> Tuple[Any, ...]: 
    419        return (str(self),) 
    420 
    421    @property 
    422    def _path_for_compare(self) -> Optional[_PathRepresentation]: 
    423        return None 
    424 
    425    @classmethod 
    426    def intern(cls, strvalue: str) -> PathToken: 
    427        if strvalue in cls._intern: 
    428            return cls._intern[strvalue] 
    429        else: 
    430            cls._intern[strvalue] = result = PathToken(strvalue) 
    431            return result 
    432 
    433 
    434class _TokenRegistry(PathRegistry): 
    435    __slots__ = ("token", "parent", "path", "natural_path") 
    436 
    437    inherit_cache = True 
    438 
    439    token: _StrPathToken 
    440    parent: _CreatesToken 
    441 
    442    def __init__(self, parent: _CreatesToken, token: _StrPathToken): 
    443        token = PathToken.intern(token) 
    444 
    445        self.token = token 
    446        self.parent = parent 
    447        self.path = parent.path + (token,) 
    448        self.natural_path = parent.natural_path + (token,) 
    449 
    450    has_entity = False 
    451 
    452    is_token = True 
    453 
    454    def generate_for_superclasses(self) -> Iterator[PathRegistry]: 
    455        # NOTE: this method is no longer used.  consider removal 
    456        parent = self.parent 
    457        if is_root(parent): 
    458            yield self 
    459            return 
    460 
    461        if TYPE_CHECKING: 
    462            assert isinstance(parent, _AbstractEntityRegistry) 
    463        if not parent.is_aliased_class: 
    464            for mp_ent in parent.mapper.iterate_to_root(): 
    465                yield _TokenRegistry(parent.parent[mp_ent], self.token) 
    466        elif ( 
    467            parent.is_aliased_class 
    468            and cast( 
    469                "AliasedInsp[Any]", 
    470                parent.entity, 
    471            )._is_with_polymorphic 
    472        ): 
    473            yield self 
    474            for ent in cast( 
    475                "AliasedInsp[Any]", parent.entity 
    476            )._with_polymorphic_entities: 
    477                yield _TokenRegistry(parent.parent[ent], self.token) 
    478        else: 
    479            yield self 
    480 
    481    def _generate_natural_for_superclasses( 
    482        self, 
    483    ) -> Iterator[_PathRepresentation]: 
    484        parent = self.parent 
    485        if is_root(parent): 
    486            yield self.natural_path 
    487            return 
    488 
    489        if TYPE_CHECKING: 
    490            assert isinstance(parent, _AbstractEntityRegistry) 
    491        for mp_ent in parent.mapper.iterate_to_root(): 
    492            yield _TokenRegistry( 
    493                parent.parent[mp_ent], self.token 
    494            ).natural_path 
    495        if ( 
    496            parent.is_aliased_class 
    497            and cast( 
    498                "AliasedInsp[Any]", 
    499                parent.entity, 
    500            )._is_with_polymorphic 
    501        ): 
    502            yield self.natural_path 
    503            for ent in cast( 
    504                "AliasedInsp[Any]", parent.entity 
    505            )._with_polymorphic_entities: 
    506                yield ( 
    507                    _TokenRegistry(parent.parent[ent], self.token).natural_path 
    508                ) 
    509        else: 
    510            yield self.natural_path 
    511 
    512    def _getitem(self, entity: Any) -> Any: 
    513        try: 
    514            return self.path[entity] 
    515        except TypeError as err: 
    516            raise IndexError(f"{entity}") from err 
    517 
    518    if not TYPE_CHECKING: 
    519        __getitem__ = _getitem 
    520 
    521 
    522class _PropRegistry(PathRegistry): 
    523    __slots__ = ( 
    524        "prop", 
    525        "parent", 
    526        "path", 
    527        "natural_path", 
    528        "has_entity", 
    529        "entity", 
    530        "mapper", 
    531        "_wildcard_path_loader_key", 
    532        "_default_path_loader_key", 
    533        "_loader_key", 
    534        "is_unnatural", 
    535    ) 
    536    inherit_cache = True 
    537    is_property = True 
    538 
    539    prop: StrategizedProperty[Any] 
    540    mapper: Optional[Mapper[Any]] 
    541    entity: Optional[_InternalEntityType[Any]] 
    542 
    543    def __init__( 
    544        self, parent: _AbstractEntityRegistry, prop: StrategizedProperty[Any] 
    545    ): 
    546 
    547        # restate this path in terms of the 
    548        # given StrategizedProperty's parent. 
    549        insp = cast("_InternalEntityType[Any]", parent[-1]) 
    550        natural_parent: _AbstractEntityRegistry = parent 
    551 
    552        # inherit "is_unnatural" from the parent 
    553        self.is_unnatural = parent.parent.is_unnatural or bool( 
    554            parent.mapper.inherits 
    555        ) 
    556 
    557        if not insp.is_aliased_class or insp._use_mapper_path:  # type: ignore 
    558            parent = natural_parent = parent.parent[prop.parent] 
    559        elif ( 
    560            insp.is_aliased_class 
    561            and insp.with_polymorphic_mappers 
    562            and prop.parent in insp.with_polymorphic_mappers 
    563        ): 
    564            subclass_entity: _InternalEntityType[Any] = parent[-1]._entity_for_mapper(prop.parent)  # type: ignore  # noqa: E501 
    565            parent = parent.parent[subclass_entity] 
    566 
    567            # when building a path where with_polymorphic() is in use, 
    568            # special logic to determine the "natural path" when subclass 
    569            # entities are used. 
    570            # 
    571            # here we are trying to distinguish between a path that starts 
    572            # on a with_polymorphic entity vs. one that starts on a 
    573            # normal entity that introduces a with_polymorphic() in the 
    574            # middle using of_type(): 
    575            # 
    576            #  # as in test_polymorphic_rel-> 
    577            #  #    test_subqueryload_on_subclass_uses_path_correctly 
    578            #  wp = with_polymorphic(RegularEntity, "*") 
    579            #  sess.query(wp).options(someload(wp.SomeSubEntity.foos)) 
    580            # 
    581            # vs 
    582            # 
    583            #  # as in test_relationship->JoinedloadWPolyOfTypeContinued 
    584            #  wp = with_polymorphic(SomeFoo, "*") 
    585            #  sess.query(RegularEntity).options( 
    586            #       someload(RegularEntity.foos.of_type(wp)) 
    587            #       .someload(wp.SubFoo.bar) 
    588            #   ) 
    589            # 
    590            # in the former case, the Query as it generates a path that we 
    591            # want to match will be in terms of the with_polymorphic at the 
    592            # beginning.  in the latter case, Query will generate simple 
    593            # paths that don't know about this with_polymorphic, so we must 
    594            # use a separate natural path. 
    595            # 
    596            # 
    597            if parent.parent: 
    598                natural_parent = parent.parent[subclass_entity.mapper] 
    599                self.is_unnatural = True 
    600            else: 
    601                natural_parent = parent 
    602        elif ( 
    603            natural_parent.parent 
    604            and insp.is_aliased_class 
    605            and prop.parent  # this should always be the case here 
    606            is not insp.mapper 
    607            and insp.mapper.isa(prop.parent) 
    608        ): 
    609            natural_parent = parent.parent[prop.parent] 
    610 
    611        self.prop = prop 
    612        self.parent = parent 
    613        self.path = parent.path + (prop,) 
    614        self.natural_path = natural_parent.natural_path + (prop,) 
    615 
    616        self.has_entity = prop._links_to_entity 
    617        if prop._is_relationship: 
    618            if TYPE_CHECKING: 
    619                assert isinstance(prop, RelationshipProperty) 
    620            self.entity = prop.entity 
    621            self.mapper = prop.mapper 
    622        else: 
    623            self.entity = None 
    624            self.mapper = None 
    625 
    626        self._wildcard_path_loader_key = ( 
    627            "loader", 
    628            parent.natural_path + self.prop._wildcard_token, 
    629        ) 
    630        self._default_path_loader_key = self.prop._default_path_loader_key 
    631        self._loader_key = ("loader", self.natural_path) 
    632 
    633    def _truncate_recursive(self) -> _PropRegistry: 
    634        earliest = None 
    635        for i, token in enumerate(reversed(self.path[:-1])): 
    636            if token is self.prop: 
    637                earliest = i 
    638 
    639        if earliest is None: 
    640            return self 
    641        else: 
    642            return self.coerce(self.path[0 : -(earliest + 1)])  # type: ignore 
    643 
    644    @property 
    645    def entity_path(self) -> _AbstractEntityRegistry: 
    646        assert self.entity is not None 
    647        return self[self.entity] 
    648 
    649    def _getitem( 
    650        self, entity: Union[int, slice, _InternalEntityType[Any]] 
    651    ) -> Union[_AbstractEntityRegistry, _PathElementType, _PathRepresentation]: 
    652        if isinstance(entity, (int, slice)): 
    653            return self.path[entity] 
    654        else: 
    655            return _SlotsEntityRegistry(self, entity) 
    656 
    657    if not TYPE_CHECKING: 
    658        __getitem__ = _getitem 
    659 
    660 
    661class _AbstractEntityRegistry(_CreatesToken): 
    662    __slots__ = ( 
    663        "key", 
    664        "parent", 
    665        "is_aliased_class", 
    666        "path", 
    667        "entity", 
    668        "natural_path", 
    669    ) 
    670 
    671    has_entity = True 
    672    is_entity = True 
    673 
    674    parent: Union[RootRegistry, _PropRegistry] 
    675    key: _InternalEntityType[Any] 
    676    entity: _InternalEntityType[Any] 
    677    is_aliased_class: bool 
    678 
    679    def __init__( 
    680        self, 
    681        parent: Union[RootRegistry, _PropRegistry], 
    682        entity: _InternalEntityType[Any], 
    683    ): 
    684        self.key = entity 
    685        self.parent = parent 
    686        self.is_aliased_class = entity.is_aliased_class 
    687        self.entity = entity 
    688        self.path = parent.path + (entity,) 
    689 
    690        # the "natural path" is the path that we get when Query is traversing 
    691        # from the lead entities into the various relationships; it corresponds 
    692        # to the structure of mappers and relationships. when we are given a 
    693        # path that comes from loader options, as of 1.3 it can have ac-hoc 
    694        # with_polymorphic() and other AliasedInsp objects inside of it, which 
    695        # are usually not present in mappings.  So here we track both the 
    696        # "enhanced" path in self.path and the "natural" path that doesn't 
    697        # include those objects so these two traversals can be matched up. 
    698 
    699        # the test here for "(self.is_aliased_class or parent.is_unnatural)" 
    700        # are to avoid the more expensive conditional logic that follows if we 
    701        # know we don't have to do it.   This conditional can just as well be 
    702        # "if parent.path:", it just is more function calls. 
    703        # 
    704        # This is basically the only place that the "is_unnatural" flag 
    705        # actually changes behavior. 
    706        if parent.path and (self.is_aliased_class or parent.is_unnatural): 
    707            # this is an infrequent code path used only for loader strategies 
    708            # that also make use of of_type(). 
    709            if entity.mapper.isa(parent.natural_path[-1].mapper):  # type: ignore # noqa: E501 
    710                self.natural_path = parent.natural_path + (entity.mapper,) 
    711            else: 
    712                self.natural_path = parent.natural_path + ( 
    713                    parent.natural_path[-1].entity,  # type: ignore 
    714                ) 
    715        # it seems to make sense that since these paths get mixed up 
    716        # with statements that are cached or not, we should make 
    717        # sure the natural path is cacheable across different occurrences 
    718        # of equivalent AliasedClass objects.  however, so far this 
    719        # does not seem to be needed for whatever reason. 
    720        # elif not parent.path and self.is_aliased_class: 
    721        #     self.natural_path = (self.entity._generate_cache_key()[0], ) 
    722        else: 
    723            self.natural_path = self.path 
    724 
    725    def _truncate_recursive(self) -> _AbstractEntityRegistry: 
    726        return self.parent._truncate_recursive()[self.entity] 
    727 
    728    @property 
    729    def root_entity(self) -> _InternalEntityType[Any]: 
    730        return self.odd_element(0) 
    731 
    732    @property 
    733    def entity_path(self) -> PathRegistry: 
    734        return self 
    735 
    736    @property 
    737    def mapper(self) -> Mapper[Any]: 
    738        return self.entity.mapper 
    739 
    740    def __bool__(self) -> bool: 
    741        return True 
    742 
    743    def _getitem( 
    744        self, entity: Any 
    745    ) -> Union[_PathElementType, _PathRepresentation, PathRegistry]: 
    746        if isinstance(entity, (int, slice)): 
    747            return self.path[entity] 
    748        elif entity in PathToken._intern: 
    749            return _TokenRegistry(self, PathToken._intern[entity]) 
    750        else: 
    751            return _PropRegistry(self, entity) 
    752 
    753    if not TYPE_CHECKING: 
    754        __getitem__ = _getitem 
    755 
    756 
    757class _SlotsEntityRegistry(_AbstractEntityRegistry): 
    758    # for aliased class, return lightweight, no-cycles created 
    759    # version 
    760    inherit_cache = True 
    761 
    762 
    763class _ERDict(Dict[Any, Any]): 
    764    def __init__(self, registry: _CachingEntityRegistry): 
    765        self.registry = registry 
    766 
    767    def __missing__(self, key: Any) -> _PropRegistry: 
    768        self[key] = item = _PropRegistry(self.registry, key) 
    769 
    770        return item 
    771 
    772 
    773class _CachingEntityRegistry(_AbstractEntityRegistry): 
    774    # for long lived mapper, return dict based caching 
    775    # version that creates reference cycles 
    776 
    777    __slots__ = ("_cache",) 
    778 
    779    inherit_cache = True 
    780 
    781    def __init__( 
    782        self, 
    783        parent: Union[RootRegistry, _PropRegistry], 
    784        entity: _InternalEntityType[Any], 
    785    ): 
    786        super().__init__(parent, entity) 
    787        self._cache = _ERDict(self) 
    788 
    789    def pop(self, key: Any, default: Any) -> Any: 
    790        return self._cache.pop(key, default) 
    791 
    792    def _getitem(self, entity: Any) -> Any: 
    793        if isinstance(entity, (int, slice)): 
    794            return self.path[entity] 
    795        elif isinstance(entity, PathToken): 
    796            return _TokenRegistry(self, entity) 
    797        else: 
    798            return self._cache[entity] 
    799 
    800    if not TYPE_CHECKING: 
    801        __getitem__ = _getitem 
    802 
    803 
    804if TYPE_CHECKING: 
    805 
    806    def path_is_entity( 
    807        path: PathRegistry, 
    808    ) -> TypeGuard[_AbstractEntityRegistry]: ... 
    809 
    810    def path_is_property(path: PathRegistry) -> TypeGuard[_PropRegistry]: ... 
    811 
    812else: 
    813    path_is_entity = operator.attrgetter("is_entity") 
    814    path_is_property = operator.attrgetter("is_property")