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