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 TypedDict
42from typing import TypeVar
43from typing import Union
44
45from . import exc as orm_exc
46from . import path_registry
47from .base import _MappedAttribute as _MappedAttribute
48from .base import DONT_SET as DONT_SET # noqa: F401
49from .base import EXT_CONTINUE as EXT_CONTINUE # noqa: F401
50from .base import EXT_SKIP as EXT_SKIP # noqa: F401
51from .base import EXT_STOP as EXT_STOP # noqa: F401
52from .base import InspectionAttr as InspectionAttr # noqa: F401
53from .base import InspectionAttrInfo as InspectionAttrInfo
54from .base import MANYTOMANY as MANYTOMANY # noqa: F401
55from .base import MANYTOONE as MANYTOONE # noqa: F401
56from .base import NO_KEY as NO_KEY # noqa: F401
57from .base import NO_VALUE as NO_VALUE # noqa: F401
58from .base import NotExtension as NotExtension # noqa: F401
59from .base import ONETOMANY as ONETOMANY # noqa: F401
60from .base import RelationshipDirection as RelationshipDirection # noqa: F401
61from .base import SQLORMOperations
62from .. import ColumnElement
63from .. import exc as sa_exc
64from .. import inspection
65from .. import util
66from ..sql import operators
67from ..sql import roles
68from ..sql import visitors
69from ..sql.base import _NoArg
70from ..sql.base import ExecutableOption
71from ..sql.cache_key import HasCacheKey
72from ..sql.operators import ColumnOperators
73from ..sql.schema import Column
74from ..sql.type_api import TypeEngine
75from ..util import warn_deprecated
76from ..util.typing import RODescriptorReference
77from ..util.typing import TupleAny
78from ..util.typing import Unpack
79
80if typing.TYPE_CHECKING:
81 from ._typing import _EntityType
82 from ._typing import _IdentityKeyType
83 from ._typing import _InstanceDict
84 from ._typing import _InternalEntityType
85 from ._typing import _ORMAdapterProto
86 from .attributes import InstrumentedAttribute
87 from .base import Mapped
88 from .context import _MapperEntity
89 from .context import _ORMCompileState
90 from .context import QueryContext
91 from .decl_api import RegistryType
92 from .decl_base import _ClassScanAbstractConfig
93 from .decl_base import _DeclarativeMapperConfig
94 from .loading import _PopulatorDict
95 from .mapper import Mapper
96 from .path_registry import _AbstractEntityRegistry
97 from .query import Query
98 from .session import Session
99 from .state import InstanceState
100 from .strategy_options import _LoadElement
101 from .util import AliasedInsp
102 from .util import ORMAdapter
103 from ..engine.result import Result
104 from ..sql._typing import _ColumnExpressionArgument
105 from ..sql._typing import _ColumnsClauseArgument
106 from ..sql._typing import _DMLColumnArgument
107 from ..sql._typing import _InfoType
108 from ..sql.operators import OperatorType
109 from ..sql.visitors import _TraverseInternalsType
110 from ..util.typing import _AnnotationScanType
111
112_StrategyKey = Tuple[Any, ...]
113
114_T = TypeVar("_T", bound=Any)
115_T_co = TypeVar("_T_co", bound=Any, covariant=True)
116
117_TLS = TypeVar("_TLS", bound="Type[LoaderStrategy]")
118
119
120class ORMStatementRole(roles.StatementRole):
121 __slots__ = ()
122 _role_name = (
123 "Executable SQL or text() construct, including ORM aware objects"
124 )
125
126
127class ORMColumnsClauseRole(
128 roles.ColumnsClauseRole, roles.TypedColumnsClauseRole[_T]
129):
130 __slots__ = ()
131 _role_name = "ORM mapped entity, aliased entity, or Column expression"
132
133
134class ORMEntityColumnsClauseRole(ORMColumnsClauseRole[_T]):
135 __slots__ = ()
136 _role_name = "ORM mapped or aliased entity"
137
138
139class ORMFromClauseRole(roles.FromClauseRole):
140 __slots__ = ()
141 _role_name = "ORM mapped entity, aliased entity, or FROM expression"
142
143
144class ORMColumnDescription(TypedDict):
145 name: str
146 # TODO: add python_type and sql_type here; combining them
147 # into "type" is a bad idea
148 type: Union[Type[Any], TypeEngine[Any]]
149 aliased: bool
150 expr: _ColumnsClauseArgument[Any]
151 entity: Optional[_ColumnsClauseArgument[Any]]
152
153
154class _IntrospectsAnnotations:
155 __slots__ = ()
156
157 @classmethod
158 def _mapper_property_name(cls) -> str:
159 return cls.__name__
160
161 def found_in_pep593_annotated(self) -> Any:
162 """return a copy of this object to use in declarative when the
163 object is found inside of an Annotated object."""
164
165 raise NotImplementedError(
166 f"Use of the {self._mapper_property_name()!r} "
167 "construct inside of an Annotated object is not yet supported."
168 )
169
170 def declarative_scan(
171 self,
172 decl_scan: _DeclarativeMapperConfig,
173 registry: RegistryType,
174 cls: Type[Any],
175 originating_module: Optional[str],
176 key: str,
177 mapped_container: Optional[Type[Mapped[Any]]],
178 annotation: Optional[_AnnotationScanType],
179 extracted_mapped_annotation: Optional[_AnnotationScanType],
180 is_dataclass_field: bool,
181 ) -> None:
182 """Perform class-specific initialization at early declarative scanning
183 time.
184
185 .. versionadded:: 2.0
186
187 """
188
189 def _raise_for_required(self, key: str, cls: Type[Any]) -> NoReturn:
190 raise sa_exc.ArgumentError(
191 f"Python typing annotation is required for attribute "
192 f'"{cls.__name__}.{key}" when primary argument(s) for '
193 f'"{self._mapper_property_name()}" '
194 "construct are None or not present"
195 )
196
197
198class _DataclassArguments(TypedDict):
199 """define arguments that can be passed to ORM Annotated Dataclass
200 class definitions.
201
202 """
203
204 init: Union[_NoArg, bool]
205 repr: Union[_NoArg, bool]
206 eq: Union[_NoArg, bool]
207 order: Union[_NoArg, bool]
208 unsafe_hash: Union[_NoArg, bool]
209 match_args: Union[_NoArg, bool]
210 kw_only: Union[_NoArg, bool]
211 dataclass_callable: Union[_NoArg, Callable[..., Type[Any]]]
212
213
214class _AttributeOptions(NamedTuple):
215 """define Python-local attribute behavior options common to all
216 :class:`.MapperProperty` objects.
217
218 Currently this includes dataclass-generation arguments.
219
220 .. versionadded:: 2.0
221
222 """
223
224 dataclasses_init: Union[_NoArg, bool]
225 dataclasses_repr: Union[_NoArg, bool]
226 dataclasses_default: Union[_NoArg, Any]
227 dataclasses_default_factory: Union[_NoArg, Callable[[], Any]]
228 dataclasses_compare: Union[_NoArg, bool]
229 dataclasses_kw_only: Union[_NoArg, bool]
230 dataclasses_hash: Union[_NoArg, bool, None]
231 dataclasses_dataclass_metadata: Union[_NoArg, Mapping[Any, Any], None]
232
233 def _as_dataclass_field(
234 self, key: str, dataclass_setup_arguments: _DataclassArguments
235 ) -> Any:
236 """Return a ``dataclasses.Field`` object given these arguments."""
237
238 kw: Dict[str, Any] = {}
239 if self.dataclasses_default_factory is not _NoArg.NO_ARG:
240 kw["default_factory"] = self.dataclasses_default_factory
241 if self.dataclasses_default is not _NoArg.NO_ARG:
242 kw["default"] = self.dataclasses_default
243 if self.dataclasses_init is not _NoArg.NO_ARG:
244 kw["init"] = self.dataclasses_init
245 if self.dataclasses_repr is not _NoArg.NO_ARG:
246 kw["repr"] = self.dataclasses_repr
247 if self.dataclasses_compare is not _NoArg.NO_ARG:
248 kw["compare"] = self.dataclasses_compare
249 if self.dataclasses_kw_only is not _NoArg.NO_ARG:
250 kw["kw_only"] = self.dataclasses_kw_only
251 if self.dataclasses_hash is not _NoArg.NO_ARG:
252 kw["hash"] = self.dataclasses_hash
253 if self.dataclasses_dataclass_metadata is not _NoArg.NO_ARG:
254 kw["metadata"] = self.dataclasses_dataclass_metadata
255
256 if "default" in kw and callable(kw["default"]):
257 # callable defaults are ambiguous. deprecate them in favour of
258 # insert_default or default_factory. #9936
259 warn_deprecated(
260 f"Callable object passed to the ``default`` parameter for "
261 f"attribute {key!r} in a ORM-mapped Dataclasses context is "
262 "ambiguous, "
263 "and this use will raise an error in a future release. "
264 "If this callable is intended to produce Core level INSERT "
265 "default values for an underlying ``Column``, use "
266 "the ``mapped_column.insert_default`` parameter instead. "
267 "To establish this callable as providing a default value "
268 "for instances of the dataclass itself, use the "
269 "``default_factory`` dataclasses parameter.",
270 "2.0",
271 )
272
273 if (
274 "init" in kw
275 and not kw["init"]
276 and "default" in kw
277 and not callable(kw["default"]) # ignore callable defaults. #9936
278 and "default_factory" not in kw # illegal but let dc.field raise
279 ):
280 # fix for #9879
281 default = kw.pop("default")
282 kw["default_factory"] = lambda: default
283
284 return dataclasses.field(**kw)
285
286 @classmethod
287 def _get_arguments_for_make_dataclass(
288 cls,
289 decl_scan: _ClassScanAbstractConfig,
290 key: str,
291 annotation: _AnnotationScanType,
292 mapped_container: Optional[Any],
293 elem: Any,
294 dataclass_setup_arguments: _DataclassArguments,
295 enable_descriptor_defaults: bool,
296 ) -> Union[
297 Tuple[str, _AnnotationScanType],
298 Tuple[str, _AnnotationScanType, dataclasses.Field[Any] | None],
299 ]:
300 """given attribute key, annotation, and value from a class, return
301 the argument tuple we would pass to dataclasses.make_dataclass()
302 for this attribute.
303
304 """
305 if isinstance(elem, _DCAttributeOptions):
306 attribute_options = elem._get_dataclass_setup_options(
307 decl_scan,
308 key,
309 dataclass_setup_arguments,
310 enable_descriptor_defaults,
311 )
312 dc_field = attribute_options._as_dataclass_field(
313 key, dataclass_setup_arguments
314 )
315
316 return (key, annotation, dc_field)
317 elif elem is not _NoArg.NO_ARG:
318 # why is typing not erroring on this?
319 return (key, annotation, elem)
320 elif mapped_container is not None:
321 # it's Mapped[], but there's no "element", which means declarative
322 # did not actually do anything for this field.
323 # prior to 2.1, this would never happen and we had a false
324 # assertion here, because the mapper _scan_attributes always
325 # generates a MappedColumn when one is not present
326 # (see issue #8718). However, in 2.1 we handle this case for the
327 # non-mapped dataclass use case without the need to generate
328 # MappedColumn that gets thrown away anyway.
329 return (key, annotation)
330
331 else:
332 # plain dataclass field, not mapped. Is only possible
333 # if __allow_unmapped__ is set up. I can see this mode causing
334 # problems...
335 return (key, annotation)
336
337
338_DEFAULT_ATTRIBUTE_OPTIONS = _AttributeOptions(
339 _NoArg.NO_ARG,
340 _NoArg.NO_ARG,
341 _NoArg.NO_ARG,
342 _NoArg.NO_ARG,
343 _NoArg.NO_ARG,
344 _NoArg.NO_ARG,
345 _NoArg.NO_ARG,
346 _NoArg.NO_ARG,
347)
348
349_DEFAULT_READONLY_ATTRIBUTE_OPTIONS = _AttributeOptions(
350 False,
351 _NoArg.NO_ARG,
352 _NoArg.NO_ARG,
353 _NoArg.NO_ARG,
354 _NoArg.NO_ARG,
355 _NoArg.NO_ARG,
356 _NoArg.NO_ARG,
357 _NoArg.NO_ARG,
358)
359
360
361class _DCAttributeOptions:
362 """mixin for descriptors or configurational objects that include dataclass
363 field options.
364
365 This includes :class:`.MapperProperty`, :class:`._MapsColumn` within
366 the ORM, but also includes :class:`.AssociationProxy` within ext.
367 Can in theory be used for other descriptors that serve a similar role
368 as association proxy. (*maybe* hybrids, not sure yet.)
369
370 """
371
372 __slots__ = ()
373
374 _attribute_options: _AttributeOptions
375 """behavioral options for ORM-enabled Python attributes
376
377 .. versionadded:: 2.0
378
379 """
380
381 _has_dataclass_arguments: bool
382
383 def _get_dataclass_setup_options(
384 self,
385 decl_scan: _ClassScanAbstractConfig,
386 key: str,
387 dataclass_setup_arguments: _DataclassArguments,
388 enable_descriptor_defaults: bool,
389 ) -> _AttributeOptions:
390 return self._attribute_options
391
392
393class _DataclassDefaultsDontSet(_DCAttributeOptions):
394 __slots__ = ()
395
396 _default_scalar_value: Any
397
398 _disable_dataclass_default_factory: bool = False
399
400 def _get_dataclass_setup_options(
401 self,
402 decl_scan: _ClassScanAbstractConfig,
403 key: str,
404 dataclass_setup_arguments: _DataclassArguments,
405 enable_descriptor_defaults: bool,
406 ) -> _AttributeOptions:
407
408 disable_descriptor_defaults = (
409 not enable_descriptor_defaults
410 or getattr(decl_scan.cls, "_sa_disable_descriptor_defaults", False)
411 )
412
413 if disable_descriptor_defaults:
414 return self._attribute_options
415
416 dataclasses_default = self._attribute_options.dataclasses_default
417 dataclasses_default_factory = (
418 self._attribute_options.dataclasses_default_factory
419 )
420
421 if dataclasses_default is not _NoArg.NO_ARG and not callable(
422 dataclasses_default
423 ):
424 self._default_scalar_value = (
425 self._attribute_options.dataclasses_default
426 )
427 return self._attribute_options._replace(
428 dataclasses_default=DONT_SET,
429 )
430 elif (
431 self._disable_dataclass_default_factory
432 and dataclasses_default_factory is not _NoArg.NO_ARG
433 ):
434 return self._attribute_options._replace(
435 dataclasses_default=DONT_SET,
436 dataclasses_default_factory=_NoArg.NO_ARG,
437 )
438 return self._attribute_options
439
440
441class _MapsColumns(_DCAttributeOptions, _MappedAttribute[_T]):
442 """interface for declarative-capable construct that delivers one or more
443 Column objects to the declarative process to be part of a Table.
444 """
445
446 __slots__ = ()
447
448 @property
449 def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
450 """return a MapperProperty to be assigned to the declarative mapping"""
451 raise NotImplementedError()
452
453 @property
454 def columns_to_assign(self) -> List[Tuple[Column[_T], int]]:
455 """A list of Column objects that should be declaratively added to the
456 new Table object.
457
458 """
459 raise NotImplementedError()
460
461
462# NOTE: MapperProperty needs to extend _MappedAttribute so that declarative
463# typing works, i.e. "Mapped[A] = relationship()". This introduces an
464# inconvenience which is that all the MapperProperty objects are treated
465# as descriptors by typing tools, which are misled by this as assignment /
466# access to a descriptor attribute wants to move through __get__.
467# Therefore, references to MapperProperty as an instance variable, such
468# as in PropComparator, may have some special typing workarounds such as the
469# use of sqlalchemy.util.typing.DescriptorReference to avoid mis-interpretation
470# by typing tools
471@inspection._self_inspects
472class MapperProperty(
473 HasCacheKey,
474 _DCAttributeOptions,
475 _MappedAttribute[_T],
476 InspectionAttrInfo,
477 util.MemoizedSlots,
478):
479 """Represent a particular class attribute mapped by :class:`_orm.Mapper`.
480
481 The most common occurrences of :class:`.MapperProperty` are the
482 mapped :class:`_schema.Column`, which is represented in a mapping as
483 an instance of :class:`.ColumnProperty`,
484 and a reference to another class produced by :func:`_orm.relationship`,
485 represented in the mapping as an instance of
486 :class:`.Relationship`.
487
488 """
489
490 __slots__ = (
491 "_configure_started",
492 "_configure_finished",
493 "_attribute_options",
494 "_has_dataclass_arguments",
495 "parent",
496 "key",
497 "info",
498 "doc",
499 )
500
501 _cache_key_traversal: _TraverseInternalsType = [
502 ("parent", visitors.ExtendedInternalTraversal.dp_has_cache_key),
503 ("key", visitors.ExtendedInternalTraversal.dp_string),
504 ]
505
506 if not TYPE_CHECKING:
507 cascade = None
508
509 is_property = True
510 """Part of the InspectionAttr interface; states this object is a
511 mapper property.
512
513 """
514
515 comparator: PropComparator[_T]
516 """The :class:`_orm.PropComparator` instance that implements SQL
517 expression construction on behalf of this mapped attribute."""
518
519 key: str
520 """name of class attribute"""
521
522 parent: Mapper[Any]
523 """the :class:`.Mapper` managing this property."""
524
525 _is_relationship = False
526
527 _links_to_entity: bool
528 """True if this MapperProperty refers to a mapped entity.
529
530 Should only be True for Relationship, False for all others.
531
532 """
533
534 doc: Optional[str]
535 """optional documentation string"""
536
537 info: _InfoType
538 """Info dictionary associated with the object, allowing user-defined
539 data to be associated with this :class:`.InspectionAttr`.
540
541 The dictionary is generated when first accessed. Alternatively,
542 it can be specified as a constructor argument to the
543 :func:`.column_property`, :func:`_orm.relationship`, or :func:`.composite`
544 functions.
545
546 .. seealso::
547
548 :attr:`.QueryableAttribute.info`
549
550 :attr:`.SchemaItem.info`
551
552 """
553
554 def _memoized_attr_info(self) -> _InfoType:
555 """Info dictionary associated with the object, allowing user-defined
556 data to be associated with this :class:`.InspectionAttr`.
557
558 The dictionary is generated when first accessed. Alternatively,
559 it can be specified as a constructor argument to the
560 :func:`.column_property`, :func:`_orm.relationship`, or
561 :func:`.composite`
562 functions.
563
564 .. seealso::
565
566 :attr:`.QueryableAttribute.info`
567
568 :attr:`.SchemaItem.info`
569
570 """
571 return {}
572
573 def setup(
574 self,
575 context: _ORMCompileState,
576 query_entity: _MapperEntity,
577 path: _AbstractEntityRegistry,
578 adapter: Optional[ORMAdapter],
579 **kwargs: Any,
580 ) -> None:
581 """Called by Query for the purposes of constructing a SQL statement.
582
583 Each MapperProperty associated with the target mapper processes the
584 statement referenced by the query context, adding columns and/or
585 criterion as appropriate.
586
587 """
588
589 def create_row_processor(
590 self,
591 context: _ORMCompileState,
592 query_entity: _MapperEntity,
593 path: _AbstractEntityRegistry,
594 mapper: Mapper[Any],
595 result: Result[Unpack[TupleAny]],
596 adapter: Optional[ORMAdapter],
597 populators: _PopulatorDict,
598 ) -> None:
599 """Produce row processing functions and append to the given
600 set of populators lists.
601
602 """
603
604 def cascade_iterator(
605 self,
606 type_: str,
607 state: InstanceState[Any],
608 dict_: _InstanceDict,
609 visited_states: Set[InstanceState[Any]],
610 halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None,
611 ) -> Iterator[
612 Tuple[object, Mapper[Any], InstanceState[Any], _InstanceDict]
613 ]:
614 """Iterate through instances related to the given instance for
615 a particular 'cascade', starting with this MapperProperty.
616
617 Return an iterator3-tuples (instance, mapper, state).
618
619 Note that the 'cascade' collection on this MapperProperty is
620 checked first for the given type before cascade_iterator is called.
621
622 This method typically only applies to Relationship.
623
624 """
625
626 return iter(())
627
628 def set_parent(self, parent: Mapper[Any], init: bool) -> None:
629 """Set the parent mapper that references this MapperProperty.
630
631 This method is overridden by some subclasses to perform extra
632 setup when the mapper is first known.
633
634 """
635 self.parent = parent
636
637 def instrument_class(self, mapper: Mapper[Any]) -> None:
638 """Hook called by the Mapper to the property to initiate
639 instrumentation of the class attribute managed by this
640 MapperProperty.
641
642 The MapperProperty here will typically call out to the
643 attributes module to set up an InstrumentedAttribute.
644
645 This step is the first of two steps to set up an InstrumentedAttribute,
646 and is called early in the mapper setup process.
647
648 The second step is typically the init_class_attribute step,
649 called from StrategizedProperty via the post_instrument_class()
650 hook. This step assigns additional state to the InstrumentedAttribute
651 (specifically the "impl") which has been determined after the
652 MapperProperty has determined what kind of persistence
653 management it needs to do (e.g. scalar, object, collection, etc).
654
655 """
656
657 def __init__(
658 self,
659 attribute_options: Optional[_AttributeOptions] = None,
660 _assume_readonly_dc_attributes: bool = False,
661 ) -> None:
662 self._configure_started = False
663 self._configure_finished = False
664
665 if _assume_readonly_dc_attributes:
666 default_attrs = _DEFAULT_READONLY_ATTRIBUTE_OPTIONS
667 else:
668 default_attrs = _DEFAULT_ATTRIBUTE_OPTIONS
669
670 if attribute_options and attribute_options != default_attrs:
671 self._has_dataclass_arguments = True
672 self._attribute_options = attribute_options
673 else:
674 self._has_dataclass_arguments = False
675 self._attribute_options = default_attrs
676
677 def init(self) -> None:
678 """Called after all mappers are created to assemble
679 relationships between mappers and perform other post-mapper-creation
680 initialization steps.
681
682
683 """
684 self._configure_started = True
685 self.do_init()
686 self._configure_finished = True
687
688 @property
689 def class_attribute(self) -> InstrumentedAttribute[_T]:
690 """Return the class-bound descriptor corresponding to this
691 :class:`.MapperProperty`.
692
693 This is basically a ``getattr()`` call::
694
695 return getattr(self.parent.class_, self.key)
696
697 I.e. if this :class:`.MapperProperty` were named ``addresses``,
698 and the class to which it is mapped is ``User``, this sequence
699 is possible::
700
701 >>> from sqlalchemy import inspect
702 >>> mapper = inspect(User)
703 >>> addresses_property = mapper.attrs.addresses
704 >>> addresses_property.class_attribute is User.addresses
705 True
706 >>> User.addresses.property is addresses_property
707 True
708
709
710 """
711
712 return getattr(self.parent.class_, self.key) # type: ignore[no-any-return] # noqa: E501
713
714 def do_init(self) -> None:
715 """Perform subclass-specific initialization post-mapper-creation
716 steps.
717
718 This is a template method called by the ``MapperProperty``
719 object's init() method.
720
721 """
722
723 def post_instrument_class(self, mapper: Mapper[Any]) -> None:
724 """Perform instrumentation adjustments that need to occur
725 after init() has completed.
726
727 The given Mapper is the Mapper invoking the operation, which
728 may not be the same Mapper as self.parent in an inheritance
729 scenario; however, Mapper will always at least be a sub-mapper of
730 self.parent.
731
732 This method is typically used by StrategizedProperty, which delegates
733 it to LoaderStrategy.init_class_attribute() to perform final setup
734 on the class-bound InstrumentedAttribute.
735
736 """
737
738 def merge(
739 self,
740 session: Session,
741 source_state: InstanceState[Any],
742 source_dict: _InstanceDict,
743 dest_state: InstanceState[Any],
744 dest_dict: _InstanceDict,
745 load: bool,
746 _recursive: Dict[Any, object],
747 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
748 ) -> None:
749 """Merge the attribute represented by this ``MapperProperty``
750 from source to destination object.
751
752 """
753
754 def __repr__(self) -> str:
755 return "<%s at 0x%x; %s>" % (
756 self.__class__.__name__,
757 id(self),
758 getattr(self, "key", "no key"),
759 )
760
761 def path_string(self) -> str:
762 """Return a user-facing name for this :class:`.MapperProperty`,
763 for use in a :class:`_orm.PathRegistry` string representation.
764
765 """
766 return f"{self.parent.class_.__name__}.{self.key}"
767
768
769@inspection._self_inspects
770class PropComparator(SQLORMOperations[_T_co], Generic[_T_co], ColumnOperators):
771 r"""Defines SQL operations for ORM mapped attributes.
772
773 SQLAlchemy allows for operators to
774 be redefined at both the Core and ORM level. :class:`.PropComparator`
775 is the base class of operator redefinition for ORM-level operations,
776 including those of :class:`.ColumnProperty`,
777 :class:`.Relationship`, and :class:`.Composite`.
778
779 User-defined subclasses of :class:`.PropComparator` may be created. The
780 built-in Python comparison and math operator methods, such as
781 :meth:`.operators.ColumnOperators.__eq__`,
782 :meth:`.operators.ColumnOperators.__lt__`, and
783 :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide
784 new operator behavior. The custom :class:`.PropComparator` is passed to
785 the :class:`.MapperProperty` instance via the ``comparator_factory``
786 argument. In each case,
787 the appropriate subclass of :class:`.PropComparator` should be used::
788
789 # definition of custom PropComparator subclasses
790
791 from sqlalchemy.orm.properties import (
792 ColumnProperty,
793 Composite,
794 Relationship,
795 )
796
797
798 class MyColumnComparator(ColumnProperty.Comparator):
799 def __eq__(self, other):
800 return self.__clause_element__() == other
801
802
803 class MyRelationshipComparator(Relationship.Comparator):
804 def any(self, expression):
805 "define the 'any' operation"
806 # ...
807
808
809 class MyCompositeComparator(Composite.Comparator):
810 def __gt__(self, other):
811 "redefine the 'greater than' operation"
812
813 return sql.and_(
814 *[
815 a > b
816 for a, b in zip(
817 self.__clause_element__().clauses,
818 other.__composite_values__(),
819 )
820 ]
821 )
822
823
824 # application of custom PropComparator subclasses
825
826 from sqlalchemy.orm import column_property, relationship, composite
827 from sqlalchemy import Column, String
828
829
830 class SomeMappedClass(Base):
831 some_column = column_property(
832 Column("some_column", String),
833 comparator_factory=MyColumnComparator,
834 )
835
836 some_relationship = relationship(
837 SomeOtherClass, comparator_factory=MyRelationshipComparator
838 )
839
840 some_composite = composite(
841 Column("a", String),
842 Column("b", String),
843 comparator_factory=MyCompositeComparator,
844 )
845
846 Note that for column-level operator redefinition, it's usually
847 simpler to define the operators at the Core level, using the
848 :attr:`.TypeEngine.comparator_factory` attribute. See
849 :ref:`types_operators` for more detail.
850
851 .. seealso::
852
853 :class:`.ColumnProperty.Comparator`
854
855 :class:`.Relationship.Comparator`
856
857 :class:`.Composite.Comparator`
858
859 :class:`.ColumnOperators`
860
861 :ref:`types_operators`
862
863 :attr:`.TypeEngine.comparator_factory`
864
865 """
866
867 __slots__ = "prop", "_parententity", "_adapt_to_entity"
868
869 __visit_name__ = "orm_prop_comparator"
870
871 _parententity: _InternalEntityType[Any]
872 _adapt_to_entity: Optional[AliasedInsp[Any]]
873 prop: RODescriptorReference[MapperProperty[_T_co]]
874
875 def __init__(
876 self,
877 prop: MapperProperty[_T],
878 parentmapper: _InternalEntityType[Any],
879 adapt_to_entity: Optional[AliasedInsp[Any]] = None,
880 ):
881 self.prop = prop
882 self._parententity = adapt_to_entity or parentmapper
883 self._adapt_to_entity = adapt_to_entity
884
885 @util.non_memoized_property
886 def property(self) -> MapperProperty[_T_co]:
887 """Return the :class:`.MapperProperty` associated with this
888 :class:`.PropComparator`.
889
890
891 Return values here will commonly be instances of
892 :class:`.ColumnProperty` or :class:`.Relationship`.
893
894
895 """
896 return self.prop
897
898 def __clause_element__(self) -> roles.ColumnsClauseRole:
899 raise NotImplementedError("%r" % self)
900
901 def _bulk_update_tuples(
902 self, value: Any
903 ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
904 """Receive a SQL expression that represents a value in the SET
905 clause of an UPDATE statement.
906
907 Return a tuple that can be passed to a :class:`_expression.Update`
908 construct.
909
910 """
911
912 return [(cast("_DMLColumnArgument", self.__clause_element__()), value)]
913
914 def _bulk_dml_setter(self, key: str) -> Optional[Callable[..., Any]]:
915 """return a callable that will process a bulk INSERT value"""
916
917 return None
918
919 def adapt_to_entity(
920 self, adapt_to_entity: AliasedInsp[Any]
921 ) -> PropComparator[_T_co]:
922 """Return a copy of this PropComparator which will use the given
923 :class:`.AliasedInsp` to produce corresponding expressions.
924 """
925 return self.__class__(self.prop, self._parententity, adapt_to_entity)
926
927 @util.ro_non_memoized_property
928 def _parentmapper(self) -> Mapper[Any]:
929 """legacy; this is renamed to _parententity to be
930 compatible with QueryableAttribute."""
931 return self._parententity.mapper
932
933 def _criterion_exists(
934 self,
935 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
936 **kwargs: Any,
937 ) -> ColumnElement[Any]:
938 return self.prop.comparator._criterion_exists(criterion, **kwargs)
939
940 @util.ro_non_memoized_property
941 def adapter(self) -> Optional[_ORMAdapterProto]:
942 """Produce a callable that adapts column expressions
943 to suit an aliased version of this comparator.
944
945 """
946 if self._adapt_to_entity is None:
947 return None
948 else:
949 return self._adapt_to_entity._orm_adapt_element
950
951 @util.ro_non_memoized_property
952 def info(self) -> _InfoType:
953 return self.prop.info
954
955 @staticmethod
956 def _any_op(a: Any, b: Any, **kwargs: Any) -> Any:
957 return a.any(b, **kwargs)
958
959 @staticmethod
960 def _has_op(left: Any, other: Any, **kwargs: Any) -> Any:
961 return left.has(other, **kwargs)
962
963 @staticmethod
964 def _of_type_op(a: Any, class_: Any) -> Any:
965 return a.of_type(class_)
966
967 any_op = cast(operators.OperatorType, _any_op)
968 has_op = cast(operators.OperatorType, _has_op)
969 of_type_op = cast(operators.OperatorType, _of_type_op)
970
971 if typing.TYPE_CHECKING:
972
973 def operate(
974 self, op: OperatorType, *other: Any, **kwargs: Any
975 ) -> ColumnElement[Any]: ...
976
977 def reverse_operate(
978 self, op: OperatorType, other: Any, **kwargs: Any
979 ) -> ColumnElement[Any]: ...
980
981 def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T_co]:
982 r"""Redefine this object in terms of a polymorphic subclass,
983 :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased`
984 construct.
985
986 Returns a new PropComparator from which further criterion can be
987 evaluated.
988
989 e.g.::
990
991 query.join(Company.employees.of_type(Engineer)).filter(
992 Engineer.name == "foo"
993 )
994
995 :param \class_: a class or mapper indicating that criterion will be
996 against this specific subclass.
997
998 .. seealso::
999
1000 :ref:`orm_queryguide_joining_relationships_aliased` - in the
1001 :ref:`queryguide_toplevel`
1002
1003 :ref:`inheritance_of_type`
1004
1005 """
1006
1007 return self.operate(PropComparator.of_type_op, class_) # type: ignore[return-value] # noqa: E501
1008
1009 def and_(
1010 self, *criteria: _ColumnExpressionArgument[bool]
1011 ) -> PropComparator[bool]:
1012 """Add additional criteria to the ON clause that's represented by this
1013 relationship attribute.
1014
1015 E.g.::
1016
1017
1018 stmt = select(User).join(
1019 User.addresses.and_(Address.email_address != "foo")
1020 )
1021
1022 stmt = select(User).options(
1023 joinedload(User.addresses.and_(Address.email_address != "foo"))
1024 )
1025
1026 .. versionadded:: 1.4
1027
1028 .. seealso::
1029
1030 :ref:`orm_queryguide_join_on_augmented`
1031
1032 :ref:`loader_option_criteria`
1033
1034 :func:`.with_loader_criteria`
1035
1036 """
1037 return self.operate(operators.and_, *criteria) # type: ignore[return-value] # noqa: E501
1038
1039 def any(
1040 self,
1041 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
1042 **kwargs: Any,
1043 ) -> ColumnElement[bool]:
1044 r"""Return a SQL expression representing true if this element
1045 references a member which meets the given criterion.
1046
1047 The usual implementation of ``any()`` is
1048 :meth:`.Relationship.Comparator.any`.
1049
1050 :param criterion: an optional ClauseElement formulated against the
1051 member class' table or attributes.
1052
1053 :param \**kwargs: key/value pairs corresponding to member class
1054 attribute names which will be compared via equality to the
1055 corresponding values.
1056
1057 """
1058
1059 return self.operate(PropComparator.any_op, criterion, **kwargs)
1060
1061 def has(
1062 self,
1063 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
1064 **kwargs: Any,
1065 ) -> ColumnElement[bool]:
1066 r"""Return a SQL expression representing true if this element
1067 references a member which meets the given criterion.
1068
1069 The usual implementation of ``has()`` is
1070 :meth:`.Relationship.Comparator.has`.
1071
1072 :param criterion: an optional ClauseElement formulated against the
1073 member class' table or attributes.
1074
1075 :param \**kwargs: key/value pairs corresponding to member class
1076 attribute names which will be compared via equality to the
1077 corresponding values.
1078
1079 """
1080
1081 return self.operate(PropComparator.has_op, criterion, **kwargs)
1082
1083
1084class StrategizedProperty(MapperProperty[_T]):
1085 """A MapperProperty which uses selectable strategies to affect
1086 loading behavior.
1087
1088 There is a single strategy selected by default. Alternate
1089 strategies can be selected at Query time through the usage of
1090 ``StrategizedOption`` objects via the Query.options() method.
1091
1092 The mechanics of StrategizedProperty are used for every Query
1093 invocation for every mapped attribute participating in that Query,
1094 to determine first how the attribute will be rendered in SQL
1095 and secondly how the attribute will retrieve a value from a result
1096 row and apply it to a mapped object. The routines here are very
1097 performance-critical.
1098
1099 """
1100
1101 __slots__ = (
1102 "_strategies",
1103 "strategy",
1104 "_wildcard_token",
1105 "_default_path_loader_key",
1106 "strategy_key",
1107 )
1108 inherit_cache = True
1109 strategy_wildcard_key: ClassVar[str]
1110
1111 strategy_key: _StrategyKey
1112
1113 _strategies: Dict[_StrategyKey, LoaderStrategy]
1114
1115 def _memoized_attr__wildcard_token(self) -> Tuple[str]:
1116 return (
1117 f"{self.strategy_wildcard_key}:{path_registry._WILDCARD_TOKEN}",
1118 )
1119
1120 def _memoized_attr__default_path_loader_key(
1121 self,
1122 ) -> Tuple[str, Tuple[str]]:
1123 return (
1124 "loader",
1125 (f"{self.strategy_wildcard_key}:{path_registry._DEFAULT_TOKEN}",),
1126 )
1127
1128 def _get_context_loader(
1129 self, context: _ORMCompileState, path: _AbstractEntityRegistry
1130 ) -> Optional[_LoadElement]:
1131 load: Optional[_LoadElement] = None
1132
1133 search_path = path[self]
1134
1135 # search among: exact match, "attr.*", "default" strategy
1136 # if any.
1137 for path_key in (
1138 search_path._loader_key,
1139 search_path._wildcard_path_loader_key,
1140 search_path._default_path_loader_key,
1141 ):
1142 if path_key in context.attributes:
1143 load = context.attributes[path_key]
1144 break
1145
1146 # note that if strategy_options.Load is placing non-actionable
1147 # objects in the context like defaultload(), we would
1148 # need to continue the loop here if we got such an
1149 # option as below.
1150 # if load.strategy or load.local_opts:
1151 # break
1152
1153 return load
1154
1155 def _get_strategy(self, key: _StrategyKey) -> LoaderStrategy:
1156 try:
1157 return self._strategies[key]
1158 except KeyError:
1159 pass
1160
1161 # run outside to prevent transfer of exception context
1162 cls = self._strategy_lookup(self, *key)
1163 # this previously was setting self._strategies[cls], that's
1164 # a bad idea; should use strategy key at all times because every
1165 # strategy has multiple keys at this point
1166 self._strategies[key] = strategy = cls(self, key)
1167 return strategy
1168
1169 def setup(
1170 self,
1171 context: _ORMCompileState,
1172 query_entity: _MapperEntity,
1173 path: _AbstractEntityRegistry,
1174 adapter: Optional[ORMAdapter],
1175 **kwargs: Any,
1176 ) -> None:
1177 loader = self._get_context_loader(context, path)
1178 if loader and loader.strategy:
1179 strat = self._get_strategy(loader.strategy)
1180 else:
1181 strat = self.strategy
1182 strat.setup_query(
1183 context, query_entity, path, loader, adapter, **kwargs
1184 )
1185
1186 def create_row_processor(
1187 self,
1188 context: _ORMCompileState,
1189 query_entity: _MapperEntity,
1190 path: _AbstractEntityRegistry,
1191 mapper: Mapper[Any],
1192 result: Result[Unpack[TupleAny]],
1193 adapter: Optional[ORMAdapter],
1194 populators: _PopulatorDict,
1195 ) -> None:
1196 loader = self._get_context_loader(context, path)
1197 if loader and loader.strategy:
1198 strat = self._get_strategy(loader.strategy)
1199 else:
1200 strat = self.strategy
1201 strat.create_row_processor(
1202 context,
1203 query_entity,
1204 path,
1205 loader,
1206 mapper,
1207 result,
1208 adapter,
1209 populators,
1210 )
1211
1212 def do_init(self) -> None:
1213 self._strategies = {}
1214 self.strategy = self._get_strategy(self.strategy_key)
1215
1216 def post_instrument_class(self, mapper: Mapper[Any]) -> None:
1217 if not mapper.class_manager._attr_has_impl(self.key):
1218 self.strategy.init_class_attribute(mapper)
1219
1220 _all_strategies: collections.defaultdict[
1221 Type[MapperProperty[Any]], Dict[_StrategyKey, Type[LoaderStrategy]]
1222 ] = collections.defaultdict(dict)
1223
1224 @classmethod
1225 def strategy_for(cls, **kw: Any) -> Callable[[_TLS], _TLS]:
1226 def decorate(dec_cls: _TLS) -> _TLS:
1227 # ensure each subclass of the strategy has its
1228 # own _strategy_keys collection
1229 if "_strategy_keys" not in dec_cls.__dict__:
1230 dec_cls._strategy_keys = []
1231 key = tuple(sorted(kw.items()))
1232 cls._all_strategies[cls][key] = dec_cls
1233 dec_cls._strategy_keys.append(key)
1234 return dec_cls
1235
1236 return decorate
1237
1238 @classmethod
1239 def _strategy_lookup(
1240 cls, requesting_property: MapperProperty[Any], *key: Any
1241 ) -> Type[LoaderStrategy]:
1242 requesting_property.parent._with_polymorphic_mappers
1243
1244 for prop_cls in cls.__mro__:
1245 if prop_cls in cls._all_strategies:
1246 if TYPE_CHECKING:
1247 assert issubclass(prop_cls, MapperProperty)
1248 strategies = cls._all_strategies[prop_cls]
1249 try:
1250 return strategies[key]
1251 except KeyError:
1252 pass
1253
1254 for property_type, strats in cls._all_strategies.items():
1255 if key in strats:
1256 intended_property_type = property_type
1257 actual_strategy = strats[key]
1258 break
1259 else:
1260 intended_property_type = None
1261 actual_strategy = None
1262
1263 raise orm_exc.LoaderStrategyException(
1264 cls,
1265 requesting_property,
1266 intended_property_type,
1267 actual_strategy,
1268 key,
1269 )
1270
1271
1272class ORMOption(ExecutableOption):
1273 """Base class for option objects that are passed to ORM queries.
1274
1275 These options may be consumed by :meth:`.Query.options`,
1276 :meth:`.Select.options`, or in a more general sense by any
1277 :meth:`.Executable.options` method. They are interpreted at
1278 statement compile time or execution time in modern use. The
1279 deprecated :class:`.MapperOption` is consumed at ORM query construction
1280 time.
1281
1282 .. versionadded:: 1.4
1283
1284 """
1285
1286 __slots__ = ()
1287
1288 _is_legacy_option = False
1289
1290 propagate_to_loaders = False
1291 """if True, indicate this option should be carried along
1292 to "secondary" SELECT statements that occur for relationship
1293 lazy loaders as well as attribute load / refresh operations.
1294
1295 """
1296
1297 _is_core = False
1298
1299 _is_user_defined = False
1300
1301 _is_compile_state = False
1302
1303 _is_criteria_option = False
1304
1305 _is_strategy_option = False
1306
1307 def _adapt_cached_option_to_uncached_option(
1308 self, context: QueryContext, uncached_opt: ORMOption
1309 ) -> ORMOption:
1310 """adapt this option to the "uncached" version of itself in a
1311 loader strategy context.
1312
1313 given "self" which is an option from a cached query, as well as the
1314 corresponding option from the uncached version of the same query,
1315 return the option we should use in a new query, in the context of a
1316 loader strategy being asked to load related rows on behalf of that
1317 cached query, which is assumed to be building a new query based on
1318 entities passed to us from the cached query.
1319
1320 Currently this routine chooses between "self" and "uncached" without
1321 manufacturing anything new. If the option is itself a loader strategy
1322 option which has a path, that path needs to match to the entities being
1323 passed to us by the cached query, so the :class:`_orm.Load` subclass
1324 overrides this to return "self". For all other options, we return the
1325 uncached form which may have changing state, such as a
1326 with_loader_criteria() option which will very often have new state.
1327
1328 This routine could in the future involve
1329 generating a new option based on both inputs if use cases arise,
1330 such as if with_loader_criteria() needed to match up to
1331 ``AliasedClass`` instances given in the parent query.
1332
1333 However, longer term it might be better to restructure things such that
1334 ``AliasedClass`` entities are always matched up on their cache key,
1335 instead of identity, in things like paths and such, so that this whole
1336 issue of "the uncached option does not match the entities" goes away.
1337 However this would make ``PathRegistry`` more complicated and difficult
1338 to debug as well as potentially less performant in that it would be
1339 hashing enormous cache keys rather than a simple AliasedInsp. UNLESS,
1340 we could get cache keys overall to be reliably hashed into something
1341 like an md5 key.
1342
1343 .. versionadded:: 1.4.41
1344
1345 """
1346 if uncached_opt is not None:
1347 return uncached_opt
1348 else:
1349 return self
1350
1351
1352class CompileStateOption(HasCacheKey, ORMOption):
1353 """base for :class:`.ORMOption` classes that affect the compilation of
1354 a SQL query and therefore need to be part of the cache key.
1355
1356 .. note:: :class:`.CompileStateOption` is generally non-public and
1357 should not be used as a base class for user-defined options; instead,
1358 use :class:`.UserDefinedOption`, which is easier to use as it does not
1359 interact with ORM compilation internals or caching.
1360
1361 :class:`.CompileStateOption` defines an internal attribute
1362 ``_is_compile_state=True`` which has the effect of the ORM compilation
1363 routines for SELECT and other statements will call upon these options when
1364 a SQL string is being compiled. As such, these classes implement
1365 :class:`.HasCacheKey` and need to provide robust ``_cache_key_traversal``
1366 structures.
1367
1368 The :class:`.CompileStateOption` class is used to implement the ORM
1369 :class:`.LoaderOption` and :class:`.CriteriaOption` classes.
1370
1371 .. versionadded:: 1.4.28
1372
1373
1374 """
1375
1376 __slots__ = ()
1377
1378 _is_compile_state = True
1379
1380 def process_compile_state(self, compile_state: _ORMCompileState) -> None:
1381 """Apply a modification to a given :class:`.ORMCompileState`.
1382
1383 This method is part of the implementation of a particular
1384 :class:`.CompileStateOption` and is only invoked internally
1385 when an ORM query is compiled.
1386
1387 """
1388
1389 def process_compile_state_replaced_entities(
1390 self,
1391 compile_state: _ORMCompileState,
1392 mapper_entities: Sequence[_MapperEntity],
1393 ) -> None:
1394 """Apply a modification to a given :class:`.ORMCompileState`,
1395 given entities that were replaced by with_only_columns() or
1396 with_entities().
1397
1398 This method is part of the implementation of a particular
1399 :class:`.CompileStateOption` and is only invoked internally
1400 when an ORM query is compiled.
1401
1402 .. versionadded:: 1.4.19
1403
1404 """
1405
1406
1407class LoaderOption(CompileStateOption):
1408 """Describe a loader modification to an ORM statement at compilation time.
1409
1410 .. versionadded:: 1.4
1411
1412 """
1413
1414 __slots__ = ()
1415
1416 def process_compile_state_replaced_entities(
1417 self,
1418 compile_state: _ORMCompileState,
1419 mapper_entities: Sequence[_MapperEntity],
1420 ) -> None:
1421 self.process_compile_state(compile_state)
1422
1423
1424class CriteriaOption(CompileStateOption):
1425 """Describe a WHERE criteria modification to an ORM statement at
1426 compilation time.
1427
1428 .. versionadded:: 1.4
1429
1430 """
1431
1432 __slots__ = ()
1433
1434 _is_criteria_option = True
1435
1436 def get_global_criteria(self, attributes: Dict[str, Any]) -> None:
1437 """update additional entity criteria options in the given
1438 attributes dictionary.
1439
1440 """
1441
1442
1443class UserDefinedOption(ORMOption):
1444 """Base class for a user-defined option that can be consumed from the
1445 :meth:`.SessionEvents.do_orm_execute` event hook.
1446
1447 """
1448
1449 __slots__ = ("payload",)
1450
1451 _is_legacy_option = False
1452
1453 _is_user_defined = True
1454
1455 propagate_to_loaders = False
1456 """if True, indicate this option should be carried along
1457 to "secondary" Query objects produced during lazy loads
1458 or refresh operations.
1459
1460 """
1461
1462 def __init__(self, payload: Optional[Any] = None):
1463 self.payload = payload
1464
1465
1466@util.deprecated_cls(
1467 "1.4",
1468 "The :class:`.MapperOption class is deprecated and will be removed "
1469 "in a future release. For "
1470 "modifications to queries on a per-execution basis, use the "
1471 ":class:`.UserDefinedOption` class to establish state within a "
1472 ":class:`.Query` or other Core statement, then use the "
1473 ":meth:`.SessionEvents.before_orm_execute` hook to consume them.",
1474 constructor=None,
1475)
1476class MapperOption(ORMOption):
1477 """Describe a modification to a Query"""
1478
1479 __slots__ = ()
1480
1481 _is_legacy_option = True
1482
1483 propagate_to_loaders = False
1484 """if True, indicate this option should be carried along
1485 to "secondary" Query objects produced during lazy loads
1486 or refresh operations.
1487
1488 """
1489
1490 def process_query(self, query: Query[Any]) -> None:
1491 """Apply a modification to the given :class:`_query.Query`."""
1492
1493 def process_query_conditionally(self, query: Query[Any]) -> None:
1494 """same as process_query(), except that this option may not
1495 apply to the given query.
1496
1497 This is typically applied during a lazy load or scalar refresh
1498 operation to propagate options stated in the original Query to the
1499 new Query being used for the load. It occurs for those options that
1500 specify propagate_to_loaders=True.
1501
1502 """
1503
1504 self.process_query(query)
1505
1506
1507class LoaderStrategy:
1508 """Describe the loading behavior of a StrategizedProperty object.
1509
1510 The ``LoaderStrategy`` interacts with the querying process in three
1511 ways:
1512
1513 * it controls the configuration of the ``InstrumentedAttribute``
1514 placed on a class to handle the behavior of the attribute. this
1515 may involve setting up class-level callable functions to fire
1516 off a select operation when the attribute is first accessed
1517 (i.e. a lazy load)
1518
1519 * it processes the ``QueryContext`` at statement construction time,
1520 where it can modify the SQL statement that is being produced.
1521 For example, simple column attributes will add their represented
1522 column to the list of selected columns, a joined eager loader
1523 may establish join clauses to add to the statement.
1524
1525 * It produces "row processor" functions at result fetching time.
1526 These "row processor" functions populate a particular attribute
1527 on a particular mapped instance.
1528
1529 """
1530
1531 __slots__ = (
1532 "parent_property",
1533 "is_class_level",
1534 "parent",
1535 "key",
1536 "strategy_key",
1537 "strategy_opts",
1538 )
1539
1540 _strategy_keys: ClassVar[List[_StrategyKey]]
1541
1542 def __init__(
1543 self, parent: MapperProperty[Any], strategy_key: _StrategyKey
1544 ):
1545 self.parent_property = parent
1546 self.is_class_level = False
1547 self.parent = self.parent_property.parent
1548 self.key = self.parent_property.key
1549 self.strategy_key = strategy_key
1550 self.strategy_opts = dict(strategy_key)
1551
1552 def init_class_attribute(self, mapper: Mapper[Any]) -> None:
1553 pass
1554
1555 def setup_query(
1556 self,
1557 compile_state: _ORMCompileState,
1558 query_entity: _MapperEntity,
1559 path: _AbstractEntityRegistry,
1560 loadopt: Optional[_LoadElement],
1561 adapter: Optional[ORMAdapter],
1562 **kwargs: Any,
1563 ) -> None:
1564 """Establish column and other state for a given QueryContext.
1565
1566 This method fulfills the contract specified by MapperProperty.setup().
1567
1568 StrategizedProperty delegates its setup() method
1569 directly to this method.
1570
1571 """
1572
1573 def create_row_processor(
1574 self,
1575 context: _ORMCompileState,
1576 query_entity: _MapperEntity,
1577 path: _AbstractEntityRegistry,
1578 loadopt: Optional[_LoadElement],
1579 mapper: Mapper[Any],
1580 result: Result[Unpack[TupleAny]],
1581 adapter: Optional[ORMAdapter],
1582 populators: _PopulatorDict,
1583 ) -> None:
1584 """Establish row processing functions for a given QueryContext.
1585
1586 This method fulfills the contract specified by
1587 MapperProperty.create_row_processor().
1588
1589 StrategizedProperty delegates its create_row_processor() method
1590 directly to this method.
1591
1592 """
1593
1594 def __str__(self) -> str:
1595 return str(self.parent_property)