Team Ai
Datasetpublic

codekingpro/portable-devtools

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