Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
attributes.py2836 linesDownload Raw Back to orm
1# orm/attributes.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# 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[Any]) -> 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 __getattr__(self, key: str) -> Any:
466        try:
467            return util.MemoizedSlots.__getattr__(self, key)
468        except AttributeError:
469            pass
470
471        try:
472            return getattr(self.comparator, key)
473        except AttributeError as err:
474            raise AttributeError(
475                "Neither %r object nor %r object associated with %s "
476                "has an attribute %r"
477                % (
478                    type(self).__name__,
479                    type(self.comparator).__name__,
480                    self,
481                    key,
482                )
483            ) from err
484
485    def __str__(self) -> str:
486        return f"{self.class_.__name__}.{self.key}"
487
488    def _memoized_attr_property(self) -> Optional[MapperProperty[Any]]:
489        return self.comparator.property
490
491
492def _queryable_attribute_unreduce(
493    key: str,
494    mapped_class: Type[_O],
495    parententity: _InternalEntityType[_O],
496    entity: _ExternalEntityType[Any],
497) -> Any:
498    # this method is only used in terms of the
499    # sqlalchemy.ext.serializer extension
500    if insp_is_aliased_class(parententity):
501        return entity._get_from_serialized(key, mapped_class, parententity)
502    else:
503        return getattr(entity, key)
504
505
506class InstrumentedAttribute(QueryableAttribute[_T_co]):
507    """Class bound instrumented attribute which adds basic
508    :term:`descriptor` methods.
509
510    See :class:`.QueryableAttribute` for a description of most features.
511
512
513    """
514
515    __slots__ = ()
516
517    inherit_cache = True
518    """:meta private:"""
519
520    # hack to make __doc__ writeable on instances of
521    # InstrumentedAttribute, while still keeping classlevel
522    # __doc__ correct
523
524    @util.rw_hybridproperty
525    def __doc__(self) -> Optional[str]:
526        return self._doc
527
528    @__doc__.setter  # type: ignore
529    def __doc__(self, value: Optional[str]) -> None:
530        self._doc = value
531
532    @__doc__.classlevel  # type: ignore
533    def __doc__(cls) -> Optional[str]:
534        return super().__doc__
535
536    def __set__(self, instance: object, value: Any) -> None:
537        self.impl.set(
538            instance_state(instance), instance_dict(instance), value, None
539        )
540
541    def __delete__(self, instance: object) -> None:
542        self.impl.delete(instance_state(instance), instance_dict(instance))
543
544    @overload
545    def __get__(
546        self, instance: None, owner: Any
547    ) -> InstrumentedAttribute[_T_co]: ...
548
549    @overload
550    def __get__(self, instance: object, owner: Any) -> _T_co: ...
551
552    def __get__(
553        self, instance: Optional[object], owner: Any
554    ) -> Union[InstrumentedAttribute[_T_co], _T_co]:
555        if instance is None:
556            return self
557
558        dict_ = instance_dict(instance)
559        if self.impl.supports_population and self.key in dict_:
560            return dict_[self.key]  # type: ignore[no-any-return]
561        else:
562            try:
563                state = instance_state(instance)
564            except AttributeError as err:
565                raise orm_exc.UnmappedInstanceError(instance) from err
566            return self.impl.get(state, dict_)  # type: ignore[no-any-return]
567
568
569@dataclasses.dataclass(frozen=True)
570class AdHocHasEntityNamespace(HasCacheKey):
571    _traverse_internals: ClassVar[_TraverseInternalsType] = [
572        ("_entity_namespace", InternalTraversal.dp_has_cache_key),
573    ]
574
575    # py37 compat, no slots=True on dataclass
576    __slots__ = ("_entity_namespace",)
577    _entity_namespace: _InternalEntityType[Any]
578    is_mapper: ClassVar[bool] = False
579    is_aliased_class: ClassVar[bool] = False
580
581    @property
582    def entity_namespace(self):
583        return self._entity_namespace.entity_namespace
584
585
586def create_proxied_attribute(
587    descriptor: Any,
588) -> Callable[..., QueryableAttribute[Any]]:
589    """Create an QueryableAttribute / user descriptor hybrid.
590
591    Returns a new QueryableAttribute type that delegates descriptor
592    behavior and getattr() to the given descriptor.
593    """
594
595    # TODO: can move this to descriptor_props if the need for this
596    # function is removed from ext/hybrid.py
597
598    class Proxy(QueryableAttribute[Any]):
599        """Presents the :class:`.QueryableAttribute` interface as a
600        proxy on top of a Python descriptor / :class:`.PropComparator`
601        combination.
602
603        """
604
605        _extra_criteria = ()
606
607        # the attribute error catches inside of __getattr__ basically create a
608        # singularity if you try putting slots on this too
609        # __slots__ = ("descriptor", "original_property", "_comparator")
610
611        def __init__(
612            self,
613            class_,
614            key,
615            descriptor,
616            comparator,
617            adapt_to_entity=None,
618            doc=None,
619            original_property=None,
620        ):
621            self.class_ = class_
622            self.key = key
623            self.descriptor = descriptor
624            self.original_property = original_property
625            self._comparator = comparator
626            self._adapt_to_entity = adapt_to_entity
627            self._doc = self.__doc__ = doc
628
629        @property
630        def _parententity(self):
631            return inspection.inspect(self.class_, raiseerr=False)
632
633        @property
634        def parent(self):
635            return inspection.inspect(self.class_, raiseerr=False)
636
637        _is_internal_proxy = True
638
639        _cache_key_traversal = [
640            ("key", visitors.ExtendedInternalTraversal.dp_string),
641            ("_parententity", visitors.ExtendedInternalTraversal.dp_multi),
642        ]
643
644        @property
645        def _impl_uses_objects(self):
646            return (
647                self.original_property is not None
648                and getattr(self.class_, self.key).impl.uses_objects
649            )
650
651        @property
652        def _entity_namespace(self):
653            if hasattr(self._comparator, "_parententity"):
654                return self._comparator._parententity
655            else:
656                # used by hybrid attributes which try to remain
657                # agnostic of any ORM concepts like mappers
658                return AdHocHasEntityNamespace(self._parententity)
659
660        @property
661        def property(self):
662            return self.comparator.property
663
664        @util.memoized_property
665        def comparator(self):
666            if callable(self._comparator):
667                self._comparator = self._comparator()
668            if self._adapt_to_entity:
669                self._comparator = self._comparator.adapt_to_entity(
670                    self._adapt_to_entity
671                )
672            return self._comparator
673
674        def adapt_to_entity(self, adapt_to_entity):
675            return self.__class__(
676                adapt_to_entity.entity,
677                self.key,
678                self.descriptor,
679                self._comparator,
680                adapt_to_entity,
681            )
682
683        def _clone(self, **kw):
684            return self.__class__(
685                self.class_,
686                self.key,
687                self.descriptor,
688                self._comparator,
689                adapt_to_entity=self._adapt_to_entity,
690                original_property=self.original_property,
691            )
692
693        def __get__(self, instance, owner):
694            retval = self.descriptor.__get__(instance, owner)
695            # detect if this is a plain Python @property, which just returns
696            # itself for class level access.  If so, then return us.
697            # Otherwise, return the object returned by the descriptor.
698            if retval is self.descriptor and instance is None:
699                return self
700            else:
701                return retval
702
703        def __str__(self) -> str:
704            return f"{self.class_.__name__}.{self.key}"
705
706        def __getattr__(self, attribute):
707            """Delegate __getattr__ to the original descriptor and/or
708            comparator."""
709
710            # this is unfortunately very complicated, and is easily prone
711            # to recursion overflows when implementations of related
712            # __getattr__ schemes are changed
713
714            try:
715                return util.MemoizedSlots.__getattr__(self, attribute)
716            except AttributeError:
717                pass
718
719            try:
720                return getattr(descriptor, attribute)
721            except AttributeError as err:
722                if attribute == "comparator":
723                    raise AttributeError("comparator") from err
724                try:
725                    # comparator itself might be unreachable
726                    comparator = self.comparator
727                except AttributeError as err2:
728                    raise AttributeError(
729                        "Neither %r object nor unconfigured comparator "
730                        "object associated with %s has an attribute %r"
731                        % (type(descriptor).__name__, self, attribute)
732                    ) from err2
733                else:
734                    try:
735                        return getattr(comparator, attribute)
736                    except AttributeError as err3:
737                        raise AttributeError(
738                            "Neither %r object nor %r object "
739                            "associated with %s has an attribute %r"
740                            % (
741                                type(descriptor).__name__,
742                                type(comparator).__name__,
743                                self,
744                                attribute,
745                            )
746                        ) from err3
747
748    Proxy.__name__ = type(descriptor).__name__ + "Proxy"
749
750    util.monkeypatch_proxied_specials(
751        Proxy, type(descriptor), name="descriptor", from_instance=descriptor
752    )
753    return Proxy
754
755
756OP_REMOVE = util.symbol("REMOVE")
757OP_APPEND = util.symbol("APPEND")
758OP_REPLACE = util.symbol("REPLACE")
759OP_BULK_REPLACE = util.symbol("BULK_REPLACE")
760OP_MODIFIED = util.symbol("MODIFIED")
761
762
763class AttributeEventToken:
764    """A token propagated throughout the course of a chain of attribute
765    events.
766
767    Serves as an indicator of the source of the event and also provides
768    a means of controlling propagation across a chain of attribute
769    operations.
770
771    The :class:`.Event` object is sent as the ``initiator`` argument
772    when dealing with events such as :meth:`.AttributeEvents.append`,
773    :meth:`.AttributeEvents.set`,
774    and :meth:`.AttributeEvents.remove`.
775
776    The :class:`.Event` object is currently interpreted by the backref
777    event handlers, and is used to control the propagation of operations
778    across two mutually-dependent attributes.
779
780    .. versionchanged:: 2.0  Changed the name from ``AttributeEvent``
781       to ``AttributeEventToken``.
782
783    :attribute impl: The :class:`.AttributeImpl` which is the current event
784     initiator.
785
786    :attribute op: The symbol :attr:`.OP_APPEND`, :attr:`.OP_REMOVE`,
787     :attr:`.OP_REPLACE`, or :attr:`.OP_BULK_REPLACE`, indicating the
788     source operation.
789
790    """
791
792    __slots__ = "impl", "op", "parent_token"
793
794    def __init__(self, attribute_impl: AttributeImpl, op: util.symbol):
795        self.impl = attribute_impl
796        self.op = op
797        self.parent_token = self.impl.parent_token
798
799    def __eq__(self, other):
800        return (
801            isinstance(other, AttributeEventToken)
802            and other.impl is self.impl
803            and other.op == self.op
804        )
805
806    @property
807    def key(self):
808        return self.impl.key
809
810    def hasparent(self, state):
811        return self.impl.hasparent(state)
812
813
814AttributeEvent = AttributeEventToken  # legacy
815Event = AttributeEventToken  # legacy
816
817
818class AttributeImpl:
819    """internal implementation for instrumented attributes."""
820
821    collection: bool
822    default_accepts_scalar_loader: bool
823    uses_objects: bool
824    supports_population: bool
825    dynamic: bool
826
827    _is_has_collection_adapter = False
828
829    _replace_token: AttributeEventToken
830    _remove_token: AttributeEventToken
831    _append_token: AttributeEventToken
832
833    def __init__(
834        self,
835        class_: _ExternalEntityType[_O],
836        key: str,
837        callable_: Optional[_LoaderCallable],
838        dispatch: _Dispatch[QueryableAttribute[Any]],
839        trackparent: bool = False,
840        compare_function: Optional[Callable[..., bool]] = None,
841        active_history: bool = False,
842        parent_token: Optional[AttributeEventToken] = None,
843        load_on_unexpire: bool = True,
844        send_modified_events: bool = True,
845        accepts_scalar_loader: Optional[bool] = None,
846        **kwargs: Any,
847    ):
848        r"""Construct an AttributeImpl.
849
850        :param \class_: associated class
851
852        :param key: string name of the attribute
853
854        :param \callable_:
855          optional function which generates a callable based on a parent
856          instance, which produces the "default" values for a scalar or
857          collection attribute when it's first accessed, if not present
858          already.
859
860        :param trackparent:
861          if True, attempt to track if an instance has a parent attached
862          to it via this attribute.
863
864        :param compare_function:
865          a function that compares two values which are normally
866          assignable to this attribute.
867
868        :param active_history:
869          indicates that get_history() should always return the "old" value,
870          even if it means executing a lazy callable upon attribute change.
871
872        :param parent_token:
873          Usually references the MapperProperty, used as a key for
874          the hasparent() function to identify an "owning" attribute.
875          Allows multiple AttributeImpls to all match a single
876          owner attribute.
877
878        :param load_on_unexpire:
879          if False, don't include this attribute in a load-on-expired
880          operation, i.e. the "expired_attribute_loader" process.
881          The attribute can still be in the "expired" list and be
882          considered to be "expired".   Previously, this flag was called
883          "expire_missing" and is only used by a deferred column
884          attribute.
885
886        :param send_modified_events:
887          if False, the InstanceState._modified_event method will have no
888          effect; this means the attribute will never show up as changed in a
889          history entry.
890
891        """
892        self.class_ = class_
893        self.key = key
894        self.callable_ = callable_
895        self.dispatch = dispatch
896        self.trackparent = trackparent
897        self.parent_token = parent_token or self
898        self.send_modified_events = send_modified_events
899        if compare_function is None:
900            self.is_equal = operator.eq
901        else:
902            self.is_equal = compare_function
903
904        if accepts_scalar_loader is not None:
905            self.accepts_scalar_loader = accepts_scalar_loader
906        else:
907            self.accepts_scalar_loader = self.default_accepts_scalar_loader
908
909        _deferred_history = kwargs.pop("_deferred_history", False)
910        self._deferred_history = _deferred_history
911
912        if active_history:
913            self.dispatch._active_history = True
914
915        self.load_on_unexpire = load_on_unexpire
916        self._modified_token = AttributeEventToken(self, OP_MODIFIED)
917
918    __slots__ = (
919        "class_",
920        "key",
921        "callable_",
922        "dispatch",
923        "trackparent",
924        "parent_token",
925        "send_modified_events",
926        "is_equal",
927        "load_on_unexpire",
928        "_modified_token",
929        "accepts_scalar_loader",
930        "_deferred_history",
931    )
932
933    def __str__(self) -> str:
934        return f"{self.class_.__name__}.{self.key}"
935
936    def _get_active_history(self):
937        """Backwards compat for impl.active_history"""
938
939        return self.dispatch._active_history
940
941    def _set_active_history(self, value):
942        self.dispatch._active_history = value
943
944    active_history = property(_get_active_history, _set_active_history)
945
946    def hasparent(
947        self, state: InstanceState[Any], optimistic: bool = False
948    ) -> bool:
949        """Return the boolean value of a `hasparent` flag attached to
950        the given state.
951
952        The `optimistic` flag determines what the default return value
953        should be if no `hasparent` flag can be located.
954
955        As this function is used to determine if an instance is an
956        *orphan*, instances that were loaded from storage should be
957        assumed to not be orphans, until a True/False value for this
958        flag is set.
959
960        An instance attribute that is loaded by a callable function
961        will also not have a `hasparent` flag.
962
963        """
964        msg = "This AttributeImpl is not configured to track parents."
965        assert self.trackparent, msg
966
967        return (
968            state.parents.get(id(self.parent_token), optimistic) is not False
969        )
970
971    def sethasparent(
972        self,
973        state: InstanceState[Any],
974        parent_state: InstanceState[Any],
975        value: bool,
976    ) -> None:
977        """Set a boolean flag on the given item corresponding to
978        whether or not it is attached to a parent object via the
979        attribute represented by this ``InstrumentedAttribute``.
980
981        """
982        msg = "This AttributeImpl is not configured to track parents."
983        assert self.trackparent, msg
984
985        id_ = id(self.parent_token)
986        if value:
987            state.parents[id_] = parent_state
988        else:
989            if id_ in state.parents:
990                last_parent = state.parents[id_]
991
992                if (
993                    last_parent is not False
994                    and last_parent.key != parent_state.key
995                ):
996                    if last_parent.obj() is None:
997                        raise orm_exc.StaleDataError(
998                            "Removing state %s from parent "
999                            "state %s along attribute '%s', "
1000                            "but the parent record "
1001                            "has gone stale, can't be sure this "
1002                            "is the most recent parent."
1003                            % (
1004                                state_str(state),
1005                                state_str(parent_state),
1006                                self.key,
1007                            )
1008                        )
1009
1010                    return
1011
1012            state.parents[id_] = False
1013
1014    def get_history(
1015        self,
1016        state: InstanceState[Any],
1017        dict_: _InstanceDict,
1018        passive: PassiveFlag = PASSIVE_OFF,
1019    ) -> History:
1020        raise NotImplementedError()
1021
1022    def get_all_pending(
1023        self,
1024        state: InstanceState[Any],
1025        dict_: _InstanceDict,
1026        passive: PassiveFlag = PASSIVE_NO_INITIALIZE,
1027    ) -> _AllPendingType:
1028        """Return a list of tuples of (state, obj)
1029        for all objects in this attribute's current state
1030        + history.
1031
1032        Only applies to object-based attributes.
1033
1034        This is an inlining of existing functionality
1035        which roughly corresponds to:
1036
1037            get_state_history(
1038                        state,
1039                        key,
1040                        passive=PASSIVE_NO_INITIALIZE).sum()
1041
1042        """
1043        raise NotImplementedError()
1044
1045    def _default_value(
1046        self, state: InstanceState[Any], dict_: _InstanceDict
1047    ) -> Any:
1048        """Produce an empty value for an uninitialized scalar attribute."""
1049
1050        assert self.key not in dict_, (
1051            "_default_value should only be invoked for an "
1052            "uninitialized or expired attribute"
1053        )
1054
1055        value = None
1056        for fn in self.dispatch.init_scalar:
1057            ret = fn(state, value, dict_)
1058            if ret is not ATTR_EMPTY:
1059                value = ret
1060
1061        return value
1062
1063    def get(
1064        self,
1065        state: InstanceState[Any],
1066        dict_: _InstanceDict,
1067        passive: PassiveFlag = PASSIVE_OFF,
1068    ) -> Any:
1069        """Retrieve a value from the given object.
1070        If a callable is assembled on this object's attribute, and
1071        passive is False, the callable will be executed and the
1072        resulting value will be set as the new value for this attribute.
1073        """
1074        if self.key in dict_:
1075            return dict_[self.key]
1076        else:
1077            # if history present, don't load
1078            key = self.key
1079            if (
1080                key not in state.committed_state
1081                or state.committed_state[key] is NO_VALUE
1082            ):
1083                if not passive & CALLABLES_OK:
1084                    return PASSIVE_NO_RESULT
1085
1086                value = self._fire_loader_callables(state, key, passive)
1087
1088                if value is PASSIVE_NO_RESULT or value is NO_VALUE:
1089                    return value
1090                elif value is ATTR_WAS_SET:
1091                    try:
1092                        return dict_[key]
1093                    except KeyError as err:
1094                        # TODO: no test coverage here.
1095                        raise KeyError(
1096                            "Deferred loader for attribute "
1097                            "%r failed to populate "
1098                            "correctly" % key
1099                        ) from err
1100                elif value is not ATTR_EMPTY:
1101                    return self.set_committed_value(state, dict_, value)
1102
1103            if not passive & INIT_OK:
1104                return NO_VALUE
1105            else:
1106                return self._default_value(state, dict_)
1107
1108    def _fire_loader_callables(
1109        self, state: InstanceState[Any], key: str, passive: PassiveFlag
1110    ) -> Any:
1111        if (
1112            self.accepts_scalar_loader
1113            and self.load_on_unexpire
1114            and key in state.expired_attributes
1115        ):
1116            return state._load_expired(state, passive)
1117        elif key in state.callables:
1118            callable_ = state.callables[key]
1119            return callable_(state, passive)
1120        elif self.callable_:
1121            return self.callable_(state, passive)
1122        else:
1123            return ATTR_EMPTY
1124
1125    def append(
1126        self,
1127        state: InstanceState[Any],
1128        dict_: _InstanceDict,
1129        value: Any,
1130        initiator: Optional[AttributeEventToken],
1131        passive: PassiveFlag = PASSIVE_OFF,
1132    ) -> None:
1133        self.set(state, dict_, value, initiator, passive=passive)
1134
1135    def remove(
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(
1144            state, dict_, None, initiator, passive=passive, check_old=value
1145        )
1146
1147    def pop(
1148        self,
1149        state: InstanceState[Any],
1150        dict_: _InstanceDict,
1151        value: Any,
1152        initiator: Optional[AttributeEventToken],
1153        passive: PassiveFlag = PASSIVE_OFF,
1154    ) -> None:
1155        self.set(
1156            state,
1157            dict_,
1158            None,
1159            initiator,
1160            passive=passive,
1161            check_old=value,
1162            pop=True,
1163        )
1164
1165    def set(
1166        self,
1167        state: InstanceState[Any],
1168        dict_: _InstanceDict,
1169        value: Any,
1170        initiator: Optional[AttributeEventToken] = None,
1171        passive: PassiveFlag = PASSIVE_OFF,
1172        check_old: Any = None,
1173        pop: bool = False,
1174    ) -> None:
1175        raise NotImplementedError()
1176
1177    def delete(self, state: InstanceState[Any], dict_: _InstanceDict) -> None:
1178        raise NotImplementedError()
1179
1180    def get_committed_value(
1181        self,
1182        state: InstanceState[Any],
1183        dict_: _InstanceDict,
1184        passive: PassiveFlag = PASSIVE_OFF,
1185    ) -> Any:
1186        """return the unchanged value of this attribute"""
1187
1188        if self.key in state.committed_state:
1189            value = state.committed_state[self.key]
1190            if value is NO_VALUE:
1191                return None
1192            else:
1193                return value
1194        else:
1195            return self.get(state, dict_, passive=passive)
1196
1197    def set_committed_value(self, state, dict_, value):
1198        """set an attribute value on the given instance and 'commit' it."""
1199
1200        dict_[self.key] = value

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

codekingpro/portable-devtools · Team Ai