Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/orm/interfaces.py : 81%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# orm/interfaces.py
2# Copyright (C) 2005-2020 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: http://www.opensource.org/licenses/mit-license.php
8"""
10Contains various base classes used throughout the ORM.
12Defines some key base classes prominent within the internals,
13as well as the now-deprecated ORM extension classes.
15Other than the deprecated extensions, this module and the
16classes within are mostly private, though some attributes
17are exposed when inspecting mappings.
19"""
21from __future__ import absolute_import
23import collections
25from . import exc as orm_exc
26from . import path_registry
27from .base import _MappedAttribute # noqa
28from .base import EXT_CONTINUE
29from .base import EXT_SKIP
30from .base import EXT_STOP
31from .base import InspectionAttr # noqa
32from .base import InspectionAttrInfo # noqa
33from .base import MANYTOMANY
34from .base import MANYTOONE
35from .base import NOT_EXTENSION
36from .base import ONETOMANY
37from .. import inspect
38from .. import util
39from ..sql import operators
42# imported later
43MapperExtension = SessionExtension = AttributeExtension = None
45__all__ = (
46 "AttributeExtension",
47 "EXT_CONTINUE",
48 "EXT_STOP",
49 "EXT_SKIP",
50 "ONETOMANY",
51 "MANYTOMANY",
52 "MANYTOONE",
53 "NOT_EXTENSION",
54 "LoaderStrategy",
55 "MapperExtension",
56 "MapperOption",
57 "MapperProperty",
58 "PropComparator",
59 "SessionExtension",
60 "StrategizedProperty",
61)
64class MapperProperty(_MappedAttribute, InspectionAttr, util.MemoizedSlots):
65 """Represent a particular class attribute mapped by :class:`_orm.Mapper`.
67 The most common occurrences of :class:`.MapperProperty` are the
68 mapped :class:`_schema.Column`, which is represented in a mapping as
69 an instance of :class:`.ColumnProperty`,
70 and a reference to another class produced by :func:`_orm.relationship`,
71 represented in the mapping as an instance of
72 :class:`.RelationshipProperty`.
74 """
76 __slots__ = (
77 "_configure_started",
78 "_configure_finished",
79 "parent",
80 "key",
81 "info",
82 )
84 cascade = frozenset()
85 """The set of 'cascade' attribute names.
87 This collection is checked before the 'cascade_iterator' method is called.
89 The collection typically only applies to a RelationshipProperty.
91 """
93 is_property = True
94 """Part of the InspectionAttr interface; states this object is a
95 mapper property.
97 """
99 def _memoized_attr_info(self):
100 """Info dictionary associated with the object, allowing user-defined
101 data to be associated with this :class:`.InspectionAttr`.
103 The dictionary is generated when first accessed. Alternatively,
104 it can be specified as a constructor argument to the
105 :func:`.column_property`, :func:`_orm.relationship`, or
106 :func:`.composite`
107 functions.
109 .. versionchanged:: 1.0.0 :attr:`.MapperProperty.info` is also
110 available on extension types via the
111 :attr:`.InspectionAttrInfo.info` attribute, so that it can apply
112 to a wider variety of ORM and extension constructs.
114 .. seealso::
116 :attr:`.QueryableAttribute.info`
118 :attr:`.SchemaItem.info`
120 """
121 return {}
123 def setup(self, context, query_entity, path, adapter, **kwargs):
124 """Called by Query for the purposes of constructing a SQL statement.
126 Each MapperProperty associated with the target mapper processes the
127 statement referenced by the query context, adding columns and/or
128 criterion as appropriate.
130 """
132 def create_row_processor(
133 self, context, path, mapper, result, adapter, populators
134 ):
135 """Produce row processing functions and append to the given
136 set of populators lists.
138 """
140 def cascade_iterator(
141 self, type_, state, visited_instances=None, halt_on=None
142 ):
143 """Iterate through instances related to the given instance for
144 a particular 'cascade', starting with this MapperProperty.
146 Return an iterator3-tuples (instance, mapper, state).
148 Note that the 'cascade' collection on this MapperProperty is
149 checked first for the given type before cascade_iterator is called.
151 This method typically only applies to RelationshipProperty.
153 """
155 return iter(())
157 def set_parent(self, parent, init):
158 """Set the parent mapper that references this MapperProperty.
160 This method is overridden by some subclasses to perform extra
161 setup when the mapper is first known.
163 """
164 self.parent = parent
166 def instrument_class(self, mapper):
167 """Hook called by the Mapper to the property to initiate
168 instrumentation of the class attribute managed by this
169 MapperProperty.
171 The MapperProperty here will typically call out to the
172 attributes module to set up an InstrumentedAttribute.
174 This step is the first of two steps to set up an InstrumentedAttribute,
175 and is called early in the mapper setup process.
177 The second step is typically the init_class_attribute step,
178 called from StrategizedProperty via the post_instrument_class()
179 hook. This step assigns additional state to the InstrumentedAttribute
180 (specifically the "impl") which has been determined after the
181 MapperProperty has determined what kind of persistence
182 management it needs to do (e.g. scalar, object, collection, etc).
184 """
186 def __init__(self):
187 self._configure_started = False
188 self._configure_finished = False
190 def init(self):
191 """Called after all mappers are created to assemble
192 relationships between mappers and perform other post-mapper-creation
193 initialization steps.
195 """
196 self._configure_started = True
197 self.do_init()
198 self._configure_finished = True
200 @property
201 def class_attribute(self):
202 """Return the class-bound descriptor corresponding to this
203 :class:`.MapperProperty`.
205 This is basically a ``getattr()`` call::
207 return getattr(self.parent.class_, self.key)
209 I.e. if this :class:`.MapperProperty` were named ``addresses``,
210 and the class to which it is mapped is ``User``, this sequence
211 is possible::
213 >>> from sqlalchemy import inspect
214 >>> mapper = inspect(User)
215 >>> addresses_property = mapper.attrs.addresses
216 >>> addresses_property.class_attribute is User.addresses
217 True
218 >>> User.addresses.property is addresses_property
219 True
222 """
224 return getattr(self.parent.class_, self.key)
226 def do_init(self):
227 """Perform subclass-specific initialization post-mapper-creation
228 steps.
230 This is a template method called by the ``MapperProperty``
231 object's init() method.
233 """
235 def post_instrument_class(self, mapper):
236 """Perform instrumentation adjustments that need to occur
237 after init() has completed.
239 The given Mapper is the Mapper invoking the operation, which
240 may not be the same Mapper as self.parent in an inheritance
241 scenario; however, Mapper will always at least be a sub-mapper of
242 self.parent.
244 This method is typically used by StrategizedProperty, which delegates
245 it to LoaderStrategy.init_class_attribute() to perform final setup
246 on the class-bound InstrumentedAttribute.
248 """
250 def merge(
251 self,
252 session,
253 source_state,
254 source_dict,
255 dest_state,
256 dest_dict,
257 load,
258 _recursive,
259 _resolve_conflict_map,
260 ):
261 """Merge the attribute represented by this ``MapperProperty``
262 from source to destination object.
264 """
266 def __repr__(self):
267 return "<%s at 0x%x; %s>" % (
268 self.__class__.__name__,
269 id(self),
270 getattr(self, "key", "no key"),
271 )
274class PropComparator(operators.ColumnOperators):
275 r"""Defines SQL operators for :class:`.MapperProperty` objects.
277 SQLAlchemy allows for operators to
278 be redefined at both the Core and ORM level. :class:`.PropComparator`
279 is the base class of operator redefinition for ORM-level operations,
280 including those of :class:`.ColumnProperty`,
281 :class:`.RelationshipProperty`, and :class:`.CompositeProperty`.
283 .. note:: With the advent of Hybrid properties introduced in SQLAlchemy
284 0.7, as well as Core-level operator redefinition in
285 SQLAlchemy 0.8, the use case for user-defined :class:`.PropComparator`
286 instances is extremely rare. See :ref:`hybrids_toplevel` as well
287 as :ref:`types_operators`.
289 User-defined subclasses of :class:`.PropComparator` may be created. The
290 built-in Python comparison and math operator methods, such as
291 :meth:`.operators.ColumnOperators.__eq__`,
292 :meth:`.operators.ColumnOperators.__lt__`, and
293 :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide
294 new operator behavior. The custom :class:`.PropComparator` is passed to
295 the :class:`.MapperProperty` instance via the ``comparator_factory``
296 argument. In each case,
297 the appropriate subclass of :class:`.PropComparator` should be used::
299 # definition of custom PropComparator subclasses
301 from sqlalchemy.orm.properties import \
302 ColumnProperty,\
303 CompositeProperty,\
304 RelationshipProperty
306 class MyColumnComparator(ColumnProperty.Comparator):
307 def __eq__(self, other):
308 return self.__clause_element__() == other
310 class MyRelationshipComparator(RelationshipProperty.Comparator):
311 def any(self, expression):
312 "define the 'any' operation"
313 # ...
315 class MyCompositeComparator(CompositeProperty.Comparator):
316 def __gt__(self, other):
317 "redefine the 'greater than' operation"
319 return sql.and_(*[a>b for a, b in
320 zip(self.__clause_element__().clauses,
321 other.__composite_values__())])
324 # application of custom PropComparator subclasses
326 from sqlalchemy.orm import column_property, relationship, composite
327 from sqlalchemy import Column, String
329 class SomeMappedClass(Base):
330 some_column = column_property(Column("some_column", String),
331 comparator_factory=MyColumnComparator)
333 some_relationship = relationship(SomeOtherClass,
334 comparator_factory=MyRelationshipComparator)
336 some_composite = composite(
337 Column("a", String), Column("b", String),
338 comparator_factory=MyCompositeComparator
339 )
341 Note that for column-level operator redefinition, it's usually
342 simpler to define the operators at the Core level, using the
343 :attr:`.TypeEngine.comparator_factory` attribute. See
344 :ref:`types_operators` for more detail.
346 .. seealso::
348 :class:`.ColumnProperty.Comparator`
350 :class:`.RelationshipProperty.Comparator`
352 :class:`.CompositeProperty.Comparator`
354 :class:`.ColumnOperators`
356 :ref:`types_operators`
358 :attr:`.TypeEngine.comparator_factory`
360 """
362 __slots__ = "prop", "property", "_parententity", "_adapt_to_entity"
364 def __init__(self, prop, parentmapper, adapt_to_entity=None):
365 self.prop = self.property = prop
366 self._parententity = adapt_to_entity or parentmapper
367 self._adapt_to_entity = adapt_to_entity
369 def __clause_element__(self):
370 raise NotImplementedError("%r" % self)
372 def _query_clause_element(self):
373 return self.__clause_element__()
375 def _bulk_update_tuples(self, value):
376 return [(self.__clause_element__(), value)]
378 def adapt_to_entity(self, adapt_to_entity):
379 """Return a copy of this PropComparator which will use the given
380 :class:`.AliasedInsp` to produce corresponding expressions.
381 """
382 return self.__class__(self.prop, self._parententity, adapt_to_entity)
384 @property
385 def _parentmapper(self):
386 """legacy; this is renamed to _parententity to be
387 compatible with QueryableAttribute."""
388 return inspect(self._parententity).mapper
390 @property
391 def adapter(self):
392 """Produce a callable that adapts column expressions
393 to suit an aliased version of this comparator.
395 """
396 if self._adapt_to_entity is None:
397 return None
398 else:
399 return self._adapt_to_entity._adapt_element
401 @property
402 def info(self):
403 return self.property.info
405 @staticmethod
406 def any_op(a, b, **kwargs):
407 return a.any(b, **kwargs)
409 @staticmethod
410 def has_op(a, b, **kwargs):
411 return a.has(b, **kwargs)
413 @staticmethod
414 def of_type_op(a, class_):
415 return a.of_type(class_)
417 def of_type(self, class_):
418 r"""Redefine this object in terms of a polymorphic subclass,
419 :func:`.with_polymorphic` construct, or :func:`.aliased` construct.
421 Returns a new PropComparator from which further criterion can be
422 evaluated.
424 e.g.::
426 query.join(Company.employees.of_type(Engineer)).\
427 filter(Engineer.name=='foo')
429 :param \class_: a class or mapper indicating that criterion will be
430 against this specific subclass.
432 .. seealso::
434 :ref:`inheritance_of_type`
436 """
438 return self.operate(PropComparator.of_type_op, class_)
440 def any(self, criterion=None, **kwargs):
441 r"""Return true if this collection contains any member that meets the
442 given criterion.
444 The usual implementation of ``any()`` is
445 :meth:`.RelationshipProperty.Comparator.any`.
447 :param criterion: an optional ClauseElement formulated against the
448 member class' table or attributes.
450 :param \**kwargs: key/value pairs corresponding to member class
451 attribute names which will be compared via equality to the
452 corresponding values.
454 """
456 return self.operate(PropComparator.any_op, criterion, **kwargs)
458 def has(self, criterion=None, **kwargs):
459 r"""Return true if this element references a member which meets the
460 given criterion.
462 The usual implementation of ``has()`` is
463 :meth:`.RelationshipProperty.Comparator.has`.
465 :param criterion: an optional ClauseElement formulated against the
466 member class' table or attributes.
468 :param \**kwargs: key/value pairs corresponding to member class
469 attribute names which will be compared via equality to the
470 corresponding values.
472 """
474 return self.operate(PropComparator.has_op, criterion, **kwargs)
477class StrategizedProperty(MapperProperty):
478 """A MapperProperty which uses selectable strategies to affect
479 loading behavior.
481 There is a single strategy selected by default. Alternate
482 strategies can be selected at Query time through the usage of
483 ``StrategizedOption`` objects via the Query.options() method.
485 The mechanics of StrategizedProperty are used for every Query
486 invocation for every mapped attribute participating in that Query,
487 to determine first how the attribute will be rendered in SQL
488 and secondly how the attribute will retrieve a value from a result
489 row and apply it to a mapped object. The routines here are very
490 performance-critical.
492 """
494 __slots__ = (
495 "_strategies",
496 "strategy",
497 "_wildcard_token",
498 "_default_path_loader_key",
499 )
501 strategy_wildcard_key = None
503 def _memoized_attr__wildcard_token(self):
504 return (
505 "%s:%s"
506 % (self.strategy_wildcard_key, path_registry._WILDCARD_TOKEN),
507 )
509 def _memoized_attr__default_path_loader_key(self):
510 return (
511 "loader",
512 (
513 "%s:%s"
514 % (self.strategy_wildcard_key, path_registry._DEFAULT_TOKEN),
515 ),
516 )
518 def _get_context_loader(self, context, path):
519 load = None
521 search_path = path[self]
523 # search among: exact match, "attr.*", "default" strategy
524 # if any.
525 for path_key in (
526 search_path._loader_key,
527 search_path._wildcard_path_loader_key,
528 search_path._default_path_loader_key,
529 ):
530 if path_key in context.attributes:
531 load = context.attributes[path_key]
532 break
534 return load
536 def _get_strategy(self, key):
537 try:
538 return self._strategies[key]
539 except KeyError:
540 pass
542 # run outside to prevent transfer of exception context
543 cls = self._strategy_lookup(self, *key)
544 self._strategies[key] = self._strategies[cls] = strategy = cls(
545 self, key
546 )
547 return strategy
549 def setup(self, context, query_entity, path, adapter, **kwargs):
550 loader = self._get_context_loader(context, path)
551 if loader and loader.strategy:
552 strat = self._get_strategy(loader.strategy)
553 else:
554 strat = self.strategy
555 strat.setup_query(
556 context, query_entity, path, loader, adapter, **kwargs
557 )
559 def create_row_processor(
560 self, context, path, mapper, result, adapter, populators
561 ):
562 loader = self._get_context_loader(context, path)
563 if loader and loader.strategy:
564 strat = self._get_strategy(loader.strategy)
565 else:
566 strat = self.strategy
567 strat.create_row_processor(
568 context, path, loader, mapper, result, adapter, populators
569 )
571 def do_init(self):
572 self._strategies = {}
573 self.strategy = self._get_strategy(self.strategy_key)
575 def post_instrument_class(self, mapper):
576 if (
577 not self.parent.non_primary
578 and not mapper.class_manager._attr_has_impl(self.key)
579 ):
580 self.strategy.init_class_attribute(mapper)
582 _all_strategies = collections.defaultdict(dict)
584 @classmethod
585 def strategy_for(cls, **kw):
586 def decorate(dec_cls):
587 # ensure each subclass of the strategy has its
588 # own _strategy_keys collection
589 if "_strategy_keys" not in dec_cls.__dict__:
590 dec_cls._strategy_keys = []
591 key = tuple(sorted(kw.items()))
592 cls._all_strategies[cls][key] = dec_cls
593 dec_cls._strategy_keys.append(key)
594 return dec_cls
596 return decorate
598 @classmethod
599 def _strategy_lookup(cls, requesting_property, *key):
600 for prop_cls in cls.__mro__:
601 if prop_cls in cls._all_strategies:
602 strategies = cls._all_strategies[prop_cls]
603 try:
604 return strategies[key]
605 except KeyError:
606 pass
608 for property_type, strats in cls._all_strategies.items():
609 if key in strats:
610 intended_property_type = property_type
611 actual_strategy = strats[key]
612 break
613 else:
614 intended_property_type = None
615 actual_strategy = None
617 raise orm_exc.LoaderStrategyException(
618 cls,
619 requesting_property,
620 intended_property_type,
621 actual_strategy,
622 key,
623 )
626class MapperOption(object):
627 """Describe a modification to a Query."""
629 propagate_to_loaders = False
630 """if True, indicate this option should be carried along
631 to "secondary" Query objects produced during lazy loads
632 or refresh operations.
634 """
636 def process_query(self, query):
637 """Apply a modification to the given :class:`_query.Query`."""
639 def process_query_conditionally(self, query):
640 """same as process_query(), except that this option may not
641 apply to the given query.
643 This is typically used during a lazy load or scalar refresh
644 operation to propagate options stated in the original Query to the
645 new Query being used for the load. It occurs for those options that
646 specify propagate_to_loaders=True.
648 """
650 self.process_query(query)
652 def _generate_cache_key(self, path):
653 """Used by the "baked lazy loader" to see if this option can be cached.
655 The "baked lazy loader" refers to the :class:`_query.Query` that is
656 produced during a lazy load operation for a mapped relationship.
657 It does not yet apply to the "lazy" load operation for deferred
658 or expired column attributes, however this may change in the future.
660 This loader generates SQL for a query only once and attempts to cache
661 it; from that point on, if the SQL has been cached it will no longer
662 run the :meth:`_query.Query.options` method of the
663 :class:`_query.Query`. The
664 :class:`.MapperOption` object that wishes to participate within a lazy
665 load operation therefore needs to tell the baked loader that it either
666 needs to forego this caching, or that it needs to include the state of
667 the :class:`.MapperOption` itself as part of its cache key, otherwise
668 SQL or other query state that has been affected by the
669 :class:`.MapperOption` may be cached in place of a query that does not
670 include these modifications, or the option may not be invoked at all.
672 By default, this method returns the value ``False``, which means
673 the :class:`.BakedQuery` generated by the lazy loader will
674 not cache the SQL when this :class:`.MapperOption` is present.
675 This is the safest option and ensures both that the option is
676 invoked every time, and also that the cache isn't filled up with
677 an unlimited number of :class:`_query.Query` objects for an unlimited
678 number of :class:`.MapperOption` objects.
680 .. versionchanged:: 1.2.8 the default return value of
681 :meth:`.MapperOption._generate_cache_key` is False; previously it
682 was ``None`` indicating "safe to cache, don't include as part of
683 the cache key"
685 To enable caching of :class:`_query.Query` objects within lazy loaders
686 , a
687 given :class:`.MapperOption` that returns a cache key must return a key
688 that uniquely identifies the complete state of this option, which will
689 match any other :class:`.MapperOption` that itself retains the
690 identical state. This includes path options, flags, etc. It should
691 be a state that is repeatable and part of a limited set of possible
692 options.
694 If the :class:`.MapperOption` does not apply to the given path and
695 would not affect query results on such a path, it should return None,
696 indicating the :class:`_query.Query` is safe to cache for this given
697 loader path and that this :class:`.MapperOption` need not be
698 part of the cache key.
701 """
702 return False
705class LoaderStrategy(object):
706 """Describe the loading behavior of a StrategizedProperty object.
708 The ``LoaderStrategy`` interacts with the querying process in three
709 ways:
711 * it controls the configuration of the ``InstrumentedAttribute``
712 placed on a class to handle the behavior of the attribute. this
713 may involve setting up class-level callable functions to fire
714 off a select operation when the attribute is first accessed
715 (i.e. a lazy load)
717 * it processes the ``QueryContext`` at statement construction time,
718 where it can modify the SQL statement that is being produced.
719 For example, simple column attributes will add their represented
720 column to the list of selected columns, a joined eager loader
721 may establish join clauses to add to the statement.
723 * It produces "row processor" functions at result fetching time.
724 These "row processor" functions populate a particular attribute
725 on a particular mapped instance.
727 """
729 __slots__ = (
730 "parent_property",
731 "is_class_level",
732 "parent",
733 "key",
734 "strategy_key",
735 "strategy_opts",
736 )
738 def __init__(self, parent, strategy_key):
739 self.parent_property = parent
740 self.is_class_level = False
741 self.parent = self.parent_property.parent
742 self.key = self.parent_property.key
743 self.strategy_key = strategy_key
744 self.strategy_opts = dict(strategy_key)
746 def init_class_attribute(self, mapper):
747 pass
749 def setup_query(
750 self, context, query_entity, path, loadopt, adapter, **kwargs
751 ):
752 """Establish column and other state for a given QueryContext.
754 This method fulfills the contract specified by MapperProperty.setup().
756 StrategizedProperty delegates its setup() method
757 directly to this method.
759 """
761 def create_row_processor(
762 self, context, path, loadopt, mapper, result, adapter, populators
763 ):
764 """Establish row processing functions for a given QueryContext.
766 This method fulfills the contract specified by
767 MapperProperty.create_row_processor().
769 StrategizedProperty delegates its create_row_processor() method
770 directly to this method.
772 """
774 def __str__(self):
775 return str(self.parent_property)