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