Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
attributes.py2846 linesDownload Raw Back to orm
1# orm/attributes.py
2# Copyright (C) 2005-2026 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# mypy: allow-untyped-defs, allow-untyped-calls
8
9"""Defines instrumentation for class attributes and their interaction
10with instances.
11
12This module is usually not directly visible to user applications, but
13defines a large part of the ORM's interactivity.
14
15
16"""
17
18from __future__ import annotations
19
20import dataclasses
21import operator
22from typing import Any
23from typing import Callable
24from typing import cast
25from typing import ClassVar
26from typing import Dict
27from typing import Iterable
28from typing import List
29from typing import NamedTuple
30from typing import Optional
31from typing import overload
32from typing import Sequence
33from typing import Tuple
34from typing import Type
35from typing import TYPE_CHECKING
36from typing import TypeVar
37from typing import Union
38
39from . import collections
40from . import exc as orm_exc
41from . import interfaces
42from ._typing import insp_is_aliased_class
43from .base import _DeclarativeMapped
44from .base import ATTR_EMPTY
45from .base import ATTR_WAS_SET
46from .base import CALLABLES_OK
47from .base import DEFERRED_HISTORY_LOAD
48from .base import INCLUDE_PENDING_MUTATIONS  # noqa
49from .base import INIT_OK
50from .base import instance_dict as instance_dict
51from .base import instance_state as instance_state
52from .base import instance_str
53from .base import LOAD_AGAINST_COMMITTED
54from .base import LoaderCallableStatus
55from .base import manager_of_class as manager_of_class
56from .base import Mapped as Mapped  # noqa
57from .base import NEVER_SET  # noqa
58from .base import NO_AUTOFLUSH
59from .base import NO_CHANGE  # noqa
60from .base import NO_KEY
61from .base import NO_RAISE
62from .base import NO_VALUE
63from .base import NON_PERSISTENT_OK  # noqa
64from .base import opt_manager_of_class as opt_manager_of_class
65from .base import PASSIVE_CLASS_MISMATCH  # noqa
66from .base import PASSIVE_NO_FETCH
67from .base import PASSIVE_NO_FETCH_RELATED  # noqa
68from .base import PASSIVE_NO_INITIALIZE
69from .base import PASSIVE_NO_RESULT
70from .base import PASSIVE_OFF
71from .base import PASSIVE_ONLY_PERSISTENT
72from .base import PASSIVE_RETURN_NO_VALUE
73from .base import PassiveFlag
74from .base import RELATED_OBJECT_OK  # noqa
75from .base import SQL_OK  # noqa
76from .base import SQLORMExpression
77from .base import state_str
78from .. import event
79from .. import exc
80from .. import inspection
81from .. import util
82from ..event import dispatcher
83from ..event import EventTarget
84from ..sql import base as sql_base
85from ..sql import cache_key
86from ..sql import coercions
87from ..sql import roles
88from ..sql import visitors
89from ..sql.cache_key import HasCacheKey
90from ..sql.visitors import _TraverseInternalsType
91from ..sql.visitors import InternalTraversal
92from ..util.typing import Literal
93from ..util.typing import Self
94from ..util.typing import TypeGuard
95
96if TYPE_CHECKING:
97    from ._typing import _EntityType
98    from ._typing import _ExternalEntityType
99    from ._typing import _InstanceDict
100    from ._typing import _InternalEntityType
101    from ._typing import _LoaderCallable
102    from ._typing import _O
103    from .collections import _AdaptedCollectionProtocol
104    from .collections import CollectionAdapter
105    from .interfaces import MapperProperty
106    from .relationships import RelationshipProperty
107    from .state import InstanceState
108    from .util import AliasedInsp
109    from .writeonly import WriteOnlyAttributeImpl
110    from ..event.base import _Dispatch
111    from ..sql._typing import _ColumnExpressionArgument
112    from ..sql._typing import _DMLColumnArgument
113    from ..sql._typing import _InfoType
114    from ..sql._typing import _PropagateAttrsType
115    from ..sql.annotation import _AnnotationDict
116    from ..sql.elements import ColumnElement
117    from ..sql.elements import Label
118    from ..sql.operators import OperatorType
119    from ..sql.selectable import FromClause
120
121
122_T = TypeVar("_T")
123_T_co = TypeVar("_T_co", bound=Any, covariant=True)
124
125
126_AllPendingType = Sequence[
127    Tuple[Optional["InstanceState[Any]"], Optional[object]]
128]
129
130
131_UNKNOWN_ATTR_KEY = object()
132
133
134@inspection._self_inspects
135class QueryableAttribute(
136    _DeclarativeMapped[_T_co],
137    SQLORMExpression[_T_co],
138    interfaces.InspectionAttr,
139    interfaces.PropComparator[_T_co],
140    roles.JoinTargetRole,
141    roles.OnClauseRole,
142    sql_base.Immutable,
143    cache_key.SlotsMemoizedHasCacheKey,
144    util.MemoizedSlots,
145    EventTarget,
146):
147    """Base class for :term:`descriptor` objects that intercept
148    attribute events on behalf of a :class:`.MapperProperty`
149    object.  The actual :class:`.MapperProperty` is accessible
150    via the :attr:`.QueryableAttribute.property`
151    attribute.
152
153
154    .. seealso::
155
156        :class:`.InstrumentedAttribute`
157
158        :class:`.MapperProperty`
159
160        :attr:`_orm.Mapper.all_orm_descriptors`
161
162        :attr:`_orm.Mapper.attrs`
163    """
164
165    __slots__ = (
166        "class_",
167        "key",
168        "impl",
169        "comparator",
170        "property",
171        "parent",
172        "expression",
173        "_of_type",
174        "_extra_criteria",
175        "_slots_dispatch",
176        "_propagate_attrs",
177        "_doc",
178    )
179
180    is_attribute = True
181
182    dispatch: dispatcher[QueryableAttribute[_T_co]]
183
184    class_: _ExternalEntityType[Any]
185    key: str
186    parententity: _InternalEntityType[Any]
187    impl: AttributeImpl
188    comparator: interfaces.PropComparator[_T_co]
189    _of_type: Optional[_InternalEntityType[Any]]
190    _extra_criteria: Tuple[ColumnElement[bool], ...]
191    _doc: Optional[str]
192
193    # PropComparator has a __visit_name__ to participate within
194    # traversals.   Disambiguate the attribute vs. a comparator.
195    __visit_name__ = "orm_instrumented_attribute"
196
197    def __init__(
198        self,
199        class_: _ExternalEntityType[_O],
200        key: str,
201        parententity: _InternalEntityType[_O],
202        comparator: interfaces.PropComparator[_T_co],
203        impl: Optional[AttributeImpl] = None,
204        of_type: Optional[_InternalEntityType[Any]] = None,
205        extra_criteria: Tuple[ColumnElement[bool], ...] = (),
206    ):
207        self.class_ = class_
208        self.key = key
209
210        self._parententity = self.parent = parententity
211
212        # this attribute is non-None after mappers are set up, however in the
213        # interim class manager setup, there's a check for None to see if it
214        # needs to be populated, so we assign None here leaving the attribute
215        # in a temporarily not-type-correct state
216        self.impl = impl  # type: ignore
217
218        assert comparator is not None
219        self.comparator = comparator
220        self._of_type = of_type
221        self._extra_criteria = extra_criteria
222        self._doc = None
223
224        manager = opt_manager_of_class(class_)
225        # manager is None in the case of AliasedClass
226        if manager:
227            # propagate existing event listeners from
228            # immediate superclass
229            for base in manager._bases:
230                if key in base:
231                    self.dispatch._update(base[key].dispatch)
232                    if base[key].dispatch._active_history:
233                        self.dispatch._active_history = True  # type: ignore
234
235    _cache_key_traversal = [
236        ("key", visitors.ExtendedInternalTraversal.dp_string),
237        ("_parententity", visitors.ExtendedInternalTraversal.dp_multi),
238        ("_of_type", visitors.ExtendedInternalTraversal.dp_multi),
239        ("_extra_criteria", visitors.InternalTraversal.dp_clauseelement_list),
240    ]
241
242    def __reduce__(self) -> Any:
243        # this method is only used in terms of the
244        # sqlalchemy.ext.serializer extension
245        return (
246            _queryable_attribute_unreduce,
247            (
248                self.key,
249                self._parententity.mapper.class_,
250                self._parententity,
251                self._parententity.entity,
252            ),
253        )
254
255    @property
256    def _impl_uses_objects(self) -> bool:
257        return self.impl.uses_objects
258
259    def get_history(
260        self, instance: Any, passive: PassiveFlag = PASSIVE_OFF
261    ) -> History:
262        return self.impl.get_history(
263            instance_state(instance), instance_dict(instance), passive
264        )
265
266    @property
267    def info(self) -> _InfoType:
268        """Return the 'info' dictionary for the underlying SQL element.
269
270        The behavior here is as follows:
271
272        * If the attribute is a column-mapped property, i.e.
273          :class:`.ColumnProperty`, which is mapped directly
274          to a schema-level :class:`_schema.Column` object, this attribute
275          will return the :attr:`.SchemaItem.info` dictionary associated
276          with the core-level :class:`_schema.Column` object.
277
278        * If the attribute is a :class:`.ColumnProperty` but is mapped to
279          any other kind of SQL expression other than a
280          :class:`_schema.Column`,
281          the attribute will refer to the :attr:`.MapperProperty.info`
282          dictionary associated directly with the :class:`.ColumnProperty`,
283          assuming the SQL expression itself does not have its own ``.info``
284          attribute (which should be the case, unless a user-defined SQL
285          construct has defined one).
286
287        * If the attribute refers to any other kind of
288          :class:`.MapperProperty`, including :class:`.Relationship`,
289          the attribute will refer to the :attr:`.MapperProperty.info`
290          dictionary associated with that :class:`.MapperProperty`.
291
292        * To access the :attr:`.MapperProperty.info` dictionary of the
293          :class:`.MapperProperty` unconditionally, including for a
294          :class:`.ColumnProperty` that's associated directly with a
295          :class:`_schema.Column`, the attribute can be referred to using
296          :attr:`.QueryableAttribute.property` attribute, as
297          ``MyClass.someattribute.property.info``.
298
299        .. seealso::
300
301            :attr:`.SchemaItem.info`
302
303            :attr:`.MapperProperty.info`
304
305        """
306        return self.comparator.info
307
308    parent: _InternalEntityType[Any]
309    """Return an inspection instance representing the parent.
310
311    This will be either an instance of :class:`_orm.Mapper`
312    or :class:`.AliasedInsp`, depending upon the nature
313    of the parent entity which this attribute is associated
314    with.
315
316    """
317
318    expression: ColumnElement[_T_co]
319    """The SQL expression object represented by this
320    :class:`.QueryableAttribute`.
321
322    This will typically be an instance of a :class:`_sql.ColumnElement`
323    subclass representing a column expression.
324
325    """
326
327    def _memoized_attr_expression(self) -> ColumnElement[_T]:
328        annotations: _AnnotationDict
329
330        # applies only to Proxy() as used by hybrid.
331        # currently is an exception to typing rather than feeding through
332        # non-string keys.
333        # ideally Proxy() would have a separate set of methods to deal
334        # with this case.
335        entity_namespace = self._entity_namespace
336        assert isinstance(entity_namespace, HasCacheKey)
337
338        if self.key is _UNKNOWN_ATTR_KEY:
339            annotations = {"entity_namespace": entity_namespace}
340        else:
341            annotations = {
342                "proxy_key": self.key,
343                "proxy_owner": self._parententity,
344                "entity_namespace": entity_namespace,
345            }
346
347        ce = self.comparator.__clause_element__()
348        try:
349            if TYPE_CHECKING:
350                assert isinstance(ce, ColumnElement)
351            anno = ce._annotate
352        except AttributeError as ae:
353            raise exc.InvalidRequestError(
354                'When interpreting attribute "%s" as a SQL expression, '
355                "expected __clause_element__() to return "
356                "a ClauseElement object, got: %r" % (self, ce)
357            ) from ae
358        else:
359            return anno(annotations)
360
361    def _memoized_attr__propagate_attrs(self) -> _PropagateAttrsType:
362        # this suits the case in coercions where we don't actually
363        # call ``__clause_element__()`` but still need to get
364        # resolved._propagate_attrs.  See #6558.
365        return util.immutabledict(
366            {
367                "compile_state_plugin": "orm",
368                "plugin_subject": self._parentmapper,
369            }
370        )
371
372    @property
373    def _entity_namespace(self) -> _InternalEntityType[Any]:
374        return self._parententity
375
376    @property
377    def _annotations(self) -> _AnnotationDict:
378        return self.__clause_element__()._annotations
379
380    def __clause_element__(self) -> ColumnElement[_T_co]:
381        return self.expression
382
383    @property
384    def _from_objects(self) -> List[FromClause]:
385        return self.expression._from_objects
386
387    def _bulk_update_tuples(
388        self, value: Any
389    ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
390        """Return setter tuples for a bulk UPDATE."""
391
392        return self.comparator._bulk_update_tuples(value)
393
394    def adapt_to_entity(self, adapt_to_entity: AliasedInsp[Any]) -> Self:
395        assert not self._of_type
396        return self.__class__(
397            adapt_to_entity.entity,
398            self.key,
399            impl=self.impl,
400            comparator=self.comparator.adapt_to_entity(adapt_to_entity),
401            parententity=adapt_to_entity,
402        )
403
404    def of_type(self, entity: _EntityType[_T]) -> QueryableAttribute[_T]:
405        return QueryableAttribute(
406            self.class_,
407            self.key,
408            self._parententity,
409            impl=self.impl,
410            comparator=self.comparator.of_type(entity),
411            of_type=inspection.inspect(entity),
412            extra_criteria=self._extra_criteria,
413        )
414
415    def and_(
416        self, *clauses: _ColumnExpressionArgument[bool]
417    ) -> QueryableAttribute[bool]:
418        if TYPE_CHECKING:
419            assert isinstance(self.comparator, RelationshipProperty.Comparator)
420
421        exprs = tuple(
422            coercions.expect(roles.WhereHavingRole, clause)
423            for clause in util.coerce_generator_arg(clauses)
424        )
425
426        return QueryableAttribute(
427            self.class_,
428            self.key,
429            self._parententity,
430            impl=self.impl,
431            comparator=self.comparator.and_(*exprs),
432            of_type=self._of_type,
433            extra_criteria=self._extra_criteria + exprs,
434        )
435
436    def _clone(self, **kw: Any) -> QueryableAttribute[_T]:
437        return QueryableAttribute(
438            self.class_,
439            self.key,
440            self._parententity,
441            impl=self.impl,
442            comparator=self.comparator,
443            of_type=self._of_type,
444            extra_criteria=self._extra_criteria,
445        )
446
447    def label(self, name: Optional[str]) -> Label[_T_co]:
448        return self.__clause_element__().label(name)
449
450    def operate(
451        self, op: OperatorType, *other: Any, **kwargs: Any
452    ) -> ColumnElement[Any]:
453        return op(self.comparator, *other, **kwargs)  # type: ignore[no-any-return]  # noqa: E501
454
455    def reverse_operate(
456        self, op: OperatorType, other: Any, **kwargs: Any
457    ) -> ColumnElement[Any]:
458        return op(other, self.comparator, **kwargs)  # type: ignore[no-any-return]  # noqa: E501
459
460    def hasparent(
461        self, state: InstanceState[Any], optimistic: bool = False
462    ) -> bool:
463        return self.impl.hasparent(state, optimistic=optimistic) is not False
464
465    def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
466        return (self,)
467
468    def __getattr__(self, key: str) -> Any:
469        try:
470            return util.MemoizedSlots.__getattr__(self, key)
471        except AttributeError:
472            pass
473
474        try:
475            return getattr(self.comparator, key)
476        except AttributeError as err:
477            raise AttributeError(
478                "Neither %r object nor %r object associated with %s "
479                "has an attribute %r"
480                % (
481                    type(self).__name__,
482                    type(self.comparator).__name__,
483                    self,
484                    key,
485                )
486            ) from err
487
488    def __str__(self) -> str:
489        return f"{self.class_.__name__}.{self.key}"
490
491    def _memoized_attr_property(self) -> Optional[MapperProperty[Any]]:
492        return self.comparator.property
493
494
495def _queryable_attribute_unreduce(
496    key: str,
497    mapped_class: Type[_O],
498    parententity: _InternalEntityType[_O],
499    entity: _ExternalEntityType[Any],
500) -> Any:
501    # this method is only used in terms of the
502    # sqlalchemy.ext.serializer extension
503    if insp_is_aliased_class(parententity):
504        return entity._get_from_serialized(key, mapped_class, parententity)
505    else:
506        return getattr(entity, key)
507
508
509class InstrumentedAttribute(QueryableAttribute[_T_co]):
510    """Class bound instrumented attribute which adds basic
511    :term:`descriptor` methods.
512
513    See :class:`.QueryableAttribute` for a description of most features.
514
515
516    """
517
518    __slots__ = ()
519
520    inherit_cache = True
521    """:meta private:"""
522
523    # hack to make __doc__ writeable on instances of
524    # InstrumentedAttribute, while still keeping classlevel
525    # __doc__ correct
526
527    @util.rw_hybridproperty
528    def __doc__(self) -> Optional[str]:
529        return self._doc
530
531    @__doc__.setter  # type: ignore
532    def __doc__(self, value: Optional[str]) -> None:
533        self._doc = value
534
535    @__doc__.classlevel  # type: ignore
536    def __doc__(cls) -> Optional[str]:
537        return super().__doc__
538
539    def __set__(self, instance: object, value: Any) -> None:
540        self.impl.set(
541            instance_state(instance), instance_dict(instance), value, None
542        )
543
544    def __delete__(self, instance: object) -> None:
545        self.impl.delete(instance_state(instance), instance_dict(instance))
546
547    @overload
548    def __get__(
549        self, instance: None, owner: Any
550    ) -> InstrumentedAttribute[_T_co]: ...
551
552    @overload
553    def __get__(self, instance: object, owner: Any) -> _T_co: ...
554
555    def __get__(
556        self, instance: Optional[object], owner: Any
557    ) -> Union[InstrumentedAttribute[_T_co], _T_co]:
558        if instance is None:
559            return self
560
561        dict_ = instance_dict(instance)
562        if self.impl.supports_population and self.key in dict_:
563            return dict_[self.key]  # type: ignore[no-any-return]
564        else:
565            try:
566                state = instance_state(instance)
567            except AttributeError as err:
568                raise orm_exc.UnmappedInstanceError(instance) from err
569            return self.impl.get(state, dict_)  # type: ignore[no-any-return]
570
571
572@dataclasses.dataclass(frozen=True)
573class AdHocHasEntityNamespace(HasCacheKey):
574    _traverse_internals: ClassVar[_TraverseInternalsType] = [
575        ("_entity_namespace", InternalTraversal.dp_has_cache_key),
576    ]
577
578    # py37 compat, no slots=True on dataclass
579    __slots__ = ("_entity_namespace",)
580    _entity_namespace: _InternalEntityType[Any]
581    is_mapper: ClassVar[bool] = False
582    is_aliased_class: ClassVar[bool] = False
583
584    @property
585    def entity_namespace(self):
586        return self._entity_namespace.entity_namespace
587
588
589def create_proxied_attribute(
590    descriptor: Any,
591) -> Callable[..., QueryableAttribute[Any]]:
592    """Create an QueryableAttribute / user descriptor hybrid.
593
594    Returns a new QueryableAttribute type that delegates descriptor
595    behavior and getattr() to the given descriptor.
596    """
597
598    # TODO: can move this to descriptor_props if the need for this
599    # function is removed from ext/hybrid.py
600
601    class Proxy(QueryableAttribute[_T_co]):
602        """Presents the :class:`.QueryableAttribute` interface as a
603        proxy on top of a Python descriptor / :class:`.PropComparator`
604        combination.
605
606        """
607
608        _extra_criteria = ()
609
610        # the attribute error catches inside of __getattr__ basically create a
611        # singularity if you try putting slots on this too
612        # __slots__ = ("descriptor", "original_property", "_comparator")
613
614        def __init__(
615            self,
616            class_: _ExternalEntityType[Any],
617            key: str,
618            descriptor: Any,
619            comparator: interfaces.PropComparator[_T_co],
620            adapt_to_entity: Optional[AliasedInsp[Any]] = None,
621            doc: Optional[str] = None,
622            original_property: Optional[QueryableAttribute[_T_co]] = None,
623        ):
624            self.class_ = class_
625            self.key = key
626            self.descriptor = descriptor
627            self.original_property = original_property
628            self._comparator = comparator
629            self._adapt_to_entity = adapt_to_entity
630            self._doc = self.__doc__ = doc
631
632        @property
633        def _parententity(self):  # type: ignore[override]
634            return inspection.inspect(self.class_, raiseerr=False)
635
636        @property
637        def parent(self):  # type: ignore[override]
638            return inspection.inspect(self.class_, raiseerr=False)
639
640        _is_internal_proxy = True
641
642        _cache_key_traversal = [
643            ("key", visitors.ExtendedInternalTraversal.dp_string),
644            ("_parententity", visitors.ExtendedInternalTraversal.dp_multi),
645        ]
646
647        def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
648            prop = self.original_property
649            if prop is None:
650                return ()
651            else:
652                return prop._column_strategy_attrs()
653
654        @property
655        def _impl_uses_objects(self):
656            return (
657                self.original_property is not None
658                and getattr(self.class_, self.key).impl.uses_objects
659            )
660
661        @property
662        def _entity_namespace(self):
663            if hasattr(self._comparator, "_parententity"):
664                return self._comparator._parententity
665            else:
666                # used by hybrid attributes which try to remain
667                # agnostic of any ORM concepts like mappers
668                return AdHocHasEntityNamespace(self._parententity)
669
670        @property
671        def property(self):
672            return self.comparator.property
673
674        @util.memoized_property
675        def comparator(self):
676            if callable(self._comparator):
677                self._comparator = self._comparator()
678            if self._adapt_to_entity:
679                self._comparator = self._comparator.adapt_to_entity(
680                    self._adapt_to_entity
681                )
682            return self._comparator
683
684        def adapt_to_entity(self, adapt_to_entity):
685            return self.__class__(
686                adapt_to_entity.entity,
687                self.key,
688                self.descriptor,
689                self._comparator,
690                adapt_to_entity,
691            )
692
693        def _clone(self, **kw):
694            return self.__class__(
695                self.class_,
696                self.key,
697                self.descriptor,
698                self._comparator,
699                adapt_to_entity=self._adapt_to_entity,
700                original_property=self.original_property,
701            )
702
703        def __get__(self, instance, owner):
704            retval = self.descriptor.__get__(instance, owner)
705            # detect if this is a plain Python @property, which just returns
706            # itself for class level access.  If so, then return us.
707            # Otherwise, return the object returned by the descriptor.
708            if retval is self.descriptor and instance is None:
709                return self
710            else:
711                return retval
712
713        def __str__(self) -> str:
714            return f"{self.class_.__name__}.{self.key}"
715
716        def __getattr__(self, attribute):
717            """Delegate __getattr__ to the original descriptor and/or
718            comparator."""
719
720            # this is unfortunately very complicated, and is easily prone
721            # to recursion overflows when implementations of related
722            # __getattr__ schemes are changed
723
724            try:
725                return util.MemoizedSlots.__getattr__(self, attribute)
726            except AttributeError:
727                pass
728
729            try:
730                return getattr(descriptor, attribute)
731            except AttributeError as err:
732                if attribute == "comparator":
733                    raise AttributeError("comparator") from err
734                try:
735                    # comparator itself might be unreachable
736                    comparator = self.comparator
737                except AttributeError as err2:
738                    raise AttributeError(
739                        "Neither %r object nor unconfigured comparator "
740                        "object associated with %s has an attribute %r"
741                        % (type(descriptor).__name__, self, attribute)
742                    ) from err2
743                else:
744                    try:
745                        return getattr(comparator, attribute)
746                    except AttributeError as err3:
747                        raise AttributeError(
748                            "Neither %r object nor %r object "
749                            "associated with %s has an attribute %r"
750                            % (
751                                type(descriptor).__name__,
752                                type(comparator).__name__,
753                                self,
754                                attribute,
755                            )
756                        ) from err3
757
758    Proxy.__name__ = type(descriptor).__name__ + "Proxy"
759
760    util.monkeypatch_proxied_specials(
761        Proxy, type(descriptor), name="descriptor", from_instance=descriptor
762    )
763    return Proxy
764
765
766OP_REMOVE = util.symbol("REMOVE")
767OP_APPEND = util.symbol("APPEND")
768OP_REPLACE = util.symbol("REPLACE")
769OP_BULK_REPLACE = util.symbol("BULK_REPLACE")
770OP_MODIFIED = util.symbol("MODIFIED")
771
772
773class AttributeEventToken:
774    """A token propagated throughout the course of a chain of attribute
775    events.
776
777    Serves as an indicator of the source of the event and also provides
778    a means of controlling propagation across a chain of attribute
779    operations.
780
781    The :class:`.Event` object is sent as the ``initiator`` argument
782    when dealing with events such as :meth:`.AttributeEvents.append`,
783    :meth:`.AttributeEvents.set`,
784    and :meth:`.AttributeEvents.remove`.
785
786    The :class:`.Event` object is currently interpreted by the backref
787    event handlers, and is used to control the propagation of operations
788    across two mutually-dependent attributes.
789
790    .. versionchanged:: 2.0  Changed the name from ``AttributeEvent``
791       to ``AttributeEventToken``.
792
793    :attribute impl: The :class:`.AttributeImpl` which is the current event
794     initiator.
795
796    :attribute op: The symbol :attr:`.OP_APPEND`, :attr:`.OP_REMOVE`,
797     :attr:`.OP_REPLACE`, or :attr:`.OP_BULK_REPLACE`, indicating the
798     source operation.
799
800    """
801
802    __slots__ = "impl", "op", "parent_token"
803
804    def __init__(self, attribute_impl: AttributeImpl, op: util.symbol):
805        self.impl = attribute_impl
806        self.op = op
807        self.parent_token = self.impl.parent_token
808
809    def __eq__(self, other):
810        return (
811            isinstance(other, AttributeEventToken)
812            and other.impl is self.impl
813            and other.op == self.op
814        )
815
816    @property
817    def key(self):
818        return self.impl.key
819
820    def hasparent(self, state):
821        return self.impl.hasparent(state)
822
823
824AttributeEvent = AttributeEventToken  # legacy
825Event = AttributeEventToken  # legacy
826
827
828class AttributeImpl:
829    """internal implementation for instrumented attributes."""
830
831    collection: bool
832    default_accepts_scalar_loader: bool
833    uses_objects: bool
834    supports_population: bool
835    dynamic: bool
836
837    _is_has_collection_adapter = False
838
839    _replace_token: AttributeEventToken
840    _remove_token: AttributeEventToken
841    _append_token: AttributeEventToken
842
843    def __init__(
844        self,
845        class_: _ExternalEntityType[_O],
846        key: str,
847        callable_: Optional[_LoaderCallable],
848        dispatch: _Dispatch[QueryableAttribute[Any]],
849        trackparent: bool = False,
850        compare_function: Optional[Callable[..., bool]] = None,
851        active_history: bool = False,
852        parent_token: Optional[AttributeEventToken] = None,
853        load_on_unexpire: bool = True,
854        send_modified_events: bool = True,
855        accepts_scalar_loader: Optional[bool] = None,
856        **kwargs: Any,
857    ):
858        r"""Construct an AttributeImpl.
859
860        :param \class_: associated class
861
862        :param key: string name of the attribute
863
864        :param \callable_:
865          optional function which generates a callable based on a parent
866          instance, which produces the "default" values for a scalar or
867          collection attribute when it's first accessed, if not present
868          already.
869
870        :param trackparent:
871          if True, attempt to track if an instance has a parent attached
872          to it via this attribute.
873
874        :param compare_function:
875          a function that compares two values which are normally
876          assignable to this attribute.
877
878        :param active_history:
879          indicates that get_history() should always return the "old" value,
880          even if it means executing a lazy callable upon attribute change.
881
882        :param parent_token:
883          Usually references the MapperProperty, used as a key for
884          the hasparent() function to identify an "owning" attribute.
885          Allows multiple AttributeImpls to all match a single
886          owner attribute.
887
888        :param load_on_unexpire:
889          if False, don't include this attribute in a load-on-expired
890          operation, i.e. the "expired_attribute_loader" process.
891          The attribute can still be in the "expired" list and be
892          considered to be "expired".   Previously, this flag was called
893          "expire_missing" and is only used by a deferred column
894          attribute.
895
896        :param send_modified_events:
897          if False, the InstanceState._modified_event method will have no
898          effect; this means the attribute will never show up as changed in a
899          history entry.
900
901        """
902        self.class_ = class_
903        self.key = key
904        self.callable_ = callable_
905        self.dispatch = dispatch
906        self.trackparent = trackparent
907        self.parent_token = parent_token or self
908        self.send_modified_events = send_modified_events
909        if compare_function is None:
910            self.is_equal = operator.eq
911        else:
912            self.is_equal = compare_function
913
914        if accepts_scalar_loader is not None:
915            self.accepts_scalar_loader = accepts_scalar_loader
916        else:
917            self.accepts_scalar_loader = self.default_accepts_scalar_loader
918
919        _deferred_history = kwargs.pop("_deferred_history", False)
920        self._deferred_history = _deferred_history
921
922        if active_history:
923            self.dispatch._active_history = True
924
925        self.load_on_unexpire = load_on_unexpire
926        self._modified_token = AttributeEventToken(self, OP_MODIFIED)
927
928    __slots__ = (
929        "class_",
930        "key",
931        "callable_",
932        "dispatch",
933        "trackparent",
934        "parent_token",
935        "send_modified_events",
936        "is_equal",
937        "load_on_unexpire",
938        "_modified_token",
939        "accepts_scalar_loader",
940        "_deferred_history",
941    )
942
943    def __str__(self) -> str:
944        return f"{self.class_.__name__}.{self.key}"
945
946    def _get_active_history(self):
947        """Backwards compat for impl.active_history"""
948
949        return self.dispatch._active_history
950
951    def _set_active_history(self, value):
952        self.dispatch._active_history = value
953
954    active_history = property(_get_active_history, _set_active_history)
955
956    def hasparent(
957        self, state: InstanceState[Any], optimistic: bool = False
958    ) -> bool:
959        """Return the boolean value of a `hasparent` flag attached to
960        the given state.
961
962        The `optimistic` flag determines what the default return value
963        should be if no `hasparent` flag can be located.
964
965        As this function is used to determine if an instance is an
966        *orphan*, instances that were loaded from storage should be
967        assumed to not be orphans, until a True/False value for this
968        flag is set.
969
970        An instance attribute that is loaded by a callable function
971        will also not have a `hasparent` flag.
972
973        """
974        msg = "This AttributeImpl is not configured to track parents."
975        assert self.trackparent, msg
976
977        return (
978            state.parents.get(id(self.parent_token), optimistic) is not False
979        )
980
981    def sethasparent(
982        self,
983        state: InstanceState[Any],
984        parent_state: InstanceState[Any],
985        value: bool,
986    ) -> None:
987        """Set a boolean flag on the given item corresponding to
988        whether or not it is attached to a parent object via the
989        attribute represented by this ``InstrumentedAttribute``.
990
991        """
992        msg = "This AttributeImpl is not configured to track parents."
993        assert self.trackparent, msg
994
995        id_ = id(self.parent_token)
996        if value:
997            state.parents[id_] = parent_state
998        else:
999            if id_ in state.parents:
1000                last_parent = state.parents[id_]
1001
1002                if (
1003                    last_parent is not False
1004                    and last_parent.key != parent_state.key
1005                ):
1006                    if last_parent.obj() is None:
1007                        raise orm_exc.StaleDataError(
1008                            "Removing state %s from parent "
1009                            "state %s along attribute '%s', "
1010                            "but the parent record "
1011                            "has gone stale, can't be sure this "
1012                            "is the most recent parent."
1013                            % (
1014                                state_str(state),
1015                                state_str(parent_state),
1016                                self.key,
1017                            )
1018                        )
1019
1020                    return
1021
1022            state.parents[id_] = False
1023
1024    def get_history(
1025        self,
1026        state: InstanceState[Any],
1027        dict_: _InstanceDict,
1028        passive: PassiveFlag = PASSIVE_OFF,
1029    ) -> History:
1030        raise NotImplementedError()
1031
1032    def get_all_pending(
1033        self,
1034        state: InstanceState[Any],
1035        dict_: _InstanceDict,
1036        passive: PassiveFlag = PASSIVE_NO_INITIALIZE,
1037    ) -> _AllPendingType:
1038        """Return a list of tuples of (state, obj)
1039        for all objects in this attribute's current state
1040        + history.
1041
1042        Only applies to object-based attributes.
1043
1044        This is an inlining of existing functionality
1045        which roughly corresponds to:
1046
1047            get_state_history(
1048                        state,
1049                        key,
1050                        passive=PASSIVE_NO_INITIALIZE).sum()
1051
1052        """
1053        raise NotImplementedError()
1054
1055    def _default_value(
1056        self, state: InstanceState[Any], dict_: _InstanceDict
1057    ) -> Any:
1058        """Produce an empty value for an uninitialized scalar attribute."""
1059
1060        assert self.key not in dict_, (
1061            "_default_value should only be invoked for an "
1062            "uninitialized or expired attribute"
1063        )
1064
1065        value = None
1066        for fn in self.dispatch.init_scalar:
1067            ret = fn(state, value, dict_)
1068            if ret is not ATTR_EMPTY:
1069                value = ret
1070
1071        return value
1072
1073    def get(
1074        self,
1075        state: InstanceState[Any],
1076        dict_: _InstanceDict,
1077        passive: PassiveFlag = PASSIVE_OFF,
1078    ) -> Any:
1079        """Retrieve a value from the given object.
1080        If a callable is assembled on this object's attribute, and
1081        passive is False, the callable will be executed and the
1082        resulting value will be set as the new value for this attribute.
1083        """
1084        if self.key in dict_:
1085            return dict_[self.key]
1086        else:
1087            # if history present, don't load
1088            key = self.key
1089            if (
1090                key not in state.committed_state
1091                or state.committed_state[key] is NO_VALUE
1092            ):
1093                if not passive & CALLABLES_OK:
1094                    return PASSIVE_NO_RESULT
1095
1096                value = self._fire_loader_callables(state, key, passive)
1097
1098                if value is PASSIVE_NO_RESULT or value is NO_VALUE:
1099                    return value
1100                elif value is ATTR_WAS_SET:
1101                    try:
1102                        return dict_[key]
1103                    except KeyError as err:
1104                        # TODO: no test coverage here.
1105                        raise KeyError(
1106                            "Deferred loader for attribute "
1107                            "%r failed to populate "
1108                            "correctly" % key
1109                        ) from err
1110                elif value is not ATTR_EMPTY:
1111                    return self.set_committed_value(state, dict_, value)
1112
1113            if not passive & INIT_OK:
1114                return NO_VALUE
1115            else:
1116                return self._default_value(state, dict_)
1117
1118    def _fire_loader_callables(
1119        self, state: InstanceState[Any], key: str, passive: PassiveFlag
1120    ) -> Any:
1121        if (
1122            self.accepts_scalar_loader
1123            and self.load_on_unexpire
1124            and key in state.expired_attributes
1125        ):
1126            return state._load_expired(state, passive)
1127        elif key in state.callables:
1128            callable_ = state.callables[key]
1129            return callable_(state, passive)
1130        elif self.callable_:
1131            return self.callable_(state, passive)
1132        else:
1133            return ATTR_EMPTY
1134
1135    def append(
1136        self,
1137        state: InstanceState[Any],
1138        dict_: _InstanceDict,
1139        value: Any,
1140        initiator: Optional[AttributeEventToken],
1141        passive: PassiveFlag = PASSIVE_OFF,
1142    ) -> None:
1143        self.set(state, dict_, value, initiator, passive=passive)
1144
1145    def remove(
1146        self,
1147        state: InstanceState[Any],
1148        dict_: _InstanceDict,
1149        value: Any,
1150        initiator: Optional[AttributeEventToken],
1151        passive: PassiveFlag = PASSIVE_OFF,
1152    ) -> None:
1153        self.set(
1154            state, dict_, None, initiator, passive=passive, check_old=value
1155        )
1156
1157    def pop(
1158        self,
1159        state: InstanceState[Any],
1160        dict_: _InstanceDict,
1161        value: Any,
1162        initiator: Optional[AttributeEventToken],
1163        passive: PassiveFlag = PASSIVE_OFF,
1164    ) -> None:
1165        self.set(
1166            state,
1167            dict_,
1168            None,
1169            initiator,
1170            passive=passive,
1171            check_old=value,
1172            pop=True,
1173        )
1174
1175    def set(
1176        self,
1177        state: InstanceState[Any],
1178        dict_: _InstanceDict,
1179        value: Any,
1180        initiator: Optional[AttributeEventToken] = None,
1181        passive: PassiveFlag = PASSIVE_OFF,
1182        check_old: Any = None,
1183        pop: bool = False,
1184    ) -> None:
1185        raise NotImplementedError()
1186
1187    def delete(self, state: InstanceState[Any], dict_: _InstanceDict) -> None:
1188        raise NotImplementedError()
1189
1190    def get_committed_value(
1191        self,
1192        state: InstanceState[Any],
1193        dict_: _InstanceDict,
1194        passive: PassiveFlag = PASSIVE_OFF,
1195    ) -> Any:
1196        """return the unchanged value of this attribute"""
1197
1198        if self.key in state.committed_state:
1199            value = state.committed_state[self.key]
1200            if value is NO_VALUE:

Showing the first 1,200 of 2846 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai