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
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
762@inspection._self_inspects
763class PropComparator(SQLORMOperations[_T_co], Generic[_T_co], ColumnOperators):
764 r"""Defines SQL operations for ORM mapped attributes.
765
766 SQLAlchemy allows for operators to
767 be redefined at both the Core and ORM level. :class:`.PropComparator`
768 is the base class of operator redefinition for ORM-level operations,
769 including those of :class:`.ColumnProperty`,
770 :class:`.Relationship`, and :class:`.Composite`.
771
772 User-defined subclasses of :class:`.PropComparator` may be created. The
773 built-in Python comparison and math operator methods, such as
774 :meth:`.operators.ColumnOperators.__eq__`,
775 :meth:`.operators.ColumnOperators.__lt__`, and
776 :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide
777 new operator behavior. The custom :class:`.PropComparator` is passed to
778 the :class:`.MapperProperty` instance via the ``comparator_factory``
779 argument. In each case,
780 the appropriate subclass of :class:`.PropComparator` should be used::
781
782 # definition of custom PropComparator subclasses
783
784 from sqlalchemy.orm.properties import (
785 ColumnProperty,
786 Composite,
787 Relationship,
788 )
789
790
791 class MyColumnComparator(ColumnProperty.Comparator):
792 def __eq__(self, other):
793 return self.__clause_element__() == other
794
795
796 class MyRelationshipComparator(Relationship.Comparator):
797 def any(self, expression):
798 "define the 'any' operation"
799 # ...
800
801
802 class MyCompositeComparator(Composite.Comparator):
803 def __gt__(self, other):
804 "redefine the 'greater than' operation"
805
806 return sql.and_(
807 *[
808 a > b
809 for a, b in zip(
810 self.__clause_element__().clauses,
811 other.__composite_values__(),
812 )
813 ]
814 )
815
816
817 # application of custom PropComparator subclasses
818
819 from sqlalchemy.orm import column_property, relationship, composite
820 from sqlalchemy import Column, String
821
822
823 class SomeMappedClass(Base):
824 some_column = column_property(
825 Column("some_column", String),
826 comparator_factory=MyColumnComparator,
827 )
828
829 some_relationship = relationship(
830 SomeOtherClass, comparator_factory=MyRelationshipComparator
831 )
832
833 some_composite = composite(
834 Column("a", String),
835 Column("b", String),
836 comparator_factory=MyCompositeComparator,
837 )
838
839 Note that for column-level operator redefinition, it's usually
840 simpler to define the operators at the Core level, using the
841 :attr:`.TypeEngine.comparator_factory` attribute. See
842 :ref:`types_operators` for more detail.
843
844 .. seealso::
845
846 :class:`.ColumnProperty.Comparator`
847
848 :class:`.Relationship.Comparator`
849
850 :class:`.Composite.Comparator`
851
852 :class:`.ColumnOperators`
853
854 :ref:`types_operators`
855
856 :attr:`.TypeEngine.comparator_factory`
857
858 """
859
860 __slots__ = "prop", "_parententity", "_adapt_to_entity"
861
862 __visit_name__ = "orm_prop_comparator"
863
864 _parententity: _InternalEntityType[Any]
865 _adapt_to_entity: Optional[AliasedInsp[Any]]
866 prop: RODescriptorReference[MapperProperty[_T_co]]
867
868 def __init__(
869 self,
870 prop: MapperProperty[_T],
871 parentmapper: _InternalEntityType[Any],
872 adapt_to_entity: Optional[AliasedInsp[Any]] = None,
873 ):
874 self.prop = prop
875 self._parententity = adapt_to_entity or parentmapper
876 self._adapt_to_entity = adapt_to_entity
877
878 @util.non_memoized_property
879 def property(self) -> MapperProperty[_T_co]:
880 """Return the :class:`.MapperProperty` associated with this
881 :class:`.PropComparator`.
882
883
884 Return values here will commonly be instances of
885 :class:`.ColumnProperty` or :class:`.Relationship`.
886
887
888 """
889 return self.prop
890
891 def __clause_element__(self) -> roles.ColumnsClauseRole:
892 raise NotImplementedError("%r" % self)
893
894 def _bulk_update_tuples(
895 self, value: Any
896 ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
897 """Receive a SQL expression that represents a value in the SET
898 clause of an UPDATE statement.
899
900 Return a tuple that can be passed to a :class:`_expression.Update`
901 construct.
902
903 """
904
905 return [(cast("_DMLColumnArgument", self.__clause_element__()), value)]
906
907 def _bulk_dml_setter(self, key: str) -> Optional[Callable[..., Any]]:
908 """return a callable that will process a bulk INSERT value"""
909
910 return None
911
912 def adapt_to_entity(
913 self, adapt_to_entity: AliasedInsp[Any]
914 ) -> PropComparator[_T_co]:
915 """Return a copy of this PropComparator which will use the given
916 :class:`.AliasedInsp` to produce corresponding expressions.
917 """
918 return self.__class__(self.prop, self._parententity, adapt_to_entity)
919
920 @util.ro_non_memoized_property
921 def _parentmapper(self) -> Mapper[Any]:
922 """legacy; this is renamed to _parententity to be
923 compatible with QueryableAttribute."""
924 return self._parententity.mapper
925
926 def _criterion_exists(
927 self,
928 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
929 **kwargs: Any,
930 ) -> ColumnElement[Any]:
931 return self.prop.comparator._criterion_exists(criterion, **kwargs)
932
933 @util.ro_non_memoized_property
934 def adapter(self) -> Optional[_ORMAdapterProto]:
935 """Produce a callable that adapts column expressions
936 to suit an aliased version of this comparator.
937
938 """
939 if self._adapt_to_entity is None:
940 return None
941 else:
942 return self._adapt_to_entity._orm_adapt_element
943
944 @util.ro_non_memoized_property
945 def info(self) -> _InfoType:
946 return self.prop.info
947
948 @staticmethod
949 def _any_op(a: Any, b: Any, **kwargs: Any) -> Any:
950 return a.any(b, **kwargs)
951
952 @staticmethod
953 def _has_op(left: Any, other: Any, **kwargs: Any) -> Any:
954 return left.has(other, **kwargs)
955
956 @staticmethod
957 def _of_type_op(a: Any, class_: Any) -> Any:
958 return a.of_type(class_)
959
960 any_op = cast(operators.OperatorType, _any_op)
961 has_op = cast(operators.OperatorType, _has_op)
962 of_type_op = cast(operators.OperatorType, _of_type_op)
963
964 if typing.TYPE_CHECKING:
965
966 def operate(
967 self, op: OperatorType, *other: Any, **kwargs: Any
968 ) -> ColumnElement[Any]: ...
969
970 def reverse_operate(
971 self, op: OperatorType, other: Any, **kwargs: Any
972 ) -> ColumnElement[Any]: ...
973
974 def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T_co]:
975 r"""Redefine this object in terms of a polymorphic subclass,
976 :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased`
977 construct.
978
979 Returns a new PropComparator from which further criterion can be
980 evaluated.
981
982 e.g.::
983
984 query.join(Company.employees.of_type(Engineer)).filter(
985 Engineer.name == "foo"
986 )
987
988 :param \class_: a class or mapper indicating that criterion will be
989 against this specific subclass.
990
991 .. seealso::
992
993 :ref:`orm_queryguide_joining_relationships_aliased` - in the
994 :ref:`queryguide_toplevel`
995
996 :ref:`inheritance_of_type`
997
998 """
999
1000 return self.operate(PropComparator.of_type_op, class_) # type: ignore
1001
1002 def and_(
1003 self, *criteria: _ColumnExpressionArgument[bool]
1004 ) -> PropComparator[bool]:
1005 """Add additional criteria to the ON clause that's represented by this
1006 relationship attribute.
1007
1008 E.g.::
1009
1010
1011 stmt = select(User).join(
1012 User.addresses.and_(Address.email_address != "foo")
1013 )
1014
1015 stmt = select(User).options(
1016 joinedload(User.addresses.and_(Address.email_address != "foo"))
1017 )
1018
1019 .. versionadded:: 1.4
1020
1021 .. seealso::
1022
1023 :ref:`orm_queryguide_join_on_augmented`
1024
1025 :ref:`loader_option_criteria`
1026
1027 :func:`.with_loader_criteria`
1028
1029 """
1030 return self.operate(operators.and_, *criteria) # type: ignore
1031
1032 def any(
1033 self,
1034 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
1035 **kwargs: Any,
1036 ) -> ColumnElement[bool]:
1037 r"""Return a SQL expression representing true if this element
1038 references a member which meets the given criterion.
1039
1040 The usual implementation of ``any()`` is
1041 :meth:`.Relationship.Comparator.any`.
1042
1043 :param criterion: an optional ClauseElement formulated against the
1044 member class' table or attributes.
1045
1046 :param \**kwargs: key/value pairs corresponding to member class
1047 attribute names which will be compared via equality to the
1048 corresponding values.
1049
1050 """
1051
1052 return self.operate(PropComparator.any_op, criterion, **kwargs)
1053
1054 def has(
1055 self,
1056 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
1057 **kwargs: Any,
1058 ) -> ColumnElement[bool]:
1059 r"""Return a SQL expression representing true if this element
1060 references a member which meets the given criterion.
1061
1062 The usual implementation of ``has()`` is
1063 :meth:`.Relationship.Comparator.has`.
1064
1065 :param criterion: an optional ClauseElement formulated against the
1066 member class' table or attributes.
1067
1068 :param \**kwargs: key/value pairs corresponding to member class
1069 attribute names which will be compared via equality to the
1070 corresponding values.
1071
1072 """
1073
1074 return self.operate(PropComparator.has_op, criterion, **kwargs)
1075
1076
1077class StrategizedProperty(MapperProperty[_T]):
1078 """A MapperProperty which uses selectable strategies to affect
1079 loading behavior.
1080
1081 There is a single strategy selected by default. Alternate
1082 strategies can be selected at Query time through the usage of
1083 ``StrategizedOption`` objects via the Query.options() method.
1084
1085 The mechanics of StrategizedProperty are used for every Query
1086 invocation for every mapped attribute participating in that Query,
1087 to determine first how the attribute will be rendered in SQL
1088 and secondly how the attribute will retrieve a value from a result
1089 row and apply it to a mapped object. The routines here are very
1090 performance-critical.
1091
1092 """
1093
1094 __slots__ = (
1095 "_strategies",
1096 "strategy",
1097 "_wildcard_token",
1098 "_default_path_loader_key",
1099 "strategy_key",
1100 )
1101 inherit_cache = True
1102 strategy_wildcard_key: ClassVar[str]
1103
1104 strategy_key: _StrategyKey
1105
1106 _strategies: Dict[_StrategyKey, LoaderStrategy]
1107
1108 def _memoized_attr__wildcard_token(self) -> Tuple[str]:
1109 return (
1110 f"{self.strategy_wildcard_key}:{path_registry._WILDCARD_TOKEN}",
1111 )
1112
1113 def _memoized_attr__default_path_loader_key(
1114 self,
1115 ) -> Tuple[str, Tuple[str]]:
1116 return (
1117 "loader",
1118 (f"{self.strategy_wildcard_key}:{path_registry._DEFAULT_TOKEN}",),
1119 )
1120
1121 def _get_context_loader(
1122 self, context: _ORMCompileState, path: _AbstractEntityRegistry
1123 ) -> Optional[_LoadElement]:
1124 load: Optional[_LoadElement] = None
1125
1126 search_path = path[self]
1127
1128 # search among: exact match, "attr.*", "default" strategy
1129 # if any.
1130 for path_key in (
1131 search_path._loader_key,
1132 search_path._wildcard_path_loader_key,
1133 search_path._default_path_loader_key,
1134 ):
1135 if path_key in context.attributes:
1136 load = context.attributes[path_key]
1137 break
1138
1139 # note that if strategy_options.Load is placing non-actionable
1140 # objects in the context like defaultload(), we would
1141 # need to continue the loop here if we got such an
1142 # option as below.
1143 # if load.strategy or load.local_opts:
1144 # break
1145
1146 return load
1147
1148 def _get_strategy(self, key: _StrategyKey) -> LoaderStrategy:
1149 try:
1150 return self._strategies[key]
1151 except KeyError:
1152 pass
1153
1154 # run outside to prevent transfer of exception context
1155 cls = self._strategy_lookup(self, *key)
1156 # this previously was setting self._strategies[cls], that's
1157 # a bad idea; should use strategy key at all times because every
1158 # strategy has multiple keys at this point
1159 self._strategies[key] = strategy = cls(self, key)
1160 return strategy
1161
1162 def setup(
1163 self,
1164 context: _ORMCompileState,
1165 query_entity: _MapperEntity,
1166 path: _AbstractEntityRegistry,
1167 adapter: Optional[ORMAdapter],
1168 **kwargs: Any,
1169 ) -> None:
1170 loader = self._get_context_loader(context, path)
1171 if loader and loader.strategy:
1172 strat = self._get_strategy(loader.strategy)
1173 else:
1174 strat = self.strategy
1175 strat.setup_query(
1176 context, query_entity, path, loader, adapter, **kwargs
1177 )
1178
1179 def create_row_processor(
1180 self,
1181 context: _ORMCompileState,
1182 query_entity: _MapperEntity,
1183 path: _AbstractEntityRegistry,
1184 mapper: Mapper[Any],
1185 result: Result[Unpack[TupleAny]],
1186 adapter: Optional[ORMAdapter],
1187 populators: _PopulatorDict,
1188 ) -> None:
1189 loader = self._get_context_loader(context, path)
1190 if loader and loader.strategy:
1191 strat = self._get_strategy(loader.strategy)
1192 else:
1193 strat = self.strategy
1194 strat.create_row_processor(
1195 context,
1196 query_entity,
1197 path,
1198 loader,
1199 mapper,
1200 result,
1201 adapter,
1202 populators,
1203 )
1204
1205 def do_init(self) -> None:
1206 self._strategies = {}
1207 self.strategy = self._get_strategy(self.strategy_key)
1208
1209 def post_instrument_class(self, mapper: Mapper[Any]) -> None:
1210 if not mapper.class_manager._attr_has_impl(self.key):
1211 self.strategy.init_class_attribute(mapper)
1212
1213 _all_strategies: collections.defaultdict[
1214 Type[MapperProperty[Any]], Dict[_StrategyKey, Type[LoaderStrategy]]
1215 ] = collections.defaultdict(dict)
1216
1217 @classmethod
1218 def strategy_for(cls, **kw: Any) -> Callable[[_TLS], _TLS]:
1219 def decorate(dec_cls: _TLS) -> _TLS:
1220 # ensure each subclass of the strategy has its
1221 # own _strategy_keys collection
1222 if "_strategy_keys" not in dec_cls.__dict__:
1223 dec_cls._strategy_keys = []
1224 key = tuple(sorted(kw.items()))
1225 cls._all_strategies[cls][key] = dec_cls
1226 dec_cls._strategy_keys.append(key)
1227 return dec_cls
1228
1229 return decorate
1230
1231 @classmethod
1232 def _strategy_lookup(
1233 cls, requesting_property: MapperProperty[Any], *key: Any
1234 ) -> Type[LoaderStrategy]:
1235 requesting_property.parent._with_polymorphic_mappers
1236
1237 for prop_cls in cls.__mro__:
1238 if prop_cls in cls._all_strategies:
1239 if TYPE_CHECKING:
1240 assert issubclass(prop_cls, MapperProperty)
1241 strategies = cls._all_strategies[prop_cls]
1242 try:
1243 return strategies[key]
1244 except KeyError:
1245 pass
1246
1247 for property_type, strats in cls._all_strategies.items():
1248 if key in strats:
1249 intended_property_type = property_type
1250 actual_strategy = strats[key]
1251 break
1252 else:
1253 intended_property_type = None
1254 actual_strategy = None
1255
1256 raise orm_exc.LoaderStrategyException(
1257 cls,
1258 requesting_property,
1259 intended_property_type,
1260 actual_strategy,
1261 key,
1262 )
1263
1264
1265class ORMOption(ExecutableOption):
1266 """Base class for option objects that are passed to ORM queries.
1267
1268 These options may be consumed by :meth:`.Query.options`,
1269 :meth:`.Select.options`, or in a more general sense by any
1270 :meth:`.Executable.options` method. They are interpreted at
1271 statement compile time or execution time in modern use. The
1272 deprecated :class:`.MapperOption` is consumed at ORM query construction
1273 time.
1274
1275 .. versionadded:: 1.4
1276
1277 """
1278
1279 __slots__ = ()
1280
1281 _is_legacy_option = False
1282
1283 propagate_to_loaders = False
1284 """if True, indicate this option should be carried along
1285 to "secondary" SELECT statements that occur for relationship
1286 lazy loaders as well as attribute load / refresh operations.
1287
1288 """
1289
1290 _is_core = False
1291
1292 _is_user_defined = False
1293
1294 _is_compile_state = False
1295
1296 _is_criteria_option = False
1297
1298 _is_strategy_option = False
1299
1300 def _adapt_cached_option_to_uncached_option(
1301 self, context: QueryContext, uncached_opt: ORMOption
1302 ) -> ORMOption:
1303 """adapt this option to the "uncached" version of itself in a
1304 loader strategy context.
1305
1306 given "self" which is an option from a cached query, as well as the
1307 corresponding option from the uncached version of the same query,
1308 return the option we should use in a new query, in the context of a
1309 loader strategy being asked to load related rows on behalf of that
1310 cached query, which is assumed to be building a new query based on
1311 entities passed to us from the cached query.
1312
1313 Currently this routine chooses between "self" and "uncached" without
1314 manufacturing anything new. If the option is itself a loader strategy
1315 option which has a path, that path needs to match to the entities being
1316 passed to us by the cached query, so the :class:`_orm.Load` subclass
1317 overrides this to return "self". For all other options, we return the
1318 uncached form which may have changing state, such as a
1319 with_loader_criteria() option which will very often have new state.
1320
1321 This routine could in the future involve
1322 generating a new option based on both inputs if use cases arise,
1323 such as if with_loader_criteria() needed to match up to
1324 ``AliasedClass`` instances given in the parent query.
1325
1326 However, longer term it might be better to restructure things such that
1327 ``AliasedClass`` entities are always matched up on their cache key,
1328 instead of identity, in things like paths and such, so that this whole
1329 issue of "the uncached option does not match the entities" goes away.
1330 However this would make ``PathRegistry`` more complicated and difficult
1331 to debug as well as potentially less performant in that it would be
1332 hashing enormous cache keys rather than a simple AliasedInsp. UNLESS,
1333 we could get cache keys overall to be reliably hashed into something
1334 like an md5 key.
1335
1336 .. versionadded:: 1.4.41
1337
1338 """
1339 if uncached_opt is not None:
1340 return uncached_opt
1341 else:
1342 return self
1343
1344
1345class CompileStateOption(HasCacheKey, ORMOption):
1346 """base for :class:`.ORMOption` classes that affect the compilation of
1347 a SQL query and therefore need to be part of the cache key.
1348
1349 .. note:: :class:`.CompileStateOption` is generally non-public and
1350 should not be used as a base class for user-defined options; instead,
1351 use :class:`.UserDefinedOption`, which is easier to use as it does not
1352 interact with ORM compilation internals or caching.
1353
1354 :class:`.CompileStateOption` defines an internal attribute
1355 ``_is_compile_state=True`` which has the effect of the ORM compilation
1356 routines for SELECT and other statements will call upon these options when
1357 a SQL string is being compiled. As such, these classes implement
1358 :class:`.HasCacheKey` and need to provide robust ``_cache_key_traversal``
1359 structures.
1360
1361 The :class:`.CompileStateOption` class is used to implement the ORM
1362 :class:`.LoaderOption` and :class:`.CriteriaOption` classes.
1363
1364 .. versionadded:: 1.4.28
1365
1366
1367 """
1368
1369 __slots__ = ()
1370
1371 _is_compile_state = True
1372
1373 def process_compile_state(self, compile_state: _ORMCompileState) -> None:
1374 """Apply a modification to a given :class:`.ORMCompileState`.
1375
1376 This method is part of the implementation of a particular
1377 :class:`.CompileStateOption` and is only invoked internally
1378 when an ORM query is compiled.
1379
1380 """
1381
1382 def process_compile_state_replaced_entities(
1383 self,
1384 compile_state: _ORMCompileState,
1385 mapper_entities: Sequence[_MapperEntity],
1386 ) -> None:
1387 """Apply a modification to a given :class:`.ORMCompileState`,
1388 given entities that were replaced by with_only_columns() or
1389 with_entities().
1390
1391 This method is part of the implementation of a particular
1392 :class:`.CompileStateOption` and is only invoked internally
1393 when an ORM query is compiled.
1394
1395 .. versionadded:: 1.4.19
1396
1397 """
1398
1399
1400class LoaderOption(CompileStateOption):
1401 """Describe a loader modification to an ORM statement at compilation time.
1402
1403 .. versionadded:: 1.4
1404
1405 """
1406
1407 __slots__ = ()
1408
1409 def process_compile_state_replaced_entities(
1410 self,
1411 compile_state: _ORMCompileState,
1412 mapper_entities: Sequence[_MapperEntity],
1413 ) -> None:
1414 self.process_compile_state(compile_state)
1415
1416
1417class CriteriaOption(CompileStateOption):
1418 """Describe a WHERE criteria modification to an ORM statement at
1419 compilation time.
1420
1421 .. versionadded:: 1.4
1422
1423 """
1424
1425 __slots__ = ()
1426
1427 _is_criteria_option = True
1428
1429 def get_global_criteria(self, attributes: Dict[str, Any]) -> None:
1430 """update additional entity criteria options in the given
1431 attributes dictionary.
1432
1433 """
1434
1435
1436class UserDefinedOption(ORMOption):
1437 """Base class for a user-defined option that can be consumed from the
1438 :meth:`.SessionEvents.do_orm_execute` event hook.
1439
1440 """
1441
1442 __slots__ = ("payload",)
1443
1444 _is_legacy_option = False
1445
1446 _is_user_defined = True
1447
1448 propagate_to_loaders = False
1449 """if True, indicate this option should be carried along
1450 to "secondary" Query objects produced during lazy loads
1451 or refresh operations.
1452
1453 """
1454
1455 def __init__(self, payload: Optional[Any] = None):
1456 self.payload = payload
1457
1458
1459@util.deprecated_cls(
1460 "1.4",
1461 "The :class:`.MapperOption class is deprecated and will be removed "
1462 "in a future release. For "
1463 "modifications to queries on a per-execution basis, use the "
1464 ":class:`.UserDefinedOption` class to establish state within a "
1465 ":class:`.Query` or other Core statement, then use the "
1466 ":meth:`.SessionEvents.before_orm_execute` hook to consume them.",
1467 constructor=None,
1468)
1469class MapperOption(ORMOption):
1470 """Describe a modification to a Query"""
1471
1472 __slots__ = ()
1473
1474 _is_legacy_option = True
1475
1476 propagate_to_loaders = False
1477 """if True, indicate this option should be carried along
1478 to "secondary" Query objects produced during lazy loads
1479 or refresh operations.
1480
1481 """
1482
1483 def process_query(self, query: Query[Any]) -> None:
1484 """Apply a modification to the given :class:`_query.Query`."""
1485
1486 def process_query_conditionally(self, query: Query[Any]) -> None:
1487 """same as process_query(), except that this option may not
1488 apply to the given query.
1489
1490 This is typically applied during a lazy load or scalar refresh
1491 operation to propagate options stated in the original Query to the
1492 new Query being used for the load. It occurs for those options that
1493 specify propagate_to_loaders=True.
1494
1495 """
1496
1497 self.process_query(query)
1498
1499
1500class LoaderStrategy:
1501 """Describe the loading behavior of a StrategizedProperty object.
1502
1503 The ``LoaderStrategy`` interacts with the querying process in three
1504 ways:
1505
1506 * it controls the configuration of the ``InstrumentedAttribute``
1507 placed on a class to handle the behavior of the attribute. this
1508 may involve setting up class-level callable functions to fire
1509 off a select operation when the attribute is first accessed
1510 (i.e. a lazy load)
1511
1512 * it processes the ``QueryContext`` at statement construction time,
1513 where it can modify the SQL statement that is being produced.
1514 For example, simple column attributes will add their represented
1515 column to the list of selected columns, a joined eager loader
1516 may establish join clauses to add to the statement.
1517
1518 * It produces "row processor" functions at result fetching time.
1519 These "row processor" functions populate a particular attribute
1520 on a particular mapped instance.
1521
1522 """
1523
1524 __slots__ = (
1525 "parent_property",
1526 "is_class_level",
1527 "parent",
1528 "key",
1529 "strategy_key",
1530 "strategy_opts",
1531 )
1532
1533 _strategy_keys: ClassVar[List[_StrategyKey]]
1534
1535 def __init__(
1536 self, parent: MapperProperty[Any], strategy_key: _StrategyKey
1537 ):
1538 self.parent_property = parent
1539 self.is_class_level = False
1540 self.parent = self.parent_property.parent
1541 self.key = self.parent_property.key
1542 self.strategy_key = strategy_key
1543 self.strategy_opts = dict(strategy_key)
1544
1545 def init_class_attribute(self, mapper: Mapper[Any]) -> None:
1546 pass
1547
1548 def setup_query(
1549 self,
1550 compile_state: _ORMCompileState,
1551 query_entity: _MapperEntity,
1552 path: _AbstractEntityRegistry,
1553 loadopt: Optional[_LoadElement],
1554 adapter: Optional[ORMAdapter],
1555 **kwargs: Any,
1556 ) -> None:
1557 """Establish column and other state for a given QueryContext.
1558
1559 This method fulfills the contract specified by MapperProperty.setup().
1560
1561 StrategizedProperty delegates its setup() method
1562 directly to this method.
1563
1564 """
1565
1566 def create_row_processor(
1567 self,
1568 context: _ORMCompileState,
1569 query_entity: _MapperEntity,
1570 path: _AbstractEntityRegistry,
1571 loadopt: Optional[_LoadElement],
1572 mapper: Mapper[Any],
1573 result: Result[Unpack[TupleAny]],
1574 adapter: Optional[ORMAdapter],
1575 populators: _PopulatorDict,
1576 ) -> None:
1577 """Establish row processing functions for a given QueryContext.
1578
1579 This method fulfills the contract specified by
1580 MapperProperty.create_row_processor().
1581
1582 StrategizedProperty delegates its create_row_processor() method
1583 directly to this method.
1584
1585 """
1586
1587 def __str__(self) -> str:
1588 return str(self.parent_property)