Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
interfaces.py1497 linesDownload Raw Back to orm
1# orm/interfaces.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
8"""
9
10Contains various base classes used throughout the ORM.
11
12Defines some key base classes prominent within the internals.
13
14This module and the classes within are mostly private, though some attributes
15are exposed when inspecting mappings.
16
17"""
18
19from __future__ import annotations
20
21import collections
22import dataclasses
23import typing
24from typing import Any
25from typing import Callable
26from typing import cast
27from typing import ClassVar
28from typing import Dict
29from typing import Generic
30from typing import Iterator
31from typing import List
32from typing import Mapping
33from typing import NamedTuple
34from typing import NoReturn
35from typing import Optional
36from typing import Sequence
37from typing import Set
38from typing import Tuple
39from typing import Type
40from typing import TYPE_CHECKING
41from typing import TypeVar
42from typing import Union
43
44from . import exc as orm_exc
45from . import path_registry
46from .base import _MappedAttribute as _MappedAttribute
47from .base import EXT_CONTINUE as EXT_CONTINUE  # noqa: F401
48from .base import EXT_SKIP as EXT_SKIP  # noqa: F401
49from .base import EXT_STOP as EXT_STOP  # noqa: F401
50from .base import InspectionAttr as InspectionAttr  # noqa: F401
51from .base import InspectionAttrInfo as InspectionAttrInfo
52from .base import MANYTOMANY as MANYTOMANY  # noqa: F401
53from .base import MANYTOONE as MANYTOONE  # noqa: F401
54from .base import NO_KEY as NO_KEY  # noqa: F401
55from .base import NO_VALUE as NO_VALUE  # noqa: F401
56from .base import NotExtension as NotExtension  # noqa: F401
57from .base import ONETOMANY as ONETOMANY  # noqa: F401
58from .base import RelationshipDirection as RelationshipDirection  # noqa: F401
59from .base import SQLORMOperations
60from .. import ColumnElement
61from .. import exc as sa_exc
62from .. import inspection
63from .. import util
64from ..sql import operators
65from ..sql import roles
66from ..sql import visitors
67from ..sql.base import _NoArg
68from ..sql.base import ExecutableOption
69from ..sql.cache_key import HasCacheKey
70from ..sql.operators import ColumnOperators
71from ..sql.schema import Column
72from ..sql.type_api import TypeEngine
73from ..util import warn_deprecated
74from ..util.typing import RODescriptorReference
75from ..util.typing import TypedDict
76
77if typing.TYPE_CHECKING:
78    from ._typing import _EntityType
79    from ._typing import _IdentityKeyType
80    from ._typing import _InstanceDict
81    from ._typing import _InternalEntityType
82    from ._typing import _ORMAdapterProto
83    from .attributes import InstrumentedAttribute
84    from .base import Mapped
85    from .context import _MapperEntity
86    from .context import ORMCompileState
87    from .context import QueryContext
88    from .decl_api import RegistryType
89    from .decl_base import _ClassScanMapperConfig
90    from .loading import _PopulatorDict
91    from .mapper import Mapper
92    from .path_registry import AbstractEntityRegistry
93    from .query import Query
94    from .session import Session
95    from .state import InstanceState
96    from .strategy_options import _LoadElement
97    from .util import AliasedInsp
98    from .util import ORMAdapter
99    from ..engine.result import Result
100    from ..sql._typing import _ColumnExpressionArgument
101    from ..sql._typing import _ColumnsClauseArgument
102    from ..sql._typing import _DMLColumnArgument
103    from ..sql._typing import _InfoType
104    from ..sql.operators import OperatorType
105    from ..sql.visitors import _TraverseInternalsType
106    from ..util.typing import _AnnotationScanType
107
108_StrategyKey = Tuple[Any, ...]
109
110_T = TypeVar("_T", bound=Any)
111_T_co = TypeVar("_T_co", bound=Any, covariant=True)
112
113_TLS = TypeVar("_TLS", bound="Type[LoaderStrategy]")
114
115
116class ORMStatementRole(roles.StatementRole):
117    __slots__ = ()
118    _role_name = (
119        "Executable SQL or text() construct, including ORM aware objects"
120    )
121
122
123class ORMColumnsClauseRole(
124    roles.ColumnsClauseRole, roles.TypedColumnsClauseRole[_T]
125):
126    __slots__ = ()
127    _role_name = "ORM mapped entity, aliased entity, or Column expression"
128
129
130class ORMEntityColumnsClauseRole(ORMColumnsClauseRole[_T]):
131    __slots__ = ()
132    _role_name = "ORM mapped or aliased entity"
133
134
135class ORMFromClauseRole(roles.StrictFromClauseRole):
136    __slots__ = ()
137    _role_name = "ORM mapped entity, aliased entity, or FROM expression"
138
139
140class ORMColumnDescription(TypedDict):
141    name: str
142    # TODO: add python_type and sql_type here; combining them
143    # into "type" is a bad idea
144    type: Union[Type[Any], TypeEngine[Any]]
145    aliased: bool
146    expr: _ColumnsClauseArgument[Any]
147    entity: Optional[_ColumnsClauseArgument[Any]]
148
149
150class _IntrospectsAnnotations:
151    __slots__ = ()
152
153    @classmethod
154    def _mapper_property_name(cls) -> str:
155        return cls.__name__
156
157    def found_in_pep593_annotated(self) -> Any:
158        """return a copy of this object to use in declarative when the
159        object is found inside of an Annotated object."""
160
161        raise NotImplementedError(
162            f"Use of the {self._mapper_property_name()!r} "
163            "construct inside of an Annotated object is not yet supported."
164        )
165
166    def declarative_scan(
167        self,
168        decl_scan: _ClassScanMapperConfig,
169        registry: RegistryType,
170        cls: Type[Any],
171        originating_module: Optional[str],
172        key: str,
173        mapped_container: Optional[Type[Mapped[Any]]],
174        annotation: Optional[_AnnotationScanType],
175        extracted_mapped_annotation: Optional[_AnnotationScanType],
176        is_dataclass_field: bool,
177    ) -> None:
178        """Perform class-specific initialization at early declarative scanning
179        time.
180
181        .. versionadded:: 2.0
182
183        """
184
185    def _raise_for_required(self, key: str, cls: Type[Any]) -> NoReturn:
186        raise sa_exc.ArgumentError(
187            f"Python typing annotation is required for attribute "
188            f'"{cls.__name__}.{key}" when primary argument(s) for '
189            f'"{self._mapper_property_name()}" '
190            "construct are None or not present"
191        )
192
193
194class _AttributeOptions(NamedTuple):
195    """define Python-local attribute behavior options common to all
196    :class:`.MapperProperty` objects.
197
198    Currently this includes dataclass-generation arguments.
199
200    .. versionadded:: 2.0
201
202    """
203
204    dataclasses_init: Union[_NoArg, bool]
205    dataclasses_repr: Union[_NoArg, bool]
206    dataclasses_default: Union[_NoArg, Any]
207    dataclasses_default_factory: Union[_NoArg, Callable[[], Any]]
208    dataclasses_compare: Union[_NoArg, bool]
209    dataclasses_kw_only: Union[_NoArg, bool]
210    dataclasses_hash: Union[_NoArg, bool, None]
211    dataclasses_dataclass_metadata: Union[_NoArg, Mapping[Any, Any], None]
212
213    def _as_dataclass_field(self, key: str) -> Any:
214        """Return a ``dataclasses.Field`` object given these arguments."""
215
216        kw: Dict[str, Any] = {}
217        if self.dataclasses_default_factory is not _NoArg.NO_ARG:
218            kw["default_factory"] = self.dataclasses_default_factory
219        if self.dataclasses_default is not _NoArg.NO_ARG:
220            kw["default"] = self.dataclasses_default
221        if self.dataclasses_init is not _NoArg.NO_ARG:
222            kw["init"] = self.dataclasses_init
223        if self.dataclasses_repr is not _NoArg.NO_ARG:
224            kw["repr"] = self.dataclasses_repr
225        if self.dataclasses_compare is not _NoArg.NO_ARG:
226            kw["compare"] = self.dataclasses_compare
227        if self.dataclasses_kw_only is not _NoArg.NO_ARG:
228            kw["kw_only"] = self.dataclasses_kw_only
229        if self.dataclasses_hash is not _NoArg.NO_ARG:
230            kw["hash"] = self.dataclasses_hash
231        if self.dataclasses_dataclass_metadata is not _NoArg.NO_ARG:
232            kw["metadata"] = self.dataclasses_dataclass_metadata
233
234        if "default" in kw and callable(kw["default"]):
235            # callable defaults are ambiguous. deprecate them in favour of
236            # insert_default or default_factory. #9936
237            warn_deprecated(
238                f"Callable object passed to the ``default`` parameter for "
239                f"attribute {key!r} in a ORM-mapped Dataclasses context is "
240                "ambiguous, "
241                "and this use will raise an error in a future release.  "
242                "If this callable is intended to produce Core level INSERT "
243                "default values for an underlying ``Column``, use "
244                "the ``mapped_column.insert_default`` parameter instead.  "
245                "To establish this callable as providing a default value "
246                "for instances of the dataclass itself, use the "
247                "``default_factory`` dataclasses parameter.",
248                "2.0",
249            )
250
251        if (
252            "init" in kw
253            and not kw["init"]
254            and "default" in kw
255            and not callable(kw["default"])  # ignore callable defaults. #9936
256            and "default_factory" not in kw  # illegal but let dc.field raise
257        ):
258            # fix for #9879
259            default = kw.pop("default")
260            kw["default_factory"] = lambda: default
261
262        return dataclasses.field(**kw)
263
264    @classmethod
265    def _get_arguments_for_make_dataclass(
266        cls,
267        key: str,
268        annotation: _AnnotationScanType,
269        mapped_container: Optional[Any],
270        elem: Any,
271    ) -> Union[
272        Tuple[str, _AnnotationScanType],
273        Tuple[str, _AnnotationScanType, dataclasses.Field[Any]],
274    ]:
275        """given attribute key, annotation, and value from a class, return
276        the argument tuple we would pass to dataclasses.make_dataclass()
277        for this attribute.
278
279        """
280        if isinstance(elem, _DCAttributeOptions):
281            dc_field = elem._attribute_options._as_dataclass_field(key)
282
283            return (key, annotation, dc_field)
284        elif elem is not _NoArg.NO_ARG:
285            # why is typing not erroring on this?
286            return (key, annotation, elem)
287        elif mapped_container is not None:
288            # it's Mapped[], but there's no "element", which means declarative
289            # did not actually do anything for this field.  this shouldn't
290            # happen.
291            # previously, this would occur because _scan_attributes would
292            # skip a field that's on an already mapped superclass, but it
293            # would still include it in the annotations, leading
294            # to issue #8718
295
296            assert False, "Mapped[] received without a mapping declaration"
297
298        else:
299            # plain dataclass field, not mapped.  Is only possible
300            # if __allow_unmapped__ is set up.  I can see this mode causing
301            # problems...
302            return (key, annotation)
303
304
305_DEFAULT_ATTRIBUTE_OPTIONS = _AttributeOptions(
306    _NoArg.NO_ARG,
307    _NoArg.NO_ARG,
308    _NoArg.NO_ARG,
309    _NoArg.NO_ARG,
310    _NoArg.NO_ARG,
311    _NoArg.NO_ARG,
312    _NoArg.NO_ARG,
313    _NoArg.NO_ARG,
314)
315
316_DEFAULT_READONLY_ATTRIBUTE_OPTIONS = _AttributeOptions(
317    False,
318    _NoArg.NO_ARG,
319    _NoArg.NO_ARG,
320    _NoArg.NO_ARG,
321    _NoArg.NO_ARG,
322    _NoArg.NO_ARG,
323    _NoArg.NO_ARG,
324    _NoArg.NO_ARG,
325)
326
327
328class _DCAttributeOptions:
329    """mixin for descriptors or configurational objects that include dataclass
330    field options.
331
332    This includes :class:`.MapperProperty`, :class:`._MapsColumn` within
333    the ORM, but also includes :class:`.AssociationProxy` within ext.
334    Can in theory be used for other descriptors that serve a similar role
335    as association proxy.   (*maybe* hybrids, not sure yet.)
336
337    """
338
339    __slots__ = ()
340
341    _attribute_options: _AttributeOptions
342    """behavioral options for ORM-enabled Python attributes
343
344    .. versionadded:: 2.0
345
346    """
347
348    _has_dataclass_arguments: bool
349
350
351class _MapsColumns(_DCAttributeOptions, _MappedAttribute[_T]):
352    """interface for declarative-capable construct that delivers one or more
353    Column objects to the declarative process to be part of a Table.
354    """
355
356    __slots__ = ()
357
358    @property
359    def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
360        """return a MapperProperty to be assigned to the declarative mapping"""
361        raise NotImplementedError()
362
363    @property
364    def columns_to_assign(self) -> List[Tuple[Column[_T], int]]:
365        """A list of Column objects that should be declaratively added to the
366        new Table object.
367
368        """
369        raise NotImplementedError()
370
371
372# NOTE: MapperProperty needs to extend _MappedAttribute so that declarative
373# typing works, i.e. "Mapped[A] = relationship()".   This introduces an
374# inconvenience which is that all the MapperProperty objects are treated
375# as descriptors by typing tools, which are misled by this as assignment /
376# access to a descriptor attribute wants to move through __get__.
377# Therefore, references to MapperProperty as an instance variable, such
378# as in PropComparator, may have some special typing workarounds such as the
379# use of sqlalchemy.util.typing.DescriptorReference to avoid mis-interpretation
380# by typing tools
381@inspection._self_inspects
382class MapperProperty(
383    HasCacheKey,
384    _DCAttributeOptions,
385    _MappedAttribute[_T],
386    InspectionAttrInfo,
387    util.MemoizedSlots,
388):
389    """Represent a particular class attribute mapped by :class:`_orm.Mapper`.
390
391    The most common occurrences of :class:`.MapperProperty` are the
392    mapped :class:`_schema.Column`, which is represented in a mapping as
393    an instance of :class:`.ColumnProperty`,
394    and a reference to another class produced by :func:`_orm.relationship`,
395    represented in the mapping as an instance of
396    :class:`.Relationship`.
397
398    """
399
400    __slots__ = (
401        "_configure_started",
402        "_configure_finished",
403        "_attribute_options",
404        "_has_dataclass_arguments",
405        "parent",
406        "key",
407        "info",
408        "doc",
409    )
410
411    _cache_key_traversal: _TraverseInternalsType = [
412        ("parent", visitors.ExtendedInternalTraversal.dp_has_cache_key),
413        ("key", visitors.ExtendedInternalTraversal.dp_string),
414    ]
415
416    if not TYPE_CHECKING:
417        cascade = None
418
419    is_property = True
420    """Part of the InspectionAttr interface; states this object is a
421    mapper property.
422
423    """
424
425    comparator: PropComparator[_T]
426    """The :class:`_orm.PropComparator` instance that implements SQL
427    expression construction on behalf of this mapped attribute."""
428
429    key: str
430    """name of class attribute"""
431
432    parent: Mapper[Any]
433    """the :class:`.Mapper` managing this property."""
434
435    _is_relationship = False
436
437    _links_to_entity: bool
438    """True if this MapperProperty refers to a mapped entity.
439
440    Should only be True for Relationship, False for all others.
441
442    """
443
444    doc: Optional[str]
445    """optional documentation string"""
446
447    info: _InfoType
448    """Info dictionary associated with the object, allowing user-defined
449    data to be associated with this :class:`.InspectionAttr`.
450
451    The dictionary is generated when first accessed.  Alternatively,
452    it can be specified as a constructor argument to the
453    :func:`.column_property`, :func:`_orm.relationship`, or :func:`.composite`
454    functions.
455
456    .. seealso::
457
458        :attr:`.QueryableAttribute.info`
459
460        :attr:`.SchemaItem.info`
461
462    """
463
464    def _memoized_attr_info(self) -> _InfoType:
465        """Info dictionary associated with the object, allowing user-defined
466        data to be associated with this :class:`.InspectionAttr`.
467
468        The dictionary is generated when first accessed.  Alternatively,
469        it can be specified as a constructor argument to the
470        :func:`.column_property`, :func:`_orm.relationship`, or
471        :func:`.composite`
472        functions.
473
474        .. seealso::
475
476            :attr:`.QueryableAttribute.info`
477
478            :attr:`.SchemaItem.info`
479
480        """
481        return {}
482
483    def setup(
484        self,
485        context: ORMCompileState,
486        query_entity: _MapperEntity,
487        path: AbstractEntityRegistry,
488        adapter: Optional[ORMAdapter],
489        **kwargs: Any,
490    ) -> None:
491        """Called by Query for the purposes of constructing a SQL statement.
492
493        Each MapperProperty associated with the target mapper processes the
494        statement referenced by the query context, adding columns and/or
495        criterion as appropriate.
496
497        """
498
499    def create_row_processor(
500        self,
501        context: ORMCompileState,
502        query_entity: _MapperEntity,
503        path: AbstractEntityRegistry,
504        mapper: Mapper[Any],
505        result: Result[Any],
506        adapter: Optional[ORMAdapter],
507        populators: _PopulatorDict,
508    ) -> None:
509        """Produce row processing functions and append to the given
510        set of populators lists.
511
512        """
513
514    def cascade_iterator(
515        self,
516        type_: str,
517        state: InstanceState[Any],
518        dict_: _InstanceDict,
519        visited_states: Set[InstanceState[Any]],
520        halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None,
521    ) -> Iterator[
522        Tuple[object, Mapper[Any], InstanceState[Any], _InstanceDict]
523    ]:
524        """Iterate through instances related to the given instance for
525        a particular 'cascade', starting with this MapperProperty.
526
527        Return an iterator3-tuples (instance, mapper, state).
528
529        Note that the 'cascade' collection on this MapperProperty is
530        checked first for the given type before cascade_iterator is called.
531
532        This method typically only applies to Relationship.
533
534        """
535
536        return iter(())
537
538    def set_parent(self, parent: Mapper[Any], init: bool) -> None:
539        """Set the parent mapper that references this MapperProperty.
540
541        This method is overridden by some subclasses to perform extra
542        setup when the mapper is first known.
543
544        """
545        self.parent = parent
546
547    def instrument_class(self, mapper: Mapper[Any]) -> None:
548        """Hook called by the Mapper to the property to initiate
549        instrumentation of the class attribute managed by this
550        MapperProperty.
551
552        The MapperProperty here will typically call out to the
553        attributes module to set up an InstrumentedAttribute.
554
555        This step is the first of two steps to set up an InstrumentedAttribute,
556        and is called early in the mapper setup process.
557
558        The second step is typically the init_class_attribute step,
559        called from StrategizedProperty via the post_instrument_class()
560        hook.  This step assigns additional state to the InstrumentedAttribute
561        (specifically the "impl") which has been determined after the
562        MapperProperty has determined what kind of persistence
563        management it needs to do (e.g. scalar, object, collection, etc).
564
565        """
566
567    def __init__(
568        self,
569        attribute_options: Optional[_AttributeOptions] = None,
570        _assume_readonly_dc_attributes: bool = False,
571    ) -> None:
572        self._configure_started = False
573        self._configure_finished = False
574
575        if _assume_readonly_dc_attributes:
576            default_attrs = _DEFAULT_READONLY_ATTRIBUTE_OPTIONS
577        else:
578            default_attrs = _DEFAULT_ATTRIBUTE_OPTIONS
579
580        if attribute_options and attribute_options != default_attrs:
581            self._has_dataclass_arguments = True
582            self._attribute_options = attribute_options
583        else:
584            self._has_dataclass_arguments = False
585            self._attribute_options = default_attrs
586
587    def init(self) -> None:
588        """Called after all mappers are created to assemble
589        relationships between mappers and perform other post-mapper-creation
590        initialization steps.
591
592
593        """
594        self._configure_started = True
595        self.do_init()
596        self._configure_finished = True
597
598    @property
599    def class_attribute(self) -> InstrumentedAttribute[_T]:
600        """Return the class-bound descriptor corresponding to this
601        :class:`.MapperProperty`.
602
603        This is basically a ``getattr()`` call::
604
605            return getattr(self.parent.class_, self.key)
606
607        I.e. if this :class:`.MapperProperty` were named ``addresses``,
608        and the class to which it is mapped is ``User``, this sequence
609        is possible::
610
611            >>> from sqlalchemy import inspect
612            >>> mapper = inspect(User)
613            >>> addresses_property = mapper.attrs.addresses
614            >>> addresses_property.class_attribute is User.addresses
615            True
616            >>> User.addresses.property is addresses_property
617            True
618
619
620        """
621
622        return getattr(self.parent.class_, self.key)  # type: ignore
623
624    def do_init(self) -> None:
625        """Perform subclass-specific initialization post-mapper-creation
626        steps.
627
628        This is a template method called by the ``MapperProperty``
629        object's init() method.
630
631        """
632
633    def post_instrument_class(self, mapper: Mapper[Any]) -> None:
634        """Perform instrumentation adjustments that need to occur
635        after init() has completed.
636
637        The given Mapper is the Mapper invoking the operation, which
638        may not be the same Mapper as self.parent in an inheritance
639        scenario; however, Mapper will always at least be a sub-mapper of
640        self.parent.
641
642        This method is typically used by StrategizedProperty, which delegates
643        it to LoaderStrategy.init_class_attribute() to perform final setup
644        on the class-bound InstrumentedAttribute.
645
646        """
647
648    def merge(
649        self,
650        session: Session,
651        source_state: InstanceState[Any],
652        source_dict: _InstanceDict,
653        dest_state: InstanceState[Any],
654        dest_dict: _InstanceDict,
655        load: bool,
656        _recursive: Dict[Any, object],
657        _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
658    ) -> None:
659        """Merge the attribute represented by this ``MapperProperty``
660        from source to destination object.
661
662        """
663
664    def __repr__(self) -> str:
665        return "<%s at 0x%x; %s>" % (
666            self.__class__.__name__,
667            id(self),
668            getattr(self, "key", "no key"),
669        )
670
671
672@inspection._self_inspects
673class PropComparator(SQLORMOperations[_T_co], Generic[_T_co], ColumnOperators):
674    r"""Defines SQL operations for ORM mapped attributes.
675
676    SQLAlchemy allows for operators to
677    be redefined at both the Core and ORM level.  :class:`.PropComparator`
678    is the base class of operator redefinition for ORM-level operations,
679    including those of :class:`.ColumnProperty`,
680    :class:`.Relationship`, and :class:`.Composite`.
681
682    User-defined subclasses of :class:`.PropComparator` may be created. The
683    built-in Python comparison and math operator methods, such as
684    :meth:`.operators.ColumnOperators.__eq__`,
685    :meth:`.operators.ColumnOperators.__lt__`, and
686    :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide
687    new operator behavior. The custom :class:`.PropComparator` is passed to
688    the :class:`.MapperProperty` instance via the ``comparator_factory``
689    argument. In each case,
690    the appropriate subclass of :class:`.PropComparator` should be used::
691
692        # definition of custom PropComparator subclasses
693
694        from sqlalchemy.orm.properties import (
695            ColumnProperty,
696            Composite,
697            Relationship,
698        )
699
700
701        class MyColumnComparator(ColumnProperty.Comparator):
702            def __eq__(self, other):
703                return self.__clause_element__() == other
704
705
706        class MyRelationshipComparator(Relationship.Comparator):
707            def any(self, expression):
708                "define the 'any' operation"
709                # ...
710
711
712        class MyCompositeComparator(Composite.Comparator):
713            def __gt__(self, other):
714                "redefine the 'greater than' operation"
715
716                return sql.and_(
717                    *[
718                        a > b
719                        for a, b in zip(
720                            self.__clause_element__().clauses,
721                            other.__composite_values__(),
722                        )
723                    ]
724                )
725
726
727        # application of custom PropComparator subclasses
728
729        from sqlalchemy.orm import column_property, relationship, composite
730        from sqlalchemy import Column, String
731
732
733        class SomeMappedClass(Base):
734            some_column = column_property(
735                Column("some_column", String),
736                comparator_factory=MyColumnComparator,
737            )
738
739            some_relationship = relationship(
740                SomeOtherClass, comparator_factory=MyRelationshipComparator
741            )
742
743            some_composite = composite(
744                Column("a", String),
745                Column("b", String),
746                comparator_factory=MyCompositeComparator,
747            )
748
749    Note that for column-level operator redefinition, it's usually
750    simpler to define the operators at the Core level, using the
751    :attr:`.TypeEngine.comparator_factory` attribute.  See
752    :ref:`types_operators` for more detail.
753
754    .. seealso::
755
756        :class:`.ColumnProperty.Comparator`
757
758        :class:`.Relationship.Comparator`
759
760        :class:`.Composite.Comparator`
761
762        :class:`.ColumnOperators`
763
764        :ref:`types_operators`
765
766        :attr:`.TypeEngine.comparator_factory`
767
768    """
769
770    __slots__ = "prop", "_parententity", "_adapt_to_entity"
771
772    __visit_name__ = "orm_prop_comparator"
773
774    _parententity: _InternalEntityType[Any]
775    _adapt_to_entity: Optional[AliasedInsp[Any]]
776    prop: RODescriptorReference[MapperProperty[_T_co]]
777
778    def __init__(
779        self,
780        prop: MapperProperty[_T],
781        parentmapper: _InternalEntityType[Any],
782        adapt_to_entity: Optional[AliasedInsp[Any]] = None,
783    ):
784        self.prop = prop
785        self._parententity = adapt_to_entity or parentmapper
786        self._adapt_to_entity = adapt_to_entity
787
788    @util.non_memoized_property
789    def property(self) -> MapperProperty[_T_co]:
790        """Return the :class:`.MapperProperty` associated with this
791        :class:`.PropComparator`.
792
793
794        Return values here will commonly be instances of
795        :class:`.ColumnProperty` or :class:`.Relationship`.
796
797
798        """
799        return self.prop
800
801    def __clause_element__(self) -> roles.ColumnsClauseRole:
802        raise NotImplementedError("%r" % self)
803
804    def _bulk_update_tuples(
805        self, value: Any
806    ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
807        """Receive a SQL expression that represents a value in the SET
808        clause of an UPDATE statement.
809
810        Return a tuple that can be passed to a :class:`_expression.Update`
811        construct.
812
813        """
814
815        return [(cast("_DMLColumnArgument", self.__clause_element__()), value)]
816
817    def adapt_to_entity(
818        self, adapt_to_entity: AliasedInsp[Any]
819    ) -> PropComparator[_T_co]:
820        """Return a copy of this PropComparator which will use the given
821        :class:`.AliasedInsp` to produce corresponding expressions.
822        """
823        return self.__class__(self.prop, self._parententity, adapt_to_entity)
824
825    @util.ro_non_memoized_property
826    def _parentmapper(self) -> Mapper[Any]:
827        """legacy; this is renamed to _parententity to be
828        compatible with QueryableAttribute."""
829        return self._parententity.mapper
830
831    def _criterion_exists(
832        self,
833        criterion: Optional[_ColumnExpressionArgument[bool]] = None,
834        **kwargs: Any,
835    ) -> ColumnElement[Any]:
836        return self.prop.comparator._criterion_exists(criterion, **kwargs)
837
838    @util.ro_non_memoized_property
839    def adapter(self) -> Optional[_ORMAdapterProto]:
840        """Produce a callable that adapts column expressions
841        to suit an aliased version of this comparator.
842
843        """
844        if self._adapt_to_entity is None:
845            return None
846        else:
847            return self._adapt_to_entity._orm_adapt_element
848
849    @util.ro_non_memoized_property
850    def info(self) -> _InfoType:
851        return self.prop.info
852
853    @staticmethod
854    def _any_op(a: Any, b: Any, **kwargs: Any) -> Any:
855        return a.any(b, **kwargs)
856
857    @staticmethod
858    def _has_op(left: Any, other: Any, **kwargs: Any) -> Any:
859        return left.has(other, **kwargs)
860
861    @staticmethod
862    def _of_type_op(a: Any, class_: Any) -> Any:
863        return a.of_type(class_)
864
865    any_op = cast(operators.OperatorType, _any_op)
866    has_op = cast(operators.OperatorType, _has_op)
867    of_type_op = cast(operators.OperatorType, _of_type_op)
868
869    if typing.TYPE_CHECKING:
870
871        def operate(
872            self, op: OperatorType, *other: Any, **kwargs: Any
873        ) -> ColumnElement[Any]: ...
874
875        def reverse_operate(
876            self, op: OperatorType, other: Any, **kwargs: Any
877        ) -> ColumnElement[Any]: ...
878
879    def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T_co]:
880        r"""Redefine this object in terms of a polymorphic subclass,
881        :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased`
882        construct.
883
884        Returns a new PropComparator from which further criterion can be
885        evaluated.
886
887        e.g.::
888
889            query.join(Company.employees.of_type(Engineer)).filter(
890                Engineer.name == "foo"
891            )
892
893        :param \class_: a class or mapper indicating that criterion will be
894            against this specific subclass.
895
896        .. seealso::
897
898            :ref:`orm_queryguide_joining_relationships_aliased` - in the
899            :ref:`queryguide_toplevel`
900
901            :ref:`inheritance_of_type`
902
903        """
904
905        return self.operate(PropComparator.of_type_op, class_)  # type: ignore
906
907    def and_(
908        self, *criteria: _ColumnExpressionArgument[bool]
909    ) -> PropComparator[bool]:
910        """Add additional criteria to the ON clause that's represented by this
911        relationship attribute.
912
913        E.g.::
914
915
916            stmt = select(User).join(
917                User.addresses.and_(Address.email_address != "foo")
918            )
919
920            stmt = select(User).options(
921                joinedload(User.addresses.and_(Address.email_address != "foo"))
922            )
923
924        .. versionadded:: 1.4
925
926        .. seealso::
927
928            :ref:`orm_queryguide_join_on_augmented`
929
930            :ref:`loader_option_criteria`
931
932            :func:`.with_loader_criteria`
933
934        """
935        return self.operate(operators.and_, *criteria)  # type: ignore
936
937    def any(
938        self,
939        criterion: Optional[_ColumnExpressionArgument[bool]] = None,
940        **kwargs: Any,
941    ) -> ColumnElement[bool]:
942        r"""Return a SQL expression representing true if this element
943        references a member which meets the given criterion.
944
945        The usual implementation of ``any()`` is
946        :meth:`.Relationship.Comparator.any`.
947
948        :param criterion: an optional ClauseElement formulated against the
949          member class' table or attributes.
950
951        :param \**kwargs: key/value pairs corresponding to member class
952          attribute names which will be compared via equality to the
953          corresponding values.
954
955        """
956
957        return self.operate(PropComparator.any_op, criterion, **kwargs)
958
959    def has(
960        self,
961        criterion: Optional[_ColumnExpressionArgument[bool]] = None,
962        **kwargs: Any,
963    ) -> ColumnElement[bool]:
964        r"""Return a SQL expression representing true if this element
965        references a member which meets the given criterion.
966
967        The usual implementation of ``has()`` is
968        :meth:`.Relationship.Comparator.has`.
969
970        :param criterion: an optional ClauseElement formulated against the
971          member class' table or attributes.
972
973        :param \**kwargs: key/value pairs corresponding to member class
974          attribute names which will be compared via equality to the
975          corresponding values.
976
977        """
978
979        return self.operate(PropComparator.has_op, criterion, **kwargs)
980
981
982class StrategizedProperty(MapperProperty[_T]):
983    """A MapperProperty which uses selectable strategies to affect
984    loading behavior.
985
986    There is a single strategy selected by default.  Alternate
987    strategies can be selected at Query time through the usage of
988    ``StrategizedOption`` objects via the Query.options() method.
989
990    The mechanics of StrategizedProperty are used for every Query
991    invocation for every mapped attribute participating in that Query,
992    to determine first how the attribute will be rendered in SQL
993    and secondly how the attribute will retrieve a value from a result
994    row and apply it to a mapped object.  The routines here are very
995    performance-critical.
996
997    """
998
999    __slots__ = (
1000        "_strategies",
1001        "strategy",
1002        "_wildcard_token",
1003        "_default_path_loader_key",
1004        "strategy_key",
1005    )
1006    inherit_cache = True
1007    strategy_wildcard_key: ClassVar[str]
1008
1009    strategy_key: _StrategyKey
1010
1011    _strategies: Dict[_StrategyKey, LoaderStrategy]
1012
1013    def _memoized_attr__wildcard_token(self) -> Tuple[str]:
1014        return (
1015            f"{self.strategy_wildcard_key}:{path_registry._WILDCARD_TOKEN}",
1016        )
1017
1018    def _memoized_attr__default_path_loader_key(
1019        self,
1020    ) -> Tuple[str, Tuple[str]]:
1021        return (
1022            "loader",
1023            (f"{self.strategy_wildcard_key}:{path_registry._DEFAULT_TOKEN}",),
1024        )
1025
1026    def _get_context_loader(
1027        self, context: ORMCompileState, path: AbstractEntityRegistry
1028    ) -> Optional[_LoadElement]:
1029        load: Optional[_LoadElement] = None
1030
1031        search_path = path[self]
1032
1033        # search among: exact match, "attr.*", "default" strategy
1034        # if any.
1035        for path_key in (
1036            search_path._loader_key,
1037            search_path._wildcard_path_loader_key,
1038            search_path._default_path_loader_key,
1039        ):
1040            if path_key in context.attributes:
1041                load = context.attributes[path_key]
1042                break
1043
1044                # note that if strategy_options.Load is placing non-actionable
1045                # objects in the context like defaultload(), we would
1046                # need to continue the loop here if we got such an
1047                # option as below.
1048                # if load.strategy or load.local_opts:
1049                #    break
1050
1051        return load
1052
1053    def _get_strategy(self, key: _StrategyKey) -> LoaderStrategy:
1054        try:
1055            return self._strategies[key]
1056        except KeyError:
1057            pass
1058
1059        # run outside to prevent transfer of exception context
1060        cls = self._strategy_lookup(self, *key)
1061        # this previously was setting self._strategies[cls], that's
1062        # a bad idea; should use strategy key at all times because every
1063        # strategy has multiple keys at this point
1064        self._strategies[key] = strategy = cls(self, key)
1065        return strategy
1066
1067    def setup(
1068        self,
1069        context: ORMCompileState,
1070        query_entity: _MapperEntity,
1071        path: AbstractEntityRegistry,
1072        adapter: Optional[ORMAdapter],
1073        **kwargs: Any,
1074    ) -> None:
1075        loader = self._get_context_loader(context, path)
1076        if loader and loader.strategy:
1077            strat = self._get_strategy(loader.strategy)
1078        else:
1079            strat = self.strategy
1080        strat.setup_query(
1081            context, query_entity, path, loader, adapter, **kwargs
1082        )
1083
1084    def create_row_processor(
1085        self,
1086        context: ORMCompileState,
1087        query_entity: _MapperEntity,
1088        path: AbstractEntityRegistry,
1089        mapper: Mapper[Any],
1090        result: Result[Any],
1091        adapter: Optional[ORMAdapter],
1092        populators: _PopulatorDict,
1093    ) -> None:
1094        loader = self._get_context_loader(context, path)
1095        if loader and loader.strategy:
1096            strat = self._get_strategy(loader.strategy)
1097        else:
1098            strat = self.strategy
1099        strat.create_row_processor(
1100            context,
1101            query_entity,
1102            path,
1103            loader,
1104            mapper,
1105            result,
1106            adapter,
1107            populators,
1108        )
1109
1110    def do_init(self) -> None:
1111        self._strategies = {}
1112        self.strategy = self._get_strategy(self.strategy_key)
1113
1114    def post_instrument_class(self, mapper: Mapper[Any]) -> None:
1115        if (
1116            not self.parent.non_primary
1117            and not mapper.class_manager._attr_has_impl(self.key)
1118        ):
1119            self.strategy.init_class_attribute(mapper)
1120
1121    _all_strategies: collections.defaultdict[
1122        Type[MapperProperty[Any]], Dict[_StrategyKey, Type[LoaderStrategy]]
1123    ] = collections.defaultdict(dict)
1124
1125    @classmethod
1126    def strategy_for(cls, **kw: Any) -> Callable[[_TLS], _TLS]:
1127        def decorate(dec_cls: _TLS) -> _TLS:
1128            # ensure each subclass of the strategy has its
1129            # own _strategy_keys collection
1130            if "_strategy_keys" not in dec_cls.__dict__:
1131                dec_cls._strategy_keys = []
1132            key = tuple(sorted(kw.items()))
1133            cls._all_strategies[cls][key] = dec_cls
1134            dec_cls._strategy_keys.append(key)
1135            return dec_cls
1136
1137        return decorate
1138
1139    @classmethod
1140    def _strategy_lookup(
1141        cls, requesting_property: MapperProperty[Any], *key: Any
1142    ) -> Type[LoaderStrategy]:
1143        requesting_property.parent._with_polymorphic_mappers
1144
1145        for prop_cls in cls.__mro__:
1146            if prop_cls in cls._all_strategies:
1147                if TYPE_CHECKING:
1148                    assert issubclass(prop_cls, MapperProperty)
1149                strategies = cls._all_strategies[prop_cls]
1150                try:
1151                    return strategies[key]
1152                except KeyError:
1153                    pass
1154
1155        for property_type, strats in cls._all_strategies.items():
1156            if key in strats:
1157                intended_property_type = property_type
1158                actual_strategy = strats[key]
1159                break
1160        else:
1161            intended_property_type = None
1162            actual_strategy = None
1163
1164        raise orm_exc.LoaderStrategyException(
1165            cls,
1166            requesting_property,
1167            intended_property_type,
1168            actual_strategy,
1169            key,
1170        )
1171
1172
1173class ORMOption(ExecutableOption):
1174    """Base class for option objects that are passed to ORM queries.
1175
1176    These options may be consumed by :meth:`.Query.options`,
1177    :meth:`.Select.options`, or in a more general sense by any
1178    :meth:`.Executable.options` method.   They are interpreted at
1179    statement compile time or execution time in modern use.  The
1180    deprecated :class:`.MapperOption` is consumed at ORM query construction
1181    time.
1182
1183    .. versionadded:: 1.4
1184
1185    """
1186
1187    __slots__ = ()
1188
1189    _is_legacy_option = False
1190
1191    propagate_to_loaders = False
1192    """if True, indicate this option should be carried along
1193    to "secondary" SELECT statements that occur for relationship
1194    lazy loaders as well as attribute load / refresh operations.
1195
1196    """
1197
1198    _is_core = False
1199
1200    _is_user_defined = False

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

codekingpro/portable-devtools · Team Ai