codekingpro/portable-devtools
114k
1# orm/interfaces.py
2# Copyright (C) 2005-2024 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 TypeVar
41from typing import Union
42
43from . import exc as orm_exc
44from . import path_registry
45from .base import _MappedAttribute as _MappedAttribute
46from .base import EXT_CONTINUE as EXT_CONTINUE # noqa: F401
47from .base import EXT_SKIP as EXT_SKIP # noqa: F401
48from .base import EXT_STOP as EXT_STOP # noqa: F401
49from .base import InspectionAttr as InspectionAttr # noqa: F401
50from .base import InspectionAttrInfo as InspectionAttrInfo
51from .base import MANYTOMANY as MANYTOMANY # noqa: F401
52from .base import MANYTOONE as MANYTOONE # noqa: F401
53from .base import NO_KEY as NO_KEY # noqa: F401
54from .base import NO_VALUE as NO_VALUE # noqa: F401
55from .base import NotExtension as NotExtension # noqa: F401
56from .base import ONETOMANY as ONETOMANY # noqa: F401
57from .base import RelationshipDirection as RelationshipDirection # noqa: F401
58from .base import SQLORMOperations
59from .. import ColumnElement
60from .. import exc as sa_exc
61from .. import inspection
62from .. import util
63from ..sql import operators
64from ..sql import roles
65from ..sql import visitors
66from ..sql.base import _NoArg
67from ..sql.base import ExecutableOption
68from ..sql.cache_key import HasCacheKey
69from ..sql.operators import ColumnOperators
70from ..sql.schema import Column
71from ..sql.type_api import TypeEngine
72from ..util import warn_deprecated
73from ..util.typing import RODescriptorReference
74from ..util.typing import TypedDict
75
76if typing.TYPE_CHECKING:
77 from ._typing import _EntityType
78 from ._typing import _IdentityKeyType
79 from ._typing import _InstanceDict
80 from ._typing import _InternalEntityType
81 from ._typing import _ORMAdapterProto
82 from .attributes import InstrumentedAttribute
83 from .base import Mapped
84 from .context import _MapperEntity
85 from .context import ORMCompileState
86 from .context import QueryContext
87 from .decl_api import RegistryType
88 from .decl_base import _ClassScanMapperConfig
89 from .loading import _PopulatorDict
90 from .mapper import Mapper
91 from .path_registry import AbstractEntityRegistry
92 from .query import Query
93 from .session import Session
94 from .state import InstanceState
95 from .strategy_options import _LoadElement
96 from .util import AliasedInsp
97 from .util import ORMAdapter
98 from ..engine.result import Result
99 from ..sql._typing import _ColumnExpressionArgument
100 from ..sql._typing import _ColumnsClauseArgument
101 from ..sql._typing import _DMLColumnArgument
102 from ..sql._typing import _InfoType
103 from ..sql.operators import OperatorType
104 from ..sql.visitors import _TraverseInternalsType
105 from ..util.typing import _AnnotationScanType
106
107_StrategyKey = Tuple[Any, ...]
108
109_T = TypeVar("_T", bound=Any)
110_T_co = TypeVar("_T_co", bound=Any, covariant=True)
111
112_TLS = TypeVar("_TLS", bound="Type[LoaderStrategy]")
113
114
115class ORMStatementRole(roles.StatementRole):
116 __slots__ = ()
117 _role_name = (
118 "Executable SQL or text() construct, including ORM aware objects"
119 )
120
121
122class ORMColumnsClauseRole(
123 roles.ColumnsClauseRole, roles.TypedColumnsClauseRole[_T]
124):
125 __slots__ = ()
126 _role_name = "ORM mapped entity, aliased entity, or Column expression"
127
128
129class ORMEntityColumnsClauseRole(ORMColumnsClauseRole[_T]):
130 __slots__ = ()
131 _role_name = "ORM mapped or aliased entity"
132
133
134class ORMFromClauseRole(roles.StrictFromClauseRole):
135 __slots__ = ()
136 _role_name = "ORM mapped entity, aliased entity, or FROM expression"
137
138
139class ORMColumnDescription(TypedDict):
140 name: str
141 # TODO: add python_type and sql_type here; combining them
142 # into "type" is a bad idea
143 type: Union[Type[Any], TypeEngine[Any]]
144 aliased: bool
145 expr: _ColumnsClauseArgument[Any]
146 entity: Optional[_ColumnsClauseArgument[Any]]
147
148
149class _IntrospectsAnnotations:
150 __slots__ = ()
151
152 @classmethod
153 def _mapper_property_name(cls) -> str:
154 return cls.__name__
155
156 def found_in_pep593_annotated(self) -> Any:
157 """return a copy of this object to use in declarative when the
158 object is found inside of an Annotated object."""
159
160 raise NotImplementedError(
161 f"Use of the {self._mapper_property_name()!r} "
162 "construct inside of an Annotated object is not yet supported."
163 )
164
165 def declarative_scan(
166 self,
167 decl_scan: _ClassScanMapperConfig,
168 registry: RegistryType,
169 cls: Type[Any],
170 originating_module: Optional[str],
171 key: str,
172 mapped_container: Optional[Type[Mapped[Any]]],
173 annotation: Optional[_AnnotationScanType],
174 extracted_mapped_annotation: Optional[_AnnotationScanType],
175 is_dataclass_field: bool,
176 ) -> None:
177 """Perform class-specific initializaton at early declarative scanning
178 time.
179
180 .. versionadded:: 2.0
181
182 """
183
184 def _raise_for_required(self, key: str, cls: Type[Any]) -> NoReturn:
185 raise sa_exc.ArgumentError(
186 f"Python typing annotation is required for attribute "
187 f'"{cls.__name__}.{key}" when primary argument(s) for '
188 f'"{self._mapper_property_name()}" '
189 "construct are None or not present"
190 )
191
192
193class _AttributeOptions(NamedTuple):
194 """define Python-local attribute behavior options common to all
195 :class:`.MapperProperty` objects.
196
197 Currently this includes dataclass-generation arguments.
198
199 .. versionadded:: 2.0
200
201 """
202
203 dataclasses_init: Union[_NoArg, bool]
204 dataclasses_repr: Union[_NoArg, bool]
205 dataclasses_default: Union[_NoArg, Any]
206 dataclasses_default_factory: Union[_NoArg, Callable[[], Any]]
207 dataclasses_compare: Union[_NoArg, bool]
208 dataclasses_kw_only: Union[_NoArg, bool]
209
210 def _as_dataclass_field(self, key: str) -> Any:
211 """Return a ``dataclasses.Field`` object given these arguments."""
212
213 kw: Dict[str, Any] = {}
214 if self.dataclasses_default_factory is not _NoArg.NO_ARG:
215 kw["default_factory"] = self.dataclasses_default_factory
216 if self.dataclasses_default is not _NoArg.NO_ARG:
217 kw["default"] = self.dataclasses_default
218 if self.dataclasses_init is not _NoArg.NO_ARG:
219 kw["init"] = self.dataclasses_init
220 if self.dataclasses_repr is not _NoArg.NO_ARG:
221 kw["repr"] = self.dataclasses_repr
222 if self.dataclasses_compare is not _NoArg.NO_ARG:
223 kw["compare"] = self.dataclasses_compare
224 if self.dataclasses_kw_only is not _NoArg.NO_ARG:
225 kw["kw_only"] = self.dataclasses_kw_only
226
227 if "default" in kw and callable(kw["default"]):
228 # callable defaults are ambiguous. deprecate them in favour of
229 # insert_default or default_factory. #9936
230 warn_deprecated(
231 f"Callable object passed to the ``default`` parameter for "
232 f"attribute {key!r} in a ORM-mapped Dataclasses context is "
233 "ambiguous, "
234 "and this use will raise an error in a future release. "
235 "If this callable is intended to produce Core level INSERT "
236 "default values for an underlying ``Column``, use "
237 "the ``mapped_column.insert_default`` parameter instead. "
238 "To establish this callable as providing a default value "
239 "for instances of the dataclass itself, use the "
240 "``default_factory`` dataclasses parameter.",
241 "2.0",
242 )
243
244 if (
245 "init" in kw
246 and not kw["init"]
247 and "default" in kw
248 and not callable(kw["default"]) # ignore callable defaults. #9936
249 and "default_factory" not in kw # illegal but let dc.field raise
250 ):
251 # fix for #9879
252 default = kw.pop("default")
253 kw["default_factory"] = lambda: default
254
255 return dataclasses.field(**kw)
256
257 @classmethod
258 def _get_arguments_for_make_dataclass(
259 cls,
260 key: str,
261 annotation: _AnnotationScanType,
262 mapped_container: Optional[Any],
263 elem: _T,
264 ) -> Union[
265 Tuple[str, _AnnotationScanType],
266 Tuple[str, _AnnotationScanType, dataclasses.Field[Any]],
267 ]:
268 """given attribute key, annotation, and value from a class, return
269 the argument tuple we would pass to dataclasses.make_dataclass()
270 for this attribute.
271
272 """
273 if isinstance(elem, _DCAttributeOptions):
274 dc_field = elem._attribute_options._as_dataclass_field(key)
275
276 return (key, annotation, dc_field)
277 elif elem is not _NoArg.NO_ARG:
278 # why is typing not erroring on this?
279 return (key, annotation, elem)
280 elif mapped_container is not None:
281 # it's Mapped[], but there's no "element", which means declarative
282 # did not actually do anything for this field. this shouldn't
283 # happen.
284 # previously, this would occur because _scan_attributes would
285 # skip a field that's on an already mapped superclass, but it
286 # would still include it in the annotations, leading
287 # to issue #8718
288
289 assert False, "Mapped[] received without a mapping declaration"
290
291 else:
292 # plain dataclass field, not mapped. Is only possible
293 # if __allow_unmapped__ is set up. I can see this mode causing
294 # problems...
295 return (key, annotation)
296
297
298_DEFAULT_ATTRIBUTE_OPTIONS = _AttributeOptions(
299 _NoArg.NO_ARG,
300 _NoArg.NO_ARG,
301 _NoArg.NO_ARG,
302 _NoArg.NO_ARG,
303 _NoArg.NO_ARG,
304 _NoArg.NO_ARG,
305)
306
307_DEFAULT_READONLY_ATTRIBUTE_OPTIONS = _AttributeOptions(
308 False,
309 _NoArg.NO_ARG,
310 _NoArg.NO_ARG,
311 _NoArg.NO_ARG,
312 _NoArg.NO_ARG,
313 _NoArg.NO_ARG,
314)
315
316
317class _DCAttributeOptions:
318 """mixin for descriptors or configurational objects that include dataclass
319 field options.
320
321 This includes :class:`.MapperProperty`, :class:`._MapsColumn` within
322 the ORM, but also includes :class:`.AssociationProxy` within ext.
323 Can in theory be used for other descriptors that serve a similar role
324 as association proxy. (*maybe* hybrids, not sure yet.)
325
326 """
327
328 __slots__ = ()
329
330 _attribute_options: _AttributeOptions
331 """behavioral options for ORM-enabled Python attributes
332
333 .. versionadded:: 2.0
334
335 """
336
337 _has_dataclass_arguments: bool
338
339
340class _MapsColumns(_DCAttributeOptions, _MappedAttribute[_T]):
341 """interface for declarative-capable construct that delivers one or more
342 Column objects to the declarative process to be part of a Table.
343 """
344
345 __slots__ = ()
346
347 @property
348 def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
349 """return a MapperProperty to be assigned to the declarative mapping"""
350 raise NotImplementedError()
351
352 @property
353 def columns_to_assign(self) -> List[Tuple[Column[_T], int]]:
354 """A list of Column objects that should be declaratively added to the
355 new Table object.
356
357 """
358 raise NotImplementedError()
359
360
361# NOTE: MapperProperty needs to extend _MappedAttribute so that declarative
362# typing works, i.e. "Mapped[A] = relationship()". This introduces an
363# inconvenience which is that all the MapperProperty objects are treated
364# as descriptors by typing tools, which are misled by this as assignment /
365# access to a descriptor attribute wants to move through __get__.
366# Therefore, references to MapperProperty as an instance variable, such
367# as in PropComparator, may have some special typing workarounds such as the
368# use of sqlalchemy.util.typing.DescriptorReference to avoid mis-interpretation
369# by typing tools
370@inspection._self_inspects
371class MapperProperty(
372 HasCacheKey,
373 _DCAttributeOptions,
374 _MappedAttribute[_T],
375 InspectionAttrInfo,
376 util.MemoizedSlots,
377):
378 """Represent a particular class attribute mapped by :class:`_orm.Mapper`.
379
380 The most common occurrences of :class:`.MapperProperty` are the
381 mapped :class:`_schema.Column`, which is represented in a mapping as
382 an instance of :class:`.ColumnProperty`,
383 and a reference to another class produced by :func:`_orm.relationship`,
384 represented in the mapping as an instance of
385 :class:`.Relationship`.
386
387 """
388
389 __slots__ = (
390 "_configure_started",
391 "_configure_finished",
392 "_attribute_options",
393 "_has_dataclass_arguments",
394 "parent",
395 "key",
396 "info",
397 "doc",
398 )
399
400 _cache_key_traversal: _TraverseInternalsType = [
401 ("parent", visitors.ExtendedInternalTraversal.dp_has_cache_key),
402 ("key", visitors.ExtendedInternalTraversal.dp_string),
403 ]
404
405 if not TYPE_CHECKING:
406 cascade = None
407
408 is_property = True
409 """Part of the InspectionAttr interface; states this object is a
410 mapper property.
411
412 """
413
414 comparator: PropComparator[_T]
415 """The :class:`_orm.PropComparator` instance that implements SQL
416 expression construction on behalf of this mapped attribute."""
417
418 key: str
419 """name of class attribute"""
420
421 parent: Mapper[Any]
422 """the :class:`.Mapper` managing this property."""
423
424 _is_relationship = False
425
426 _links_to_entity: bool
427 """True if this MapperProperty refers to a mapped entity.
428
429 Should only be True for Relationship, False for all others.
430
431 """
432
433 doc: Optional[str]
434 """optional documentation string"""
435
436 info: _InfoType
437 """Info dictionary associated with the object, allowing user-defined
438 data to be associated with this :class:`.InspectionAttr`.
439
440 The dictionary is generated when first accessed. Alternatively,
441 it can be specified as a constructor argument to the
442 :func:`.column_property`, :func:`_orm.relationship`, or :func:`.composite`
443 functions.
444
445 .. seealso::
446
447 :attr:`.QueryableAttribute.info`
448
449 :attr:`.SchemaItem.info`
450
451 """
452
453 def _memoized_attr_info(self) -> _InfoType:
454 """Info dictionary associated with the object, allowing user-defined
455 data to be associated with this :class:`.InspectionAttr`.
456
457 The dictionary is generated when first accessed. Alternatively,
458 it can be specified as a constructor argument to the
459 :func:`.column_property`, :func:`_orm.relationship`, or
460 :func:`.composite`
461 functions.
462
463 .. seealso::
464
465 :attr:`.QueryableAttribute.info`
466
467 :attr:`.SchemaItem.info`
468
469 """
470 return {}
471
472 def setup(
473 self,
474 context: ORMCompileState,
475 query_entity: _MapperEntity,
476 path: AbstractEntityRegistry,
477 adapter: Optional[ORMAdapter],
478 **kwargs: Any,
479 ) -> None:
480 """Called by Query for the purposes of constructing a SQL statement.
481
482 Each MapperProperty associated with the target mapper processes the
483 statement referenced by the query context, adding columns and/or
484 criterion as appropriate.
485
486 """
487
488 def create_row_processor(
489 self,
490 context: ORMCompileState,
491 query_entity: _MapperEntity,
492 path: AbstractEntityRegistry,
493 mapper: Mapper[Any],
494 result: Result[Any],
495 adapter: Optional[ORMAdapter],
496 populators: _PopulatorDict,
497 ) -> None:
498 """Produce row processing functions and append to the given
499 set of populators lists.
500
501 """
502
503 def cascade_iterator(
504 self,
505 type_: str,
506 state: InstanceState[Any],
507 dict_: _InstanceDict,
508 visited_states: Set[InstanceState[Any]],
509 halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None,
510 ) -> Iterator[
511 Tuple[object, Mapper[Any], InstanceState[Any], _InstanceDict]
512 ]:
513 """Iterate through instances related to the given instance for
514 a particular 'cascade', starting with this MapperProperty.
515
516 Return an iterator3-tuples (instance, mapper, state).
517
518 Note that the 'cascade' collection on this MapperProperty is
519 checked first for the given type before cascade_iterator is called.
520
521 This method typically only applies to Relationship.
522
523 """
524
525 return iter(())
526
527 def set_parent(self, parent: Mapper[Any], init: bool) -> None:
528 """Set the parent mapper that references this MapperProperty.
529
530 This method is overridden by some subclasses to perform extra
531 setup when the mapper is first known.
532
533 """
534 self.parent = parent
535
536 def instrument_class(self, mapper: Mapper[Any]) -> None:
537 """Hook called by the Mapper to the property to initiate
538 instrumentation of the class attribute managed by this
539 MapperProperty.
540
541 The MapperProperty here will typically call out to the
542 attributes module to set up an InstrumentedAttribute.
543
544 This step is the first of two steps to set up an InstrumentedAttribute,
545 and is called early in the mapper setup process.
546
547 The second step is typically the init_class_attribute step,
548 called from StrategizedProperty via the post_instrument_class()
549 hook. This step assigns additional state to the InstrumentedAttribute
550 (specifically the "impl") which has been determined after the
551 MapperProperty has determined what kind of persistence
552 management it needs to do (e.g. scalar, object, collection, etc).
553
554 """
555
556 def __init__(
557 self,
558 attribute_options: Optional[_AttributeOptions] = None,
559 _assume_readonly_dc_attributes: bool = False,
560 ) -> None:
561 self._configure_started = False
562 self._configure_finished = False
563
564 if _assume_readonly_dc_attributes:
565 default_attrs = _DEFAULT_READONLY_ATTRIBUTE_OPTIONS
566 else:
567 default_attrs = _DEFAULT_ATTRIBUTE_OPTIONS
568
569 if attribute_options and attribute_options != default_attrs:
570 self._has_dataclass_arguments = True
571 self._attribute_options = attribute_options
572 else:
573 self._has_dataclass_arguments = False
574 self._attribute_options = default_attrs
575
576 def init(self) -> None:
577 """Called after all mappers are created to assemble
578 relationships between mappers and perform other post-mapper-creation
579 initialization steps.
580
581
582 """
583 self._configure_started = True
584 self.do_init()
585 self._configure_finished = True
586
587 @property
588 def class_attribute(self) -> InstrumentedAttribute[_T]:
589 """Return the class-bound descriptor corresponding to this
590 :class:`.MapperProperty`.
591
592 This is basically a ``getattr()`` call::
593
594 return getattr(self.parent.class_, self.key)
595
596 I.e. if this :class:`.MapperProperty` were named ``addresses``,
597 and the class to which it is mapped is ``User``, this sequence
598 is possible::
599
600 >>> from sqlalchemy import inspect
601 >>> mapper = inspect(User)
602 >>> addresses_property = mapper.attrs.addresses
603 >>> addresses_property.class_attribute is User.addresses
604 True
605 >>> User.addresses.property is addresses_property
606 True
607
608
609 """
610
611 return getattr(self.parent.class_, self.key) # type: ignore
612
613 def do_init(self) -> None:
614 """Perform subclass-specific initialization post-mapper-creation
615 steps.
616
617 This is a template method called by the ``MapperProperty``
618 object's init() method.
619
620 """
621
622 def post_instrument_class(self, mapper: Mapper[Any]) -> None:
623 """Perform instrumentation adjustments that need to occur
624 after init() has completed.
625
626 The given Mapper is the Mapper invoking the operation, which
627 may not be the same Mapper as self.parent in an inheritance
628 scenario; however, Mapper will always at least be a sub-mapper of
629 self.parent.
630
631 This method is typically used by StrategizedProperty, which delegates
632 it to LoaderStrategy.init_class_attribute() to perform final setup
633 on the class-bound InstrumentedAttribute.
634
635 """
636
637 def merge(
638 self,
639 session: Session,
640 source_state: InstanceState[Any],
641 source_dict: _InstanceDict,
642 dest_state: InstanceState[Any],
643 dest_dict: _InstanceDict,
644 load: bool,
645 _recursive: Dict[Any, object],
646 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
647 ) -> None:
648 """Merge the attribute represented by this ``MapperProperty``
649 from source to destination object.
650
651 """
652
653 def __repr__(self) -> str:
654 return "<%s at 0x%x; %s>" % (
655 self.__class__.__name__,
656 id(self),
657 getattr(self, "key", "no key"),
658 )
659
660
661@inspection._self_inspects
662class PropComparator(SQLORMOperations[_T_co], Generic[_T_co], ColumnOperators):
663 r"""Defines SQL operations for ORM mapped attributes.
664
665 SQLAlchemy allows for operators to
666 be redefined at both the Core and ORM level. :class:`.PropComparator`
667 is the base class of operator redefinition for ORM-level operations,
668 including those of :class:`.ColumnProperty`,
669 :class:`.Relationship`, and :class:`.Composite`.
670
671 User-defined subclasses of :class:`.PropComparator` may be created. The
672 built-in Python comparison and math operator methods, such as
673 :meth:`.operators.ColumnOperators.__eq__`,
674 :meth:`.operators.ColumnOperators.__lt__`, and
675 :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide
676 new operator behavior. The custom :class:`.PropComparator` is passed to
677 the :class:`.MapperProperty` instance via the ``comparator_factory``
678 argument. In each case,
679 the appropriate subclass of :class:`.PropComparator` should be used::
680
681 # definition of custom PropComparator subclasses
682
683 from sqlalchemy.orm.properties import \
684 ColumnProperty,\
685 Composite,\
686 Relationship
687
688 class MyColumnComparator(ColumnProperty.Comparator):
689 def __eq__(self, other):
690 return self.__clause_element__() == other
691
692 class MyRelationshipComparator(Relationship.Comparator):
693 def any(self, expression):
694 "define the 'any' operation"
695 # ...
696
697 class MyCompositeComparator(Composite.Comparator):
698 def __gt__(self, other):
699 "redefine the 'greater than' operation"
700
701 return sql.and_(*[a>b for a, b in
702 zip(self.__clause_element__().clauses,
703 other.__composite_values__())])
704
705
706 # application of custom PropComparator subclasses
707
708 from sqlalchemy.orm import column_property, relationship, composite
709 from sqlalchemy import Column, String
710
711 class SomeMappedClass(Base):
712 some_column = column_property(Column("some_column", String),
713 comparator_factory=MyColumnComparator)
714
715 some_relationship = relationship(SomeOtherClass,
716 comparator_factory=MyRelationshipComparator)
717
718 some_composite = composite(
719 Column("a", String), Column("b", String),
720 comparator_factory=MyCompositeComparator
721 )
722
723 Note that for column-level operator redefinition, it's usually
724 simpler to define the operators at the Core level, using the
725 :attr:`.TypeEngine.comparator_factory` attribute. See
726 :ref:`types_operators` for more detail.
727
728 .. seealso::
729
730 :class:`.ColumnProperty.Comparator`
731
732 :class:`.Relationship.Comparator`
733
734 :class:`.Composite.Comparator`
735
736 :class:`.ColumnOperators`
737
738 :ref:`types_operators`
739
740 :attr:`.TypeEngine.comparator_factory`
741
742 """
743
744 __slots__ = "prop", "_parententity", "_adapt_to_entity"
745
746 __visit_name__ = "orm_prop_comparator"
747
748 _parententity: _InternalEntityType[Any]
749 _adapt_to_entity: Optional[AliasedInsp[Any]]
750 prop: RODescriptorReference[MapperProperty[_T_co]]
751
752 def __init__(
753 self,
754 prop: MapperProperty[_T],
755 parentmapper: _InternalEntityType[Any],
756 adapt_to_entity: Optional[AliasedInsp[Any]] = None,
757 ):
758 self.prop = prop
759 self._parententity = adapt_to_entity or parentmapper
760 self._adapt_to_entity = adapt_to_entity
761
762 @util.non_memoized_property
763 def property(self) -> MapperProperty[_T_co]:
764 """Return the :class:`.MapperProperty` associated with this
765 :class:`.PropComparator`.
766
767
768 Return values here will commonly be instances of
769 :class:`.ColumnProperty` or :class:`.Relationship`.
770
771
772 """
773 return self.prop
774
775 def __clause_element__(self) -> roles.ColumnsClauseRole:
776 raise NotImplementedError("%r" % self)
777
778 def _bulk_update_tuples(
779 self, value: Any
780 ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
781 """Receive a SQL expression that represents a value in the SET
782 clause of an UPDATE statement.
783
784 Return a tuple that can be passed to a :class:`_expression.Update`
785 construct.
786
787 """
788
789 return [(cast("_DMLColumnArgument", self.__clause_element__()), value)]
790
791 def adapt_to_entity(
792 self, adapt_to_entity: AliasedInsp[Any]
793 ) -> PropComparator[_T_co]:
794 """Return a copy of this PropComparator which will use the given
795 :class:`.AliasedInsp` to produce corresponding expressions.
796 """
797 return self.__class__(self.prop, self._parententity, adapt_to_entity)
798
799 @util.ro_non_memoized_property
800 def _parentmapper(self) -> Mapper[Any]:
801 """legacy; this is renamed to _parententity to be
802 compatible with QueryableAttribute."""
803 return self._parententity.mapper
804
805 def _criterion_exists(
806 self,
807 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
808 **kwargs: Any,
809 ) -> ColumnElement[Any]:
810 return self.prop.comparator._criterion_exists(criterion, **kwargs)
811
812 @util.ro_non_memoized_property
813 def adapter(self) -> Optional[_ORMAdapterProto]:
814 """Produce a callable that adapts column expressions
815 to suit an aliased version of this comparator.
816
817 """
818 if self._adapt_to_entity is None:
819 return None
820 else:
821 return self._adapt_to_entity._orm_adapt_element
822
823 @util.ro_non_memoized_property
824 def info(self) -> _InfoType:
825 return self.prop.info
826
827 @staticmethod
828 def _any_op(a: Any, b: Any, **kwargs: Any) -> Any:
829 return a.any(b, **kwargs)
830
831 @staticmethod
832 def _has_op(left: Any, other: Any, **kwargs: Any) -> Any:
833 return left.has(other, **kwargs)
834
835 @staticmethod
836 def _of_type_op(a: Any, class_: Any) -> Any:
837 return a.of_type(class_)
838
839 any_op = cast(operators.OperatorType, _any_op)
840 has_op = cast(operators.OperatorType, _has_op)
841 of_type_op = cast(operators.OperatorType, _of_type_op)
842
843 if typing.TYPE_CHECKING:
844
845 def operate(
846 self, op: OperatorType, *other: Any, **kwargs: Any
847 ) -> ColumnElement[Any]: ...
848
849 def reverse_operate(
850 self, op: OperatorType, other: Any, **kwargs: Any
851 ) -> ColumnElement[Any]: ...
852
853 def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T_co]:
854 r"""Redefine this object in terms of a polymorphic subclass,
855 :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased`
856 construct.
857
858 Returns a new PropComparator from which further criterion can be
859 evaluated.
860
861 e.g.::
862
863 query.join(Company.employees.of_type(Engineer)).\
864 filter(Engineer.name=='foo')
865
866 :param \class_: a class or mapper indicating that criterion will be
867 against this specific subclass.
868
869 .. seealso::
870
871 :ref:`orm_queryguide_joining_relationships_aliased` - in the
872 :ref:`queryguide_toplevel`
873
874 :ref:`inheritance_of_type`
875
876 """
877
878 return self.operate(PropComparator.of_type_op, class_) # type: ignore
879
880 def and_(
881 self, *criteria: _ColumnExpressionArgument[bool]
882 ) -> PropComparator[bool]:
883 """Add additional criteria to the ON clause that's represented by this
884 relationship attribute.
885
886 E.g.::
887
888
889 stmt = select(User).join(
890 User.addresses.and_(Address.email_address != 'foo')
891 )
892
893 stmt = select(User).options(
894 joinedload(User.addresses.and_(Address.email_address != 'foo'))
895 )
896
897 .. versionadded:: 1.4
898
899 .. seealso::
900
901 :ref:`orm_queryguide_join_on_augmented`
902
903 :ref:`loader_option_criteria`
904
905 :func:`.with_loader_criteria`
906
907 """
908 return self.operate(operators.and_, *criteria) # type: ignore
909
910 def any(
911 self,
912 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
913 **kwargs: Any,
914 ) -> ColumnElement[bool]:
915 r"""Return a SQL expression representing true if this element
916 references a member which meets the given criterion.
917
918 The usual implementation of ``any()`` is
919 :meth:`.Relationship.Comparator.any`.
920
921 :param criterion: an optional ClauseElement formulated against the
922 member class' table or attributes.
923
924 :param \**kwargs: key/value pairs corresponding to member class
925 attribute names which will be compared via equality to the
926 corresponding values.
927
928 """
929
930 return self.operate(PropComparator.any_op, criterion, **kwargs)
931
932 def has(
933 self,
934 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
935 **kwargs: Any,
936 ) -> ColumnElement[bool]:
937 r"""Return a SQL expression representing true if this element
938 references a member which meets the given criterion.
939
940 The usual implementation of ``has()`` is
941 :meth:`.Relationship.Comparator.has`.
942
943 :param criterion: an optional ClauseElement formulated against the
944 member class' table or attributes.
945
946 :param \**kwargs: key/value pairs corresponding to member class
947 attribute names which will be compared via equality to the
948 corresponding values.
949
950 """
951
952 return self.operate(PropComparator.has_op, criterion, **kwargs)
953
954
955class StrategizedProperty(MapperProperty[_T]):
956 """A MapperProperty which uses selectable strategies to affect
957 loading behavior.
958
959 There is a single strategy selected by default. Alternate
960 strategies can be selected at Query time through the usage of
961 ``StrategizedOption`` objects via the Query.options() method.
962
963 The mechanics of StrategizedProperty are used for every Query
964 invocation for every mapped attribute participating in that Query,
965 to determine first how the attribute will be rendered in SQL
966 and secondly how the attribute will retrieve a value from a result
967 row and apply it to a mapped object. The routines here are very
968 performance-critical.
969
970 """
971
972 __slots__ = (
973 "_strategies",
974 "strategy",
975 "_wildcard_token",
976 "_default_path_loader_key",
977 "strategy_key",
978 )
979 inherit_cache = True
980 strategy_wildcard_key: ClassVar[str]
981
982 strategy_key: _StrategyKey
983
984 _strategies: Dict[_StrategyKey, LoaderStrategy]
985
986 def _memoized_attr__wildcard_token(self) -> Tuple[str]:
987 return (
988 f"{self.strategy_wildcard_key}:{path_registry._WILDCARD_TOKEN}",
989 )
990
991 def _memoized_attr__default_path_loader_key(
992 self,
993 ) -> Tuple[str, Tuple[str]]:
994 return (
995 "loader",
996 (f"{self.strategy_wildcard_key}:{path_registry._DEFAULT_TOKEN}",),
997 )
998
999 def _get_context_loader(
1000 self, context: ORMCompileState, path: AbstractEntityRegistry
1001 ) -> Optional[_LoadElement]:
1002 load: Optional[_LoadElement] = None
1003
1004 search_path = path[self]
1005
1006 # search among: exact match, "attr.*", "default" strategy
1007 # if any.
1008 for path_key in (
1009 search_path._loader_key,
1010 search_path._wildcard_path_loader_key,
1011 search_path._default_path_loader_key,
1012 ):
1013 if path_key in context.attributes:
1014 load = context.attributes[path_key]
1015 break
1016
1017 # note that if strategy_options.Load is placing non-actionable
1018 # objects in the context like defaultload(), we would
1019 # need to continue the loop here if we got such an
1020 # option as below.
1021 # if load.strategy or load.local_opts:
1022 # break
1023
1024 return load
1025
1026 def _get_strategy(self, key: _StrategyKey) -> LoaderStrategy:
1027 try:
1028 return self._strategies[key]
1029 except KeyError:
1030 pass
1031
1032 # run outside to prevent transfer of exception context
1033 cls = self._strategy_lookup(self, *key)
1034 # this previously was setting self._strategies[cls], that's
1035 # a bad idea; should use strategy key at all times because every
1036 # strategy has multiple keys at this point
1037 self._strategies[key] = strategy = cls(self, key)
1038 return strategy
1039
1040 def setup(
1041 self,
1042 context: ORMCompileState,
1043 query_entity: _MapperEntity,
1044 path: AbstractEntityRegistry,
1045 adapter: Optional[ORMAdapter],
1046 **kwargs: Any,
1047 ) -> None:
1048 loader = self._get_context_loader(context, path)
1049 if loader and loader.strategy:
1050 strat = self._get_strategy(loader.strategy)
1051 else:
1052 strat = self.strategy
1053 strat.setup_query(
1054 context, query_entity, path, loader, adapter, **kwargs
1055 )
1056
1057 def create_row_processor(
1058 self,
1059 context: ORMCompileState,
1060 query_entity: _MapperEntity,
1061 path: AbstractEntityRegistry,
1062 mapper: Mapper[Any],
1063 result: Result[Any],
1064 adapter: Optional[ORMAdapter],
1065 populators: _PopulatorDict,
1066 ) -> None:
1067 loader = self._get_context_loader(context, path)
1068 if loader and loader.strategy:
1069 strat = self._get_strategy(loader.strategy)
1070 else:
1071 strat = self.strategy
1072 strat.create_row_processor(
1073 context,
1074 query_entity,
1075 path,
1076 loader,
1077 mapper,
1078 result,
1079 adapter,
1080 populators,
1081 )
1082
1083 def do_init(self) -> None:
1084 self._strategies = {}
1085 self.strategy = self._get_strategy(self.strategy_key)
1086
1087 def post_instrument_class(self, mapper: Mapper[Any]) -> None:
1088 if (
1089 not self.parent.non_primary
1090 and not mapper.class_manager._attr_has_impl(self.key)
1091 ):
1092 self.strategy.init_class_attribute(mapper)
1093
1094 _all_strategies: collections.defaultdict[
1095 Type[MapperProperty[Any]], Dict[_StrategyKey, Type[LoaderStrategy]]
1096 ] = collections.defaultdict(dict)
1097
1098 @classmethod
1099 def strategy_for(cls, **kw: Any) -> Callable[[_TLS], _TLS]:
1100 def decorate(dec_cls: _TLS) -> _TLS:
1101 # ensure each subclass of the strategy has its
1102 # own _strategy_keys collection
1103 if "_strategy_keys" not in dec_cls.__dict__:
1104 dec_cls._strategy_keys = []
1105 key = tuple(sorted(kw.items()))
1106 cls._all_strategies[cls][key] = dec_cls
1107 dec_cls._strategy_keys.append(key)
1108 return dec_cls
1109
1110 return decorate
1111
1112 @classmethod
1113 def _strategy_lookup(
1114 cls, requesting_property: MapperProperty[Any], *key: Any
1115 ) -> Type[LoaderStrategy]:
1116 requesting_property.parent._with_polymorphic_mappers
1117
1118 for prop_cls in cls.__mro__:
1119 if prop_cls in cls._all_strategies:
1120 if TYPE_CHECKING:
1121 assert issubclass(prop_cls, MapperProperty)
1122 strategies = cls._all_strategies[prop_cls]
1123 try:
1124 return strategies[key]
1125 except KeyError:
1126 pass
1127
1128 for property_type, strats in cls._all_strategies.items():
1129 if key in strats:
1130 intended_property_type = property_type
1131 actual_strategy = strats[key]
1132 break
1133 else:
1134 intended_property_type = None
1135 actual_strategy = None
1136
1137 raise orm_exc.LoaderStrategyException(
1138 cls,
1139 requesting_property,
1140 intended_property_type,
1141 actual_strategy,
1142 key,
1143 )
1144
1145
1146class ORMOption(ExecutableOption):
1147 """Base class for option objects that are passed to ORM queries.
1148
1149 These options may be consumed by :meth:`.Query.options`,
1150 :meth:`.Select.options`, or in a more general sense by any
1151 :meth:`.Executable.options` method. They are interpreted at
1152 statement compile time or execution time in modern use. The
1153 deprecated :class:`.MapperOption` is consumed at ORM query construction
1154 time.
1155
1156 .. versionadded:: 1.4
1157
1158 """
1159
1160 __slots__ = ()
1161
1162 _is_legacy_option = False
1163
1164 propagate_to_loaders = False
1165 """if True, indicate this option should be carried along
1166 to "secondary" SELECT statements that occur for relationship
1167 lazy loaders as well as attribute load / refresh operations.
1168
1169 """
1170
1171 _is_core = False
1172
1173 _is_user_defined = False
1174
1175 _is_compile_state = False
1176
1177 _is_criteria_option = False
1178
1179 _is_strategy_option = False
1180
1181 def _adapt_cached_option_to_uncached_option(
1182 self, context: QueryContext, uncached_opt: ORMOption
1183 ) -> ORMOption:
1184 """adapt this option to the "uncached" version of itself in a
1185 loader strategy context.
1186
1187 given "self" which is an option from a cached query, as well as the
1188 corresponding option from the uncached version of the same query,
1189 return the option we should use in a new query, in the context of a
1190 loader strategy being asked to load related rows on behalf of that
1191 cached query, which is assumed to be building a new query based on
1192 entities passed to us from the cached query.
1193
1194 Currently this routine chooses between "self" and "uncached" without
1195 manufacturing anything new. If the option is itself a loader strategy
1196 option which has a path, that path needs to match to the entities being
1197 passed to us by the cached query, so the :class:`_orm.Load` subclass
1198 overrides this to return "self". For all other options, we return the
1199 uncached form which may have changing state, such as a
1200 with_loader_criteria() option which will very often have new state.
