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