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 absolute_import
20
21import collections
22
23from . import exc as orm_exc
24from . import path_registry
25from .base import _MappedAttribute # noqa
26from .base import EXT_CONTINUE
27from .base import EXT_SKIP
28from .base import EXT_STOP
29from .base import InspectionAttr # noqa
30from .base import InspectionAttrInfo # noqa
31from .base import MANYTOMANY
32from .base import MANYTOONE
33from .base import NOT_EXTENSION
34from .base import ONETOMANY
35from .. import inspect
36from .. import inspection
37from .. import util
38from ..sql import operators
39from ..sql import roles
40from ..sql import visitors
41from ..sql.base import ExecutableOption
42from ..sql.traversals import HasCacheKey
43
44
45__all__ = (
46 "EXT_CONTINUE",
47 "EXT_STOP",
48 "EXT_SKIP",
49 "ONETOMANY",
50 "MANYTOMANY",
51 "MANYTOONE",
52 "NOT_EXTENSION",
53 "LoaderStrategy",
54 "MapperOption",
55 "LoaderOption",
56 "MapperProperty",
57 "PropComparator",
58 "StrategizedProperty",
59)
60
61
62class ORMStatementRole(roles.StatementRole):
63 _role_name = (
64 "Executable SQL or text() construct, including ORM " "aware objects"
65 )
66
67
68class ORMColumnsClauseRole(roles.ColumnsClauseRole):
69 _role_name = "ORM mapped entity, aliased entity, or Column expression"
70
71
72class ORMEntityColumnsClauseRole(ORMColumnsClauseRole):
73 _role_name = "ORM mapped or aliased entity"
74
75
76class ORMFromClauseRole(roles.StrictFromClauseRole):
77 _role_name = "ORM mapped entity, aliased entity, or FROM expression"
78
79
80@inspection._self_inspects
81class MapperProperty(
82 HasCacheKey, _MappedAttribute, InspectionAttr, util.MemoizedSlots
83):
84 """Represent a particular class attribute mapped by :class:`_orm.Mapper`.
85
86 The most common occurrences of :class:`.MapperProperty` are the
87 mapped :class:`_schema.Column`, which is represented in a mapping as
88 an instance of :class:`.ColumnProperty`,
89 and a reference to another class produced by :func:`_orm.relationship`,
90 represented in the mapping as an instance of
91 :class:`.RelationshipProperty`.
92
93 """
94
95 __slots__ = (
96 "_configure_started",
97 "_configure_finished",
98 "parent",
99 "key",
100 "info",
101 )
102
103 _cache_key_traversal = [
104 ("parent", visitors.ExtendedInternalTraversal.dp_has_cache_key),
105 ("key", visitors.ExtendedInternalTraversal.dp_string),
106 ]
107
108 cascade = frozenset()
109 """The set of 'cascade' attribute names.
110
111 This collection is checked before the 'cascade_iterator' method is called.
112
113 The collection typically only applies to a RelationshipProperty.
114
115 """
116
117 is_property = True
118 """Part of the InspectionAttr interface; states this object is a
119 mapper property.
120
121 """
122
123 @property
124 def _links_to_entity(self):
125 """True if this MapperProperty refers to a mapped entity.
126
127 Should only be True for RelationshipProperty, False for all others.
128
129 """
130 raise NotImplementedError()
131
132 def _memoized_attr_info(self):
133 """Info dictionary associated with the object, allowing user-defined
134 data to be associated with this :class:`.InspectionAttr`.
135
136 The dictionary is generated when first accessed. Alternatively,
137 it can be specified as a constructor argument to the
138 :func:`.column_property`, :func:`_orm.relationship`, or
139 :func:`.composite`
140 functions.
141
142 .. versionchanged:: 1.0.0 :attr:`.MapperProperty.info` is also
143 available on extension types via the
144 :attr:`.InspectionAttrInfo.info` attribute, so that it can apply
145 to a wider variety of ORM and extension constructs.
146
147 .. seealso::
148
149 :attr:`.QueryableAttribute.info`
150
151 :attr:`.SchemaItem.info`
152
153 """
154 return {}
155
156 def setup(self, context, query_entity, path, adapter, **kwargs):
157 """Called by Query for the purposes of constructing a SQL statement.
158
159 Each MapperProperty associated with the target mapper processes the
160 statement referenced by the query context, adding columns and/or
161 criterion as appropriate.
162
163 """
164
165 def create_row_processor(
166 self, context, query_entity, path, mapper, result, adapter, populators
167 ):
168 """Produce row processing functions and append to the given
169 set of populators lists.
170
171 """
172
173 def cascade_iterator(
174 self, type_, state, dict_, visited_states, halt_on=None
175 ):
176 """Iterate through instances related to the given instance for
177 a particular 'cascade', starting with this MapperProperty.
178
179 Return an iterator3-tuples (instance, mapper, state).
180
181 Note that the 'cascade' collection on this MapperProperty is
182 checked first for the given type before cascade_iterator is called.
183
184 This method typically only applies to RelationshipProperty.
185
186 """
187
188 return iter(())
189
190 def set_parent(self, parent, init):
191 """Set the parent mapper that references this MapperProperty.
192
193 This method is overridden by some subclasses to perform extra
194 setup when the mapper is first known.
195
196 """
197 self.parent = parent
198
199 def instrument_class(self, mapper):
200 """Hook called by the Mapper to the property to initiate
201 instrumentation of the class attribute managed by this
202 MapperProperty.
203
204 The MapperProperty here will typically call out to the
205 attributes module to set up an InstrumentedAttribute.
206
207 This step is the first of two steps to set up an InstrumentedAttribute,
208 and is called early in the mapper setup process.
209
210 The second step is typically the init_class_attribute step,
211 called from StrategizedProperty via the post_instrument_class()
212 hook. This step assigns additional state to the InstrumentedAttribute
213 (specifically the "impl") which has been determined after the
214 MapperProperty has determined what kind of persistence
215 management it needs to do (e.g. scalar, object, collection, etc).
216
217 """
218
219 def __init__(self):
220 self._configure_started = False
221 self._configure_finished = False
222
223 def init(self):
224 """Called after all mappers are created to assemble
225 relationships between mappers and perform other post-mapper-creation
226 initialization steps.
227
228
229 """
230 self._configure_started = True
231 self.do_init()
232 self._configure_finished = True
233
234 @property
235 def class_attribute(self):
236 """Return the class-bound descriptor corresponding to this
237 :class:`.MapperProperty`.
238
239 This is basically a ``getattr()`` call::
240
241 return getattr(self.parent.class_, self.key)
242
243 I.e. if this :class:`.MapperProperty` were named ``addresses``,
244 and the class to which it is mapped is ``User``, this sequence
245 is possible::
246
247 >>> from sqlalchemy import inspect
248 >>> mapper = inspect(User)
249 >>> addresses_property = mapper.attrs.addresses
250 >>> addresses_property.class_attribute is User.addresses
251 True
252 >>> User.addresses.property is addresses_property
253 True
254
255
256 """
257
258 return getattr(self.parent.class_, self.key)
259
260 def do_init(self):
261 """Perform subclass-specific initialization post-mapper-creation
262 steps.
263
264 This is a template method called by the ``MapperProperty``
265 object's init() method.
266
267 """
268
269 def post_instrument_class(self, mapper):
270 """Perform instrumentation adjustments that need to occur
271 after init() has completed.
272
273 The given Mapper is the Mapper invoking the operation, which
274 may not be the same Mapper as self.parent in an inheritance
275 scenario; however, Mapper will always at least be a sub-mapper of
276 self.parent.
277
278 This method is typically used by StrategizedProperty, which delegates
279 it to LoaderStrategy.init_class_attribute() to perform final setup
280 on the class-bound InstrumentedAttribute.
281
282 """
283
284 def merge(
285 self,
286 session,
287 source_state,
288 source_dict,
289 dest_state,
290 dest_dict,
291 load,
292 _recursive,
293 _resolve_conflict_map,
294 ):
295 """Merge the attribute represented by this ``MapperProperty``
296 from source to destination object.
297
298 """
299
300 def __repr__(self):
301 return "<%s at 0x%x; %s>" % (
302 self.__class__.__name__,
303 id(self),
304 getattr(self, "key", "no key"),
305 )
306
307
308@inspection._self_inspects
309class PropComparator(operators.ColumnOperators):
310 r"""Defines SQL operators for :class:`.MapperProperty` objects.
311
312 SQLAlchemy allows for operators to
313 be redefined at both the Core and ORM level. :class:`.PropComparator`
314 is the base class of operator redefinition for ORM-level operations,
315 including those of :class:`.ColumnProperty`,
316 :class:`.RelationshipProperty`, and :class:`.CompositeProperty`.
317
318 .. note:: With the advent of Hybrid properties introduced in SQLAlchemy
319 0.7, as well as Core-level operator redefinition in
320 SQLAlchemy 0.8, the use case for user-defined :class:`.PropComparator`
321 instances is extremely rare. See :ref:`hybrids_toplevel` as well
322 as :ref:`types_operators`.
323
324 User-defined subclasses of :class:`.PropComparator` may be created. The
325 built-in Python comparison and math operator methods, such as
326 :meth:`.operators.ColumnOperators.__eq__`,
327 :meth:`.operators.ColumnOperators.__lt__`, and
328 :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide
329 new operator behavior. The custom :class:`.PropComparator` is passed to
330 the :class:`.MapperProperty` instance via the ``comparator_factory``
331 argument. In each case,
332 the appropriate subclass of :class:`.PropComparator` should be used::
333
334 # definition of custom PropComparator subclasses
335
336 from sqlalchemy.orm.properties import \
337 ColumnProperty,\
338 CompositeProperty,\
339 RelationshipProperty
340
341 class MyColumnComparator(ColumnProperty.Comparator):
342 def __eq__(self, other):
343 return self.__clause_element__() == other
344
345 class MyRelationshipComparator(RelationshipProperty.Comparator):
346 def any(self, expression):
347 "define the 'any' operation"
348 # ...
349
350 class MyCompositeComparator(CompositeProperty.Comparator):
351 def __gt__(self, other):
352 "redefine the 'greater than' operation"
353
354 return sql.and_(*[a>b for a, b in
355 zip(self.__clause_element__().clauses,
356 other.__composite_values__())])
357
358
359 # application of custom PropComparator subclasses
360
361 from sqlalchemy.orm import column_property, relationship, composite
362 from sqlalchemy import Column, String
363
364 class SomeMappedClass(Base):
365 some_column = column_property(Column("some_column", String),
366 comparator_factory=MyColumnComparator)
367
368 some_relationship = relationship(SomeOtherClass,
369 comparator_factory=MyRelationshipComparator)
370
371 some_composite = composite(
372 Column("a", String), Column("b", String),
373 comparator_factory=MyCompositeComparator
374 )
375
376 Note that for column-level operator redefinition, it's usually
377 simpler to define the operators at the Core level, using the
378 :attr:`.TypeEngine.comparator_factory` attribute. See
379 :ref:`types_operators` for more detail.
380
381 .. seealso::
382
383 :class:`.ColumnProperty.Comparator`
384
385 :class:`.RelationshipProperty.Comparator`
386
387 :class:`.CompositeProperty.Comparator`
388
389 :class:`.ColumnOperators`
390
391 :ref:`types_operators`
392
393 :attr:`.TypeEngine.comparator_factory`
394
395 """
396
397 __slots__ = "prop", "property", "_parententity", "_adapt_to_entity"
398
399 __visit_name__ = "orm_prop_comparator"
400
401 def __init__(
402 self,
403 prop,
404 parentmapper,
405 adapt_to_entity=None,
406 ):
407 self.prop = self.property = prop
408 self._parententity = adapt_to_entity or parentmapper
409 self._adapt_to_entity = adapt_to_entity
410
411 def __clause_element__(self):
412 raise NotImplementedError("%r" % self)
413
414 def _bulk_update_tuples(self, value):
415 """Receive a SQL expression that represents a value in the SET
416 clause of an UPDATE statement.
417
418 Return a tuple that can be passed to a :class:`_expression.Update`
419 construct.
420
421 """
422
423 return [(self.__clause_element__(), value)]
424
425 def adapt_to_entity(self, adapt_to_entity):
426 """Return a copy of this PropComparator which will use the given
427 :class:`.AliasedInsp` to produce corresponding expressions.
428 """
429 return self.__class__(self.prop, self._parententity, adapt_to_entity)
430
431 @property
432 def _parentmapper(self):
433 """legacy; this is renamed to _parententity to be
434 compatible with QueryableAttribute."""
435 return inspect(self._parententity).mapper
436
437 @property
438 def _propagate_attrs(self):
439 # this suits the case in coercions where we don't actually
440 # call ``__clause_element__()`` but still need to get
441 # resolved._propagate_attrs. See #6558.
442 return util.immutabledict(
443 {
444 "compile_state_plugin": "orm",
445 "plugin_subject": self._parentmapper,
446 }
447 )
448
449 @property
450 def adapter(self):
451 """Produce a callable that adapts column expressions
452 to suit an aliased version of this comparator.
453
454 """
455 if self._adapt_to_entity is None:
456 return None
457 else:
458 return self._adapt_to_entity._adapt_element
459
460 @property
461 def info(self):
462 return self.property.info
463
464 @staticmethod
465 def any_op(a, b, **kwargs):
466 return a.any(b, **kwargs)
467
468 @staticmethod
469 def has_op(a, b, **kwargs):
470 return a.has(b, **kwargs)
471
472 @staticmethod
473 def of_type_op(a, class_):
474 return a.of_type(class_)
475
476 def of_type(self, class_):
477 r"""Redefine this object in terms of a polymorphic subclass,
478 :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased`
479 construct.
480
481 Returns a new PropComparator from which further criterion can be
482 evaluated.
483
484 e.g.::
485
486 query.join(Company.employees.of_type(Engineer)).\
487 filter(Engineer.name=='foo')
488
489 :param \class_: a class or mapper indicating that criterion will be
490 against this specific subclass.
491
492 .. seealso::
493
494 :ref:`queryguide_join_onclause` - in the :ref:`queryguide_toplevel`
495
496 :ref:`inheritance_of_type`
497
498 """
499
500 return self.operate(PropComparator.of_type_op, class_)
501
502 def and_(self, *criteria):
503 """Add additional criteria to the ON clause that's represented by this
504 relationship attribute.
505
506 E.g.::
507
508
509 stmt = select(User).join(
510 User.addresses.and_(Address.email_address != 'foo')
511 )
512
513 stmt = select(User).options(
514 joinedload(User.addresses.and_(Address.email_address != 'foo'))
515 )
516
517 .. versionadded:: 1.4
518
519 .. seealso::
520
521 :ref:`orm_queryguide_join_on_augmented`
522
523 :ref:`loader_option_criteria`
524
525 :func:`.with_loader_criteria`
526
527 """
528 return self.operate(operators.and_, *criteria)
529
530 def any(self, criterion=None, **kwargs):
531 r"""Return true if this collection contains any member that meets the
532 given criterion.
533
534 The usual implementation of ``any()`` is
535 :meth:`.RelationshipProperty.Comparator.any`.
536
537 :param criterion: an optional ClauseElement formulated against the
538 member class' table or attributes.
539
540 :param \**kwargs: key/value pairs corresponding to member class
541 attribute names which will be compared via equality to the
542 corresponding values.
543
544 """
545
546 return self.operate(PropComparator.any_op, criterion, **kwargs)
547
548 def has(self, criterion=None, **kwargs):
549 r"""Return true if this element references a member which meets the
550 given criterion.
551
552 The usual implementation of ``has()`` is
553 :meth:`.RelationshipProperty.Comparator.has`.
554
555 :param criterion: an optional ClauseElement formulated against the
556 member class' table or attributes.
557
558 :param \**kwargs: key/value pairs corresponding to member class
559 attribute names which will be compared via equality to the
560 corresponding values.
561
562 """
563
564 return self.operate(PropComparator.has_op, criterion, **kwargs)
565
566
567class StrategizedProperty(MapperProperty):
568 """A MapperProperty which uses selectable strategies to affect
569 loading behavior.
570
571 There is a single strategy selected by default. Alternate
572 strategies can be selected at Query time through the usage of
573 ``StrategizedOption`` objects via the Query.options() method.
574
575 The mechanics of StrategizedProperty are used for every Query
576 invocation for every mapped attribute participating in that Query,
577 to determine first how the attribute will be rendered in SQL
578 and secondly how the attribute will retrieve a value from a result
579 row and apply it to a mapped object. The routines here are very
580 performance-critical.
581
582 """
583
584 __slots__ = (
585 "_strategies",
586 "strategy",
587 "_wildcard_token",
588 "_default_path_loader_key",
589 )
590 inherit_cache = True
591 strategy_wildcard_key = None
592
593 def _memoized_attr__wildcard_token(self):
594 return (
595 "%s:%s"
596 % (self.strategy_wildcard_key, path_registry._WILDCARD_TOKEN),
597 )
598
599 def _memoized_attr__default_path_loader_key(self):
600 return (
601 "loader",
602 (
603 "%s:%s"
604 % (self.strategy_wildcard_key, path_registry._DEFAULT_TOKEN),
605 ),
606 )
607
608 def _get_context_loader(self, context, path):
609 load = None
610
611 search_path = path[self]
612
613 # search among: exact match, "attr.*", "default" strategy
614 # if any.
615 for path_key in (
616 search_path._loader_key,
617 search_path._wildcard_path_loader_key,
618 search_path._default_path_loader_key,
619 ):
620 if path_key in context.attributes:
621 load = context.attributes[path_key]
622 break
623
624 return load
625
626 def _get_strategy(self, key):
627 try:
628 return self._strategies[key]
629 except KeyError:
630 pass
631
632 # run outside to prevent transfer of exception context
633 cls = self._strategy_lookup(self, *key)
634 # this previously was setting self._strategies[cls], that's
635 # a bad idea; should use strategy key at all times because every
636 # strategy has multiple keys at this point
637 self._strategies[key] = strategy = cls(self, key)
638 return strategy
639
640 def setup(self, context, query_entity, path, adapter, **kwargs):
641 loader = self._get_context_loader(context, path)
642 if loader and loader.strategy:
643 strat = self._get_strategy(loader.strategy)
644 else:
645 strat = self.strategy
646 strat.setup_query(
647 context, query_entity, path, loader, adapter, **kwargs
648 )
649
650 def create_row_processor(
651 self, context, query_entity, path, mapper, result, adapter, populators
652 ):
653 loader = self._get_context_loader(context, path)
654 if loader and loader.strategy:
655 strat = self._get_strategy(loader.strategy)
656 else:
657 strat = self.strategy
658 strat.create_row_processor(
659 context,
660 query_entity,
661 path,
662 loader,
663 mapper,
664 result,
665 adapter,
666 populators,
667 )
668
669 def do_init(self):
670 self._strategies = {}
671 self.strategy = self._get_strategy(self.strategy_key)
672
673 def post_instrument_class(self, mapper):
674 if (
675 not self.parent.non_primary
676 and not mapper.class_manager._attr_has_impl(self.key)
677 ):
678 self.strategy.init_class_attribute(mapper)
679
680 _all_strategies = collections.defaultdict(dict)
681
682 @classmethod
683 def strategy_for(cls, **kw):
684 def decorate(dec_cls):
685 # ensure each subclass of the strategy has its
686 # own _strategy_keys collection
687 if "_strategy_keys" not in dec_cls.__dict__:
688 dec_cls._strategy_keys = []
689 key = tuple(sorted(kw.items()))
690 cls._all_strategies[cls][key] = dec_cls
691 dec_cls._strategy_keys.append(key)
692 return dec_cls
693
694 return decorate
695
696 @classmethod
697 def _strategy_lookup(cls, requesting_property, *key):
698 requesting_property.parent._with_polymorphic_mappers
699
700 for prop_cls in cls.__mro__:
701 if prop_cls in cls._all_strategies:
702 strategies = cls._all_strategies[prop_cls]
703 try:
704 return strategies[key]
705 except KeyError:
706 pass
707
708 for property_type, strats in cls._all_strategies.items():
709 if key in strats:
710 intended_property_type = property_type
711 actual_strategy = strats[key]
712 break
713 else:
714 intended_property_type = None
715 actual_strategy = None
716
717 raise orm_exc.LoaderStrategyException(
718 cls,
719 requesting_property,
720 intended_property_type,
721 actual_strategy,
722 key,
723 )
724
725
726class ORMOption(ExecutableOption):
727 """Base class for option objects that are passed to ORM queries.
728
729 These options may be consumed by :meth:`.Query.options`,
730 :meth:`.Select.options`, or in a more general sense by any
731 :meth:`.Executable.options` method. They are interpreted at
732 statement compile time or execution time in modern use. The
733 deprecated :class:`.MapperOption` is consumed at ORM query construction
734 time.
735
736 .. versionadded:: 1.4
737
738 """
739
740 __slots__ = ()
741
742 _is_legacy_option = False
743
744 propagate_to_loaders = False
745 """if True, indicate this option should be carried along
746 to "secondary" SELECT statements that occur for relationship
747 lazy loaders as well as attribute load / refresh operations.
748
749 """
750
751 _is_compile_state = False
752
753 _is_criteria_option = False
754
755 _is_strategy_option = False
756
757 def _adapt_cached_option_to_uncached_option(self, context, uncached_opt):
758 """given "self" which is an option from a cached query, as well as the
759 corresponding option from the uncached version of the same query,
760 return the option we should use in a new query, in the context of a
761 loader strategy being asked to load related rows on behalf of that
762 cached query, which is assumed to be building a new query based on
763 entities passed to us from the cached query.
764
765 Currently this routine chooses between "self" and "uncached" without
766 manufacturing anything new. If the option is itself a loader strategy
767 option which has a path, that path needs to match to the entities being
768 passed to us by the cached query, so the :class:`_orm.Load` subclass
769 overrides this to return "self". For all other options, we return the
770 uncached form which may have changing state, such as a
771 with_loader_criteria() option which will very often have new state.
772
773 This routine could in the future involve
774 generating a new option based on both inputs if use cases arise,
775 such as if with_loader_criteria() needed to match up to
776 ``AliasedClass`` instances given in the parent query.
777
778 However, longer term it might be better to restructure things such that
779 ``AliasedClass`` entities are always matched up on their cache key,
780 instead of identity, in things like paths and such, so that this whole
781 issue of "the uncached option does not match the entities" goes away.
782 However this would make ``PathRegistry`` more complicated and difficult
783 to debug as well as potentially less performant in that it would be
784 hashing enormous cache keys rather than a simple AliasedInsp. UNLESS,
785 we could get cache keys overall to be reliably hashed into something
786 like an md5 key.
787
788 .. versionadded:: 1.4.41
789
790
791 """
792 if uncached_opt is not None:
793 return uncached_opt
794 else:
795 return self
796
797
798class CompileStateOption(HasCacheKey, ORMOption):
799 """base for :class:`.ORMOption` classes that affect the compilation of
800 a SQL query and therefore need to be part of the cache key.
801
802 .. note:: :class:`.CompileStateOption` is generally non-public and
803 should not be used as a base class for user-defined options; instead,
804 use :class:`.UserDefinedOption`, which is easier to use as it does not
805 interact with ORM compilation internals or caching.
806
807 :class:`.CompileStateOption` defines an internal attribute
808 ``_is_compile_state=True`` which has the effect of the ORM compilation
809 routines for SELECT and other statements will call upon these options when
810 a SQL string is being compiled. As such, these classes implement
811 :class:`.HasCacheKey` and need to provide robust ``_cache_key_traversal``
812 structures.
813
814 The :class:`.CompileStateOption` class is used to implement the ORM
815 :class:`.LoaderOption` and :class:`.CriteriaOption` classes.
816
817 .. versionadded:: 1.4.28
818
819
820 """
821
822 _is_compile_state = True
823
824 def process_compile_state(self, compile_state):
825 """Apply a modification to a given :class:`.CompileState`."""
826
827 def process_compile_state_replaced_entities(
828 self, compile_state, mapper_entities
829 ):
830 """Apply a modification to a given :class:`.CompileState`,
831 given entities that were replaced by with_only_columns() or
832 with_entities().
833
834 .. versionadded:: 1.4.19
835
836 """
837
838
839class LoaderOption(CompileStateOption):
840 """Describe a loader modification to an ORM statement at compilation time.
841
842 .. versionadded:: 1.4
843
844 """
845
846 def process_compile_state_replaced_entities(
847 self, compile_state, mapper_entities
848 ):
849 """Apply a modification to a given :class:`.CompileState`,
850 given entities that were replaced by with_only_columns() or
851 with_entities().
852
853 .. versionadded:: 1.4.19
854
855 """
856 self.process_compile_state(compile_state)
857
858 def process_compile_state(self, compile_state):
859 """Apply a modification to a given :class:`.CompileState`."""
860
861
862class CriteriaOption(CompileStateOption):
863 """Describe a WHERE criteria modification to an ORM statement at
864 compilation time.
865
866 .. versionadded:: 1.4
867
868 """
869
870 _is_criteria_option = True
871
872 def process_compile_state(self, compile_state):
873 """Apply a modification to a given :class:`.CompileState`."""
874
875 def get_global_criteria(self, attributes):
876 """update additional entity criteria options in the given
877 attributes dictionary.
878
879 """
880
881
882class UserDefinedOption(ORMOption):
883 """Base class for a user-defined option that can be consumed from the
884 :meth:`.SessionEvents.do_orm_execute` event hook.
885
886 """
887
888 _is_legacy_option = False
889
890 propagate_to_loaders = False
891 """if True, indicate this option should be carried along
892 to "secondary" Query objects produced during lazy loads
893 or refresh operations.
894
895 """
896
897 def __init__(self, payload=None):
898 self.payload = payload
899
900
901@util.deprecated_cls(
902 "1.4",
903 "The :class:`.MapperOption class is deprecated and will be removed "
904 "in a future release. For "
905 "modifications to queries on a per-execution basis, use the "
906 ":class:`.UserDefinedOption` class to establish state within a "
907 ":class:`.Query` or other Core statement, then use the "
908 ":meth:`.SessionEvents.before_orm_execute` hook to consume them.",
909 constructor=None,
910)
911class MapperOption(ORMOption):
912 """Describe a modification to a Query"""
913
914 _is_legacy_option = True
915
916 propagate_to_loaders = False
917 """if True, indicate this option should be carried along
918 to "secondary" Query objects produced during lazy loads
919 or refresh operations.
920
921 """
922
923 def process_query(self, query):
924 """Apply a modification to the given :class:`_query.Query`."""
925
926 def process_query_conditionally(self, query):
927 """same as process_query(), except that this option may not
928 apply to the given query.
929
930 This is typically applied during a lazy load or scalar refresh
931 operation to propagate options stated in the original Query to the
932 new Query being used for the load. It occurs for those options that
933 specify propagate_to_loaders=True.
934
935 """
936
937 self.process_query(query)
938
939
940class LoaderStrategy(object):
941 """Describe the loading behavior of a StrategizedProperty object.
942
943 The ``LoaderStrategy`` interacts with the querying process in three
944 ways:
945
946 * it controls the configuration of the ``InstrumentedAttribute``
947 placed on a class to handle the behavior of the attribute. this
948 may involve setting up class-level callable functions to fire
949 off a select operation when the attribute is first accessed
950 (i.e. a lazy load)
951
952 * it processes the ``QueryContext`` at statement construction time,
953 where it can modify the SQL statement that is being produced.
954 For example, simple column attributes will add their represented
955 column to the list of selected columns, a joined eager loader
956 may establish join clauses to add to the statement.
957
958 * It produces "row processor" functions at result fetching time.
959 These "row processor" functions populate a particular attribute
960 on a particular mapped instance.
961
962 """
963
964 __slots__ = (
965 "parent_property",
966 "is_class_level",
967 "parent",
968 "key",
969 "strategy_key",
970 "strategy_opts",
971 )
972
973 def __init__(self, parent, strategy_key):
974 self.parent_property = parent
975 self.is_class_level = False
976 self.parent = self.parent_property.parent
977 self.key = self.parent_property.key
978 self.strategy_key = strategy_key
979 self.strategy_opts = dict(strategy_key)
980
981 def init_class_attribute(self, mapper):
982 pass
983
984 def setup_query(
985 self, compile_state, query_entity, path, loadopt, adapter, **kwargs
986 ):
987 """Establish column and other state for a given QueryContext.
988
989 This method fulfills the contract specified by MapperProperty.setup().
990
991 StrategizedProperty delegates its setup() method
992 directly to this method.
993
994 """
995
996 def create_row_processor(
997 self,
998 context,
999 query_entity,
1000 path,
1001 loadopt,
1002 mapper,
1003 result,
1004 adapter,
1005 populators,
1006 ):
1007 """Establish row processing functions for a given QueryContext.
1008
1009 This method fulfills the contract specified by
1010 MapperProperty.create_row_processor().
1011
1012 StrategizedProperty delegates its create_row_processor() method
1013 directly to this method.
1014
1015 """
1016
1017 def __str__(self):
1018 return str(self.parent_property)