Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/strategy_options.py: 25%
650 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
2# <see AUTHORS file>
3#
4# This module is part of SQLAlchemy and is released under
5# the MIT License: https://www.opensource.org/licenses/mit-license.php
7"""
9"""
11from . import util as orm_util
12from .attributes import QueryableAttribute
13from .base import _class_to_mapper
14from .base import _is_aliased_class
15from .base import _is_mapped_class
16from .base import InspectionAttr
17from .interfaces import LoaderOption
18from .interfaces import MapperProperty
19from .interfaces import PropComparator
20from .path_registry import _DEFAULT_TOKEN
21from .path_registry import _WILDCARD_TOKEN
22from .path_registry import PathRegistry
23from .path_registry import TokenRegistry
24from .util import _orm_full_deannotate
25from .. import exc as sa_exc
26from .. import inspect
27from .. import util
28from ..sql import and_
29from ..sql import coercions
30from ..sql import roles
31from ..sql import traversals
32from ..sql import visitors
33from ..sql.base import _generative
34from ..sql.base import Generative
37class Load(Generative, LoaderOption):
38 """Represents loader options which modify the state of a
39 :class:`_query.Query` in order to affect how various mapped attributes are
40 loaded.
42 The :class:`_orm.Load` object is in most cases used implicitly behind the
43 scenes when one makes use of a query option like :func:`_orm.joinedload`,
44 :func:`.defer`, or similar. However, the :class:`_orm.Load` object
45 can also be used directly, and in some cases can be useful.
47 To use :class:`_orm.Load` directly, instantiate it with the target mapped
48 class as the argument. This style of usage is
49 useful when dealing with a :class:`_query.Query`
50 that has multiple entities::
52 myopt = Load(MyClass).joinedload("widgets")
54 The above ``myopt`` can now be used with :meth:`_query.Query.options`,
55 where it
56 will only take effect for the ``MyClass`` entity::
58 session.query(MyClass, MyOtherClass).options(myopt)
60 One case where :class:`_orm.Load`
61 is useful as public API is when specifying
62 "wildcard" options that only take effect for a certain class::
64 session.query(Order).options(Load(Order).lazyload('*'))
66 Above, all relationships on ``Order`` will be lazy-loaded, but other
67 attributes on those descendant objects will load using their normal
68 loader strategy.
70 .. seealso::
72 :ref:`deferred_options`
74 :ref:`deferred_loading_w_multiple`
76 :ref:`relationship_loader_options`
78 """
80 _is_strategy_option = True
82 _cache_key_traversal = [
83 ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key),
84 ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj),
85 ("_of_type", visitors.ExtendedInternalTraversal.dp_multi),
86 ("_extra_criteria", visitors.InternalTraversal.dp_clauseelement_list),
87 (
88 "_context_cache_key",
89 visitors.ExtendedInternalTraversal.dp_has_cache_key_tuples,
90 ),
91 (
92 "local_opts",
93 visitors.ExtendedInternalTraversal.dp_string_multi_dict,
94 ),
95 ]
97 def __init__(self, entity):
98 insp = inspect(entity)
99 insp._post_inspect
101 self.path = insp._path_registry
102 # note that this .context is shared among all descendant
103 # Load objects
104 self.context = util.OrderedDict()
105 self.local_opts = {}
106 self.is_class_strategy = False
108 @classmethod
109 def for_existing_path(cls, path):
110 load = cls.__new__(cls)
111 load.path = path
112 load.context = {}
113 load.local_opts = {}
114 load._of_type = None
115 load._extra_criteria = ()
116 return load
118 def _adapt_cached_option_to_uncached_option(self, context, uncached_opt):
119 return self._adjust_for_extra_criteria(context)
121 def _generate_extra_criteria(self, context):
122 """Apply the current bound parameters in a QueryContext to the
123 immediate "extra_criteria" stored with this Load object.
125 Load objects are typically pulled from the cached version of
126 the statement from a QueryContext. The statement currently being
127 executed will have new values (and keys) for bound parameters in the
128 extra criteria which need to be applied by loader strategies when
129 they handle this criteria for a result set.
131 """
133 assert (
134 self._extra_criteria
135 ), "this should only be called if _extra_criteria is present"
137 orig_query = context.compile_state.select_statement
138 current_query = context.query
140 # NOTE: while it seems like we should not do the "apply" operation
141 # here if orig_query is current_query, skipping it in the "optimized"
142 # case causes the query to be different from a cache key perspective,
143 # because we are creating a copy of the criteria which is no longer
144 # the same identity of the _extra_criteria in the loader option
145 # itself. cache key logic produces a different key for
146 # (A, copy_of_A) vs. (A, A), because in the latter case it shortens
147 # the second part of the key to just indicate on identity.
149 # if orig_query is current_query:
150 # not cached yet. just do the and_()
151 # return and_(*self._extra_criteria)
153 k1 = orig_query._generate_cache_key()
154 k2 = current_query._generate_cache_key()
156 return k2._apply_params_to_element(k1, and_(*self._extra_criteria))
158 def _adjust_for_extra_criteria(self, context):
159 """Apply the current bound parameters in a QueryContext to all
160 occurrences "extra_criteria" stored within al this Load object;
161 copying in place.
163 """
164 orig_query = context.compile_state.select_statement
166 applied = {}
168 ck = [None, None]
170 def process(opt):
171 if not opt._extra_criteria:
172 return
174 if ck[0] is None:
175 ck[:] = (
176 orig_query._generate_cache_key(),
177 context.query._generate_cache_key(),
178 )
179 k1, k2 = ck
181 opt._extra_criteria = tuple(
182 k2._apply_params_to_element(k1, crit)
183 for crit in opt._extra_criteria
184 )
186 return self._deep_clone(applied, process)
188 def _deep_clone(self, applied, process):
189 if self in applied:
190 return applied[self]
192 cloned = self._generate()
194 applied[self] = cloned
196 cloned.strategy = self.strategy
198 assert cloned.propagate_to_loaders == self.propagate_to_loaders
199 assert cloned.is_class_strategy == self.is_class_strategy
200 assert cloned.is_opts_only == self.is_opts_only
202 if self.context:
203 cloned.context = util.OrderedDict(
204 [
205 (
206 key,
207 value._deep_clone(applied, process)
208 if isinstance(value, Load)
209 else value,
210 )
211 for key, value in self.context.items()
212 ]
213 )
215 cloned.local_opts.update(self.local_opts)
217 process(cloned)
219 return cloned
221 @property
222 def _context_cache_key(self):
223 serialized = []
224 if self.context is None:
225 return []
226 for (key, loader_path), obj in self.context.items():
227 if key != "loader":
228 continue
229 serialized.append(loader_path + (obj,))
230 return serialized
232 def _generate(self):
233 cloned = super(Load, self)._generate()
234 cloned.local_opts = {}
235 return cloned
237 is_opts_only = False
238 is_class_strategy = False
239 strategy = None
240 propagate_to_loaders = False
241 _of_type = None
242 _extra_criteria = ()
244 def process_compile_state_replaced_entities(
245 self, compile_state, mapper_entities
246 ):
247 if not compile_state.compile_options._enable_eagerloads:
248 return
250 # process is being run here so that the options given are validated
251 # against what the lead entities were, as well as to accommodate
252 # for the entities having been replaced with equivalents
253 self._process(
254 compile_state,
255 mapper_entities,
256 not bool(compile_state.current_path),
257 )
259 def process_compile_state(self, compile_state):
260 if not compile_state.compile_options._enable_eagerloads:
261 return
263 self._process(
264 compile_state,
265 compile_state._lead_mapper_entities,
266 not bool(compile_state.current_path)
267 and not compile_state.compile_options._for_refresh_state,
268 )
270 def _process(self, compile_state, mapper_entities, raiseerr):
271 is_refresh = compile_state.compile_options._for_refresh_state
272 current_path = compile_state.current_path
273 if current_path:
274 for (token, start_path), loader in self.context.items():
275 if is_refresh and not loader.propagate_to_loaders:
276 continue
277 chopped_start_path = self._chop_path(start_path, current_path)
278 if chopped_start_path is not None:
279 compile_state.attributes[
280 (token, chopped_start_path)
281 ] = loader
282 else:
283 compile_state.attributes.update(self.context)
285 def _generate_path(
286 self,
287 path,
288 attr,
289 for_strategy,
290 wildcard_key,
291 raiseerr=True,
292 polymorphic_entity_context=None,
293 ):
294 existing_of_type = self._of_type
295 self._of_type = None
296 if raiseerr and not path.has_entity:
297 if isinstance(path, TokenRegistry):
298 raise sa_exc.ArgumentError(
299 "Wildcard token cannot be followed by another entity"
300 )
301 else:
302 raise sa_exc.ArgumentError(
303 "Mapped attribute '%s' does not "
304 "refer to a mapped entity" % (path.prop,)
305 )
307 if isinstance(attr, util.string_types):
309 default_token = attr.endswith(_DEFAULT_TOKEN)
310 attr_str_name = attr
311 if attr.endswith(_WILDCARD_TOKEN) or default_token:
312 if default_token:
313 self.propagate_to_loaders = False
314 if wildcard_key:
315 attr = "%s:%s" % (wildcard_key, attr)
317 # TODO: AliasedInsp inside the path for of_type is not
318 # working for a with_polymorphic entity because the
319 # relationship loaders don't render the with_poly into the
320 # path. See #4469 which will try to improve this
321 if existing_of_type and not existing_of_type.is_aliased_class:
322 path = path.parent[existing_of_type]
323 path = path.token(attr)
324 self.path = path
325 return path
327 if existing_of_type:
328 ent = inspect(existing_of_type)
329 else:
330 ent = path.entity
332 util.warn_deprecated_20(
333 "Using strings to indicate column or "
334 "relationship paths in loader options is deprecated "
335 "and will be removed in SQLAlchemy 2.0. Please use "
336 "the class-bound attribute directly.",
337 )
338 try:
339 # use getattr on the class to work around
340 # synonyms, hybrids, etc.
341 attr = getattr(ent.class_, attr)
342 except AttributeError as err:
343 if raiseerr:
344 util.raise_(
345 sa_exc.ArgumentError(
346 'Can\'t find property named "%s" on '
347 "%s in this Query." % (attr, ent)
348 ),
349 replace_context=err,
350 )
351 else:
352 return None
353 else:
354 try:
355 attr = found_property = attr.property
356 except AttributeError as ae:
357 if not isinstance(attr, MapperProperty):
358 util.raise_(
359 sa_exc.ArgumentError(
360 'Expected attribute "%s" on %s to be a '
361 "mapped attribute; "
362 "instead got %s object."
363 % (attr_str_name, ent, type(attr))
364 ),
365 replace_context=ae,
366 )
367 else:
368 raise
370 path = path[attr]
371 else:
372 insp = inspect(attr)
374 if insp.is_mapper or insp.is_aliased_class:
375 # TODO: this does not appear to be a valid codepath. "attr"
376 # would never be a mapper. This block is present in 1.2
377 # as well however does not seem to be accessed in any tests.
378 if not orm_util._entity_corresponds_to_use_path_impl(
379 attr.parent, path[-1]
380 ):
381 if raiseerr:
382 raise sa_exc.ArgumentError(
383 "Attribute '%s' does not "
384 "link from element '%s'" % (attr, path.entity)
385 )
386 else:
387 return None
388 elif insp.is_property:
389 prop = found_property = attr
390 path = path[prop]
391 elif insp.is_attribute:
392 prop = found_property = attr.property
394 if not orm_util._entity_corresponds_to_use_path_impl(
395 attr.parent, path[-1]
396 ):
397 if raiseerr:
398 raise sa_exc.ArgumentError(
399 'Attribute "%s" does not '
400 'link from element "%s".%s'
401 % (
402 attr,
403 path.entity,
404 (
405 " Did you mean to use "
406 "%s.of_type(%s)?"
407 % (path[-2], attr.class_.__name__)
408 if len(path) > 1
409 and path.entity.is_mapper
410 and attr.parent.is_aliased_class
411 else ""
412 ),
413 )
414 )
415 else:
416 return None
418 if attr._extra_criteria and not self._extra_criteria:
419 # in most cases, the process that brings us here will have
420 # already established _extra_criteria. however if not,
421 # and it's present on the attribute, then use that.
422 self._extra_criteria = attr._extra_criteria
424 if getattr(attr, "_of_type", None):
425 ac = attr._of_type
426 ext_info = of_type_info = inspect(ac)
428 if polymorphic_entity_context is None:
429 polymorphic_entity_context = self.context
431 existing = path.entity_path[prop].get(
432 polymorphic_entity_context, "path_with_polymorphic"
433 )
435 if not ext_info.is_aliased_class:
436 ac = orm_util.with_polymorphic(
437 ext_info.mapper.base_mapper,
438 ext_info.mapper,
439 aliased=True,
440 _use_mapper_path=True,
441 _existing_alias=inspect(existing)
442 if existing is not None
443 else None,
444 )
446 ext_info = inspect(ac)
448 path.entity_path[prop].set(
449 polymorphic_entity_context, "path_with_polymorphic", ac
450 )
452 path = path[prop][ext_info]
454 self._of_type = of_type_info
456 else:
457 path = path[prop]
459 if for_strategy is not None:
460 found_property._get_strategy(for_strategy)
461 if path.has_entity:
462 path = path.entity_path
463 self.path = path
464 return path
466 def __str__(self):
467 return "Load(strategy=%r)" % (self.strategy,)
469 def _coerce_strat(self, strategy):
470 if strategy is not None:
471 strategy = tuple(sorted(strategy.items()))
472 return strategy
474 def _apply_to_parent(self, parent, applied, bound):
475 raise NotImplementedError(
476 "Only 'unbound' loader options may be used with the "
477 "Load.options() method"
478 )
480 @_generative
481 def options(self, *opts):
482 r"""Apply a series of options as sub-options to this
483 :class:`_orm.Load`
484 object.
486 E.g.::
488 query = session.query(Author)
489 query = query.options(
490 joinedload(Author.book).options(
491 load_only(Book.summary, Book.excerpt),
492 joinedload(Book.citations).options(
493 joinedload(Citation.author)
494 )
495 )
496 )
498 :param \*opts: A series of loader option objects (ultimately
499 :class:`_orm.Load` objects) which should be applied to the path
500 specified by this :class:`_orm.Load` object.
502 .. versionadded:: 1.3.6
504 .. seealso::
506 :func:`.defaultload`
508 :ref:`relationship_loader_options`
510 :ref:`deferred_loading_w_multiple`
512 """
513 apply_cache = {}
514 bound = not isinstance(self, _UnboundLoad)
515 if bound:
516 raise NotImplementedError(
517 "The options() method is currently only supported "
518 "for 'unbound' loader options"
519 )
520 for opt in opts:
521 try:
522 opt._apply_to_parent(self, apply_cache, bound)
523 except AttributeError as ae:
524 if not isinstance(opt, Load):
525 util.raise_(
526 sa_exc.ArgumentError(
527 "Loader option %s is not compatible with the "
528 "Load.options() method." % (opt,)
529 ),
530 from_=ae,
531 )
532 else:
533 raise
535 @_generative
536 def set_relationship_strategy(
537 self, attr, strategy, propagate_to_loaders=True
538 ):
539 strategy = self._coerce_strat(strategy)
540 self.propagate_to_loaders = propagate_to_loaders
541 cloned = self._clone_for_bind_strategy(attr, strategy, "relationship")
542 self.path = cloned.path
543 self._of_type = cloned._of_type
544 self._extra_criteria = cloned._extra_criteria
545 cloned.is_class_strategy = self.is_class_strategy = False
546 self.propagate_to_loaders = cloned.propagate_to_loaders
548 @_generative
549 def set_column_strategy(self, attrs, strategy, opts=None, opts_only=False):
550 strategy = self._coerce_strat(strategy)
551 self.is_class_strategy = False
552 for attr in attrs:
553 cloned = self._clone_for_bind_strategy(
554 attr, strategy, "column", opts_only=opts_only, opts=opts
555 )
556 cloned.propagate_to_loaders = True
558 @_generative
559 def set_generic_strategy(self, attrs, strategy):
560 strategy = self._coerce_strat(strategy)
561 for attr in attrs:
562 cloned = self._clone_for_bind_strategy(attr, strategy, None)
563 cloned.propagate_to_loaders = True
565 @_generative
566 def set_class_strategy(self, strategy, opts):
567 strategy = self._coerce_strat(strategy)
568 cloned = self._clone_for_bind_strategy(None, strategy, None)
569 cloned.is_class_strategy = True
570 cloned.propagate_to_loaders = True
571 cloned.local_opts.update(opts)
573 def _clone_for_bind_strategy(
574 self, attr, strategy, wildcard_key, opts_only=False, opts=None
575 ):
576 """Create an anonymous clone of the Load/_UnboundLoad that is suitable
577 to be placed in the context / _to_bind collection of this Load
578 object. The clone will then lose references to context/_to_bind
579 in order to not create reference cycles.
581 """
582 cloned = self._generate()
583 cloned._generate_path(self.path, attr, strategy, wildcard_key)
584 cloned.strategy = strategy
586 cloned.local_opts = self.local_opts
587 if opts:
588 cloned.local_opts.update(opts)
589 if opts_only:
590 cloned.is_opts_only = True
592 if strategy or cloned.is_opts_only:
593 cloned._set_path_strategy()
594 return cloned
596 def _set_for_path(self, context, path, replace=True, merge_opts=False):
597 if merge_opts or not replace:
598 existing = path.get(context, "loader")
599 if existing:
600 if merge_opts:
601 existing.local_opts.update(self.local_opts)
602 existing._extra_criteria += self._extra_criteria
603 else:
604 path.set(context, "loader", self)
605 else:
606 existing = path.get(context, "loader")
607 path.set(context, "loader", self)
608 if existing and existing.is_opts_only:
609 self.local_opts.update(existing.local_opts)
610 existing._extra_criteria += self._extra_criteria
612 def _set_path_strategy(self):
613 if not self.is_class_strategy and self.path.has_entity:
614 effective_path = self.path.parent
615 else:
616 effective_path = self.path
618 if effective_path.is_token:
619 for path in effective_path.generate_for_superclasses():
620 self._set_for_path(
621 self.context,
622 path,
623 replace=True,
624 merge_opts=self.is_opts_only,
625 )
626 else:
627 self._set_for_path(
628 self.context,
629 effective_path,
630 replace=True,
631 merge_opts=self.is_opts_only,
632 )
634 # remove cycles; _set_path_strategy is always invoked on an
635 # anonymous clone of the Load / UnboundLoad object since #5056
636 self.context = None
638 def __getstate__(self):
639 d = self.__dict__.copy()
641 # can't pickle this right now; warning is raised by strategies
642 d["_extra_criteria"] = ()
644 if d["context"] is not None:
645 d["context"] = PathRegistry.serialize_context_dict(
646 d["context"], ("loader",)
647 )
648 d["path"] = self.path.serialize()
649 return d
651 def __setstate__(self, state):
652 self.__dict__.update(state)
653 self.path = PathRegistry.deserialize(self.path)
654 if self.context is not None:
655 self.context = PathRegistry.deserialize_context_dict(self.context)
657 def _chop_path(self, to_chop, path):
658 i = -1
660 for i, (c_token, p_token) in enumerate(zip(to_chop, path.path)):
661 if isinstance(c_token, util.string_types):
662 # TODO: this is approximated from the _UnboundLoad
663 # version and probably has issues, not fully covered.
665 if i == 0 and c_token.endswith(":" + _DEFAULT_TOKEN):
666 return to_chop
667 elif (
668 c_token != "relationship:%s" % (_WILDCARD_TOKEN,)
669 and c_token != p_token.key
670 ):
671 return None
673 if c_token is p_token:
674 continue
675 elif (
676 isinstance(c_token, InspectionAttr)
677 and c_token.is_mapper
678 and p_token.is_mapper
679 and c_token.isa(p_token)
680 ):
681 continue
682 else:
683 return None
684 return to_chop[i + 1 :]
687class _UnboundLoad(Load):
688 """Represent a loader option that isn't tied to a root entity.
690 The loader option will produce an entity-linked :class:`_orm.Load`
691 object when it is passed :meth:`_query.Query.options`.
693 This provides compatibility with the traditional system
694 of freestanding options, e.g. ``joinedload('x.y.z')``.
696 """
698 def __init__(self):
699 self.path = ()
700 self._to_bind = []
701 self.local_opts = {}
702 self._extra_criteria = ()
704 def _gen_cache_key(self, anon_map, bindparams, _unbound_option_seen=None):
705 """Inlined gen_cache_key
707 Original traversal is::
710 _cache_key_traversal = [
711 ("path", visitors.ExtendedInternalTraversal.dp_multi_list),
712 ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj),
713 (
714 "_to_bind",
715 visitors.ExtendedInternalTraversal.dp_has_cache_key_list,
716 ),
717 (
718 "_extra_criteria",
719 visitors.InternalTraversal.dp_clauseelement_list),
720 (
721 "local_opts",
722 visitors.ExtendedInternalTraversal.dp_string_multi_dict,
723 ),
724 ]
726 The inlining is so that the "_to_bind" list can be flattened to not
727 repeat the same UnboundLoad options over and over again.
729 See #6869
731 """
733 idself = id(self)
734 cls = self.__class__
736 if idself in anon_map:
737 return (anon_map[idself], cls)
738 else:
739 id_ = anon_map[idself]
741 vis = traversals._cache_key_traversal_visitor
743 seen = _unbound_option_seen
744 if seen is None:
745 seen = set()
747 return (
748 (id_, cls)
749 + vis.visit_multi_list(
750 "path", self.path, self, anon_map, bindparams
751 )
752 + ("strategy", self.strategy)
753 + (
754 (
755 "_to_bind",
756 tuple(
757 elem._gen_cache_key(
758 anon_map, bindparams, _unbound_option_seen=seen
759 )
760 for elem in self._to_bind
761 if elem not in seen and not seen.add(elem)
762 ),
763 )
764 if self._to_bind
765 else ()
766 )
767 + (
768 (
769 "_extra_criteria",
770 tuple(
771 elem._gen_cache_key(anon_map, bindparams)
772 for elem in self._extra_criteria
773 ),
774 )
775 if self._extra_criteria
776 else ()
777 )
778 + (
779 vis.visit_string_multi_dict(
780 "local_opts", self.local_opts, self, anon_map, bindparams
781 )
782 if self.local_opts
783 else ()
784 )
785 )
787 _is_chain_link = False
789 def _set_path_strategy(self):
790 self._to_bind.append(self)
792 # remove cycles; _set_path_strategy is always invoked on an
793 # anonymous clone of the Load / UnboundLoad object since #5056
794 self._to_bind = None
796 def _deep_clone(self, applied, process):
797 if self in applied:
798 return applied[self]
800 cloned = self._generate()
802 applied[self] = cloned
804 cloned.strategy = self.strategy
806 assert cloned.propagate_to_loaders == self.propagate_to_loaders
807 assert cloned.is_class_strategy == self.is_class_strategy
808 assert cloned.is_opts_only == self.is_opts_only
810 cloned._to_bind = [
811 elem._deep_clone(applied, process) for elem in self._to_bind or ()
812 ]
814 cloned.local_opts.update(self.local_opts)
816 process(cloned)
818 return cloned
820 def _apply_to_parent(self, parent, applied, bound, to_bind=None):
821 if self in applied:
822 return applied[self]
824 if to_bind is None:
825 to_bind = self._to_bind
827 cloned = self._generate()
829 applied[self] = cloned
831 cloned.strategy = self.strategy
832 if self.path:
833 attr = self.path[-1]
834 if isinstance(attr, util.string_types) and attr.endswith(
835 _DEFAULT_TOKEN
836 ):
837 attr = attr.split(":")[0] + ":" + _WILDCARD_TOKEN
838 cloned._generate_path(
839 parent.path + self.path[0:-1], attr, self.strategy, None
840 )
842 # these assertions can go away once the "sub options" API is
843 # mature
844 assert cloned.propagate_to_loaders == self.propagate_to_loaders
845 assert cloned.is_class_strategy == self.is_class_strategy
846 assert cloned.is_opts_only == self.is_opts_only
848 uniq = set()
850 cloned._to_bind = parent._to_bind
852 cloned._to_bind[:] = [
853 elem
854 for elem in cloned._to_bind
855 if elem not in uniq and not uniq.add(elem)
856 ] + [
857 elem._apply_to_parent(parent, applied, bound, to_bind)
858 for elem in to_bind
859 if elem not in uniq and not uniq.add(elem)
860 ]
862 cloned.local_opts.update(self.local_opts)
864 return cloned
866 def _generate_path(self, path, attr, for_strategy, wildcard_key):
867 if (
868 wildcard_key
869 and isinstance(attr, util.string_types)
870 and attr in (_WILDCARD_TOKEN, _DEFAULT_TOKEN)
871 ):
872 if attr == _DEFAULT_TOKEN:
873 self.propagate_to_loaders = False
874 attr = "%s:%s" % (wildcard_key, attr)
875 if path and _is_mapped_class(path[-1]) and not self.is_class_strategy:
876 path = path[0:-1]
877 if attr:
878 path = path + (attr,)
879 self.path = path
880 self._extra_criteria = getattr(attr, "_extra_criteria", ())
882 return path
884 def __getstate__(self):
885 d = self.__dict__.copy()
887 # can't pickle this right now; warning is raised by strategies
888 d["_extra_criteria"] = ()
890 d["path"] = self._serialize_path(self.path, filter_aliased_class=True)
891 return d
893 def __setstate__(self, state):
894 ret = []
895 for key in state["path"]:
896 if isinstance(key, tuple):
897 if len(key) == 2:
898 # support legacy
899 cls, propkey = key
900 of_type = None
901 else:
902 cls, propkey, of_type = key
903 prop = getattr(cls, propkey)
904 if of_type:
905 prop = prop.of_type(of_type)
906 ret.append(prop)
907 else:
908 ret.append(key)
909 state["path"] = tuple(ret)
910 self.__dict__ = state
912 def _process(self, compile_state, mapper_entities, raiseerr):
913 dedupes = compile_state.attributes["_unbound_load_dedupes"]
914 is_refresh = compile_state.compile_options._for_refresh_state
915 for val in self._to_bind:
916 if val not in dedupes:
917 dedupes.add(val)
918 if is_refresh and not val.propagate_to_loaders:
919 continue
920 val._bind_loader(
921 [ent.entity_zero for ent in mapper_entities],
922 compile_state.current_path,
923 compile_state.attributes,
924 raiseerr,
925 )
927 @classmethod
928 def _from_keys(cls, meth, keys, chained, kw):
929 opt = _UnboundLoad()
931 def _split_key(key):
932 if isinstance(key, util.string_types):
933 # coerce fooload('*') into "default loader strategy"
934 if key == _WILDCARD_TOKEN:
935 return (_DEFAULT_TOKEN,)
936 # coerce fooload(".*") into "wildcard on default entity"
937 elif key.startswith("." + _WILDCARD_TOKEN):
938 util.warn_deprecated(
939 "The undocumented `.{WILDCARD}` format is deprecated "
940 "and will be removed in a future version as it is "
941 "believed to be unused. "
942 "If you have been using this functionality, please "
943 "comment on Issue #4390 on the SQLAlchemy project "
944 "tracker.",
945 version="1.4",
946 )
947 key = key[1:]
948 return key.split(".")
949 else:
950 return (key,)
952 all_tokens = [token for key in keys for token in _split_key(key)]
954 for token in all_tokens[0:-1]:
955 # set _is_chain_link first so that clones of the
956 # object also inherit this flag
957 opt._is_chain_link = True
958 if chained:
959 opt = meth(opt, token, **kw)
960 else:
961 opt = opt.defaultload(token)
963 opt = meth(opt, all_tokens[-1], **kw)
964 opt._is_chain_link = False
965 return opt
967 def _chop_path(self, to_chop, path):
968 i = -1
969 for i, (c_token, (p_entity, p_prop)) in enumerate(
970 zip(to_chop, path.pairs())
971 ):
972 if isinstance(c_token, util.string_types):
973 if i == 0 and c_token.endswith(":" + _DEFAULT_TOKEN):
974 return to_chop
975 elif (
976 c_token != "relationship:%s" % (_WILDCARD_TOKEN,)
977 and c_token != p_prop.key
978 ):
979 return None
980 elif isinstance(c_token, PropComparator):
981 if c_token.property is not p_prop or (
982 c_token._parententity is not p_entity
983 and (
984 not c_token._parententity.is_mapper
985 or not c_token._parententity.isa(p_entity)
986 )
987 ):
988 return None
989 else:
990 i += 1
992 return to_chop[i:]
994 def _serialize_path(self, path, filter_aliased_class=False):
995 ret = []
996 for token in path:
997 if isinstance(token, QueryableAttribute):
998 if (
999 filter_aliased_class
1000 and token._of_type
1001 and inspect(token._of_type).is_aliased_class
1002 ):
1003 ret.append((token._parentmapper.class_, token.key, None))
1004 else:
1005 ret.append(
1006 (
1007 token._parentmapper.class_,
1008 token.key,
1009 token._of_type.entity if token._of_type else None,
1010 )
1011 )
1012 elif isinstance(token, PropComparator):
1013 ret.append((token._parentmapper.class_, token.key, None))
1014 else:
1015 ret.append(token)
1016 return ret
1018 def _bind_loader(self, entities, current_path, context, raiseerr):
1019 """Convert from an _UnboundLoad() object into a Load() object.
1021 The _UnboundLoad() uses an informal "path" and does not necessarily
1022 refer to a lead entity as it may use string tokens. The Load()
1023 OTOH refers to a complete path. This method reconciles from a
1024 given Query into a Load.
1026 Example::
1029 query = session.query(User).options(
1030 joinedload("orders").joinedload("items"))
1032 The above options will be an _UnboundLoad object along the lines
1033 of (note this is not the exact API of _UnboundLoad)::
1035 _UnboundLoad(
1036 _to_bind=[
1037 _UnboundLoad(["orders"], {"lazy": "joined"}),
1038 _UnboundLoad(["orders", "items"], {"lazy": "joined"}),
1039 ]
1040 )
1042 After this method, we get something more like this (again this is
1043 not exact API)::
1045 Load(
1046 User,
1047 (User, User.orders.property))
1048 Load(
1049 User,
1050 (User, User.orders.property, Order, Order.items.property))
1052 """
1054 start_path = self.path
1056 if self.is_class_strategy and current_path:
1057 start_path += (entities[0],)
1059 # _current_path implies we're in a
1060 # secondary load with an existing path
1062 if current_path:
1063 start_path = self._chop_path(start_path, current_path)
1065 if not start_path:
1066 return None
1068 # look at the first token and try to locate within the Query
1069 # what entity we are referring towards.
1070 token = start_path[0]
1072 if isinstance(token, util.string_types):
1073 entity = self._find_entity_basestring(entities, token, raiseerr)
1074 elif isinstance(token, PropComparator):
1075 prop = token.property
1076 entity = self._find_entity_prop_comparator(
1077 entities, prop, token._parententity, raiseerr
1078 )
1079 elif self.is_class_strategy and _is_mapped_class(token):
1080 entity = inspect(token)
1081 if entity not in entities:
1082 entity = None
1083 else:
1084 raise sa_exc.ArgumentError(
1085 "mapper option expects " "string key or list of attributes"
1086 )
1088 if not entity:
1089 return
1091 path_element = entity
1093 # transfer our entity-less state into a Load() object
1094 # with a real entity path. Start with the lead entity
1095 # we just located, then go through the rest of our path
1096 # tokens and populate into the Load().
1097 loader = Load(path_element)
1099 if context is None:
1100 context = loader.context
1102 loader.strategy = self.strategy
1103 loader.is_opts_only = self.is_opts_only
1104 loader.is_class_strategy = self.is_class_strategy
1105 loader._extra_criteria = self._extra_criteria
1107 path = loader.path
1109 if not loader.is_class_strategy:
1110 for idx, token in enumerate(start_path):
1111 if not loader._generate_path(
1112 loader.path,
1113 token,
1114 self.strategy if idx == len(start_path) - 1 else None,
1115 None,
1116 raiseerr,
1117 polymorphic_entity_context=context,
1118 ):
1119 return
1121 loader.local_opts.update(self.local_opts)
1123 if not loader.is_class_strategy and loader.path.has_entity:
1124 effective_path = loader.path.parent
1125 else:
1126 effective_path = loader.path
1128 # prioritize "first class" options over those
1129 # that were "links in the chain", e.g. "x" and "y" in
1130 # someload("x.y.z") versus someload("x") / someload("x.y")
1132 if effective_path.is_token:
1133 for path in effective_path.generate_for_superclasses():
1134 loader._set_for_path(
1135 context,
1136 path,
1137 replace=not self._is_chain_link,
1138 merge_opts=self.is_opts_only,
1139 )
1140 else:
1141 loader._set_for_path(
1142 context,
1143 effective_path,
1144 replace=not self._is_chain_link,
1145 merge_opts=self.is_opts_only,
1146 )
1148 return loader
1150 def _find_entity_prop_comparator(self, entities, prop, mapper, raiseerr):
1151 if _is_aliased_class(mapper):
1152 searchfor = mapper
1153 else:
1154 searchfor = _class_to_mapper(mapper)
1155 for ent in entities:
1156 if orm_util._entity_corresponds_to(ent, searchfor):
1157 return ent
1158 else:
1159 if raiseerr:
1160 if not list(entities):
1161 raise sa_exc.ArgumentError(
1162 "Query has only expression-based entities, "
1163 'which do not apply to %s "%s"'
1164 % (util.clsname_as_plain_name(type(prop)), prop)
1165 )
1166 else:
1167 raise sa_exc.ArgumentError(
1168 'Mapped attribute "%s" does not apply to any of the '
1169 "root entities in this query, e.g. %s. Please "
1170 "specify the full path "
1171 "from one of the root entities to the target "
1172 "attribute. "
1173 % (prop, ", ".join(str(x) for x in entities))
1174 )
1175 else:
1176 return None
1178 def _find_entity_basestring(self, entities, token, raiseerr):
1179 if token.endswith(":" + _WILDCARD_TOKEN):
1180 if len(list(entities)) != 1:
1181 if raiseerr:
1182 raise sa_exc.ArgumentError(
1183 "Can't apply wildcard ('*') or load_only() "
1184 "loader option to multiple entities %s. Specify "
1185 "loader options for each entity individually, such "
1186 "as %s."
1187 % (
1188 ", ".join(str(ent) for ent in entities),
1189 ", ".join(
1190 "Load(%s).some_option('*')" % ent
1191 for ent in entities
1192 ),
1193 )
1194 )
1195 elif token.endswith(_DEFAULT_TOKEN):
1196 raiseerr = False
1198 for ent in entities:
1199 # return only the first _MapperEntity when searching
1200 # based on string prop name. Ideally object
1201 # attributes are used to specify more exactly.
1202 return ent
1203 else:
1204 if raiseerr:
1205 raise sa_exc.ArgumentError(
1206 "Query has only expression-based entities - "
1207 'can\'t find property named "%s".' % (token,)
1208 )
1209 else:
1210 return None
1213class loader_option(object):
1214 def __init__(self):
1215 pass
1217 def __call__(self, fn):
1218 self.name = name = fn.__name__
1219 self.fn = fn
1220 if hasattr(Load, name):
1221 raise TypeError("Load class already has a %s method." % (name))
1222 setattr(Load, name, fn)
1224 return self
1226 def _add_unbound_fn(self, fn):
1227 self._unbound_fn = fn
1228 fn_doc = self.fn.__doc__
1229 self.fn.__doc__ = """Produce a new :class:`_orm.Load` object with the
1230:func:`_orm.%(name)s` option applied.
1232See :func:`_orm.%(name)s` for usage examples.
1234""" % {
1235 "name": self.name
1236 }
1238 fn.__doc__ = fn_doc
1239 return self
1241 def _add_unbound_all_fn(self, fn):
1242 fn.__doc__ = """Produce a standalone "all" option for
1243:func:`_orm.%(name)s`.
1245.. deprecated:: 0.9
1247 The :func:`_orm.%(name)s_all` function is deprecated, and will be removed
1248 in a future release. Please use method chaining with
1249 :func:`_orm.%(name)s` instead, as in::
1251 session.query(MyClass).options(
1252 %(name)s("someattribute").%(name)s("anotherattribute")
1253 )
1255""" % {
1256 "name": self.name
1257 }
1258 fn = util.deprecated(
1259 # This is used by `baked_lazyload_all` was only deprecated in
1260 # version 1.2 so this must stick around until that is removed
1261 "0.9",
1262 "The :func:`.%(name)s_all` function is deprecated, and will be "
1263 "removed in a future release. Please use method chaining with "
1264 ":func:`.%(name)s` instead" % {"name": self.name},
1265 add_deprecation_to_docstring=False,
1266 )(fn)
1268 self._unbound_all_fn = fn
1269 return self
1272@loader_option()
1273def contains_eager(loadopt, attr, alias=None):
1274 r"""Indicate that the given attribute should be eagerly loaded from
1275 columns stated manually in the query.
1277 This function is part of the :class:`_orm.Load` interface and supports
1278 both method-chained and standalone operation.
1280 The option is used in conjunction with an explicit join that loads
1281 the desired rows, i.e.::
1283 sess.query(Order).\
1284 join(Order.user).\
1285 options(contains_eager(Order.user))
1287 The above query would join from the ``Order`` entity to its related
1288 ``User`` entity, and the returned ``Order`` objects would have the
1289 ``Order.user`` attribute pre-populated.
1291 It may also be used for customizing the entries in an eagerly loaded
1292 collection; queries will normally want to use the
1293 :meth:`_query.Query.populate_existing` method assuming the primary
1294 collection of parent objects may already have been loaded::
1296 sess.query(User).\
1297 join(User.addresses).\
1298 filter(Address.email_address.like('%@aol.com')).\
1299 options(contains_eager(User.addresses)).\
1300 populate_existing()
1302 See the section :ref:`contains_eager` for complete usage details.
1304 .. seealso::
1306 :ref:`loading_toplevel`
1308 :ref:`contains_eager`
1310 """
1311 if alias is not None:
1312 if not isinstance(alias, str):
1313 info = inspect(alias)
1314 alias = info.selectable
1316 else:
1317 util.warn_deprecated(
1318 "Passing a string name for the 'alias' argument to "
1319 "'contains_eager()` is deprecated, and will not work in a "
1320 "future release. Please use a sqlalchemy.alias() or "
1321 "sqlalchemy.orm.aliased() construct.",
1322 version="1.4",
1323 )
1325 elif getattr(attr, "_of_type", None):
1326 ot = inspect(attr._of_type)
1327 alias = ot.selectable
1329 cloned = loadopt.set_relationship_strategy(
1330 attr, {"lazy": "joined"}, propagate_to_loaders=False
1331 )
1332 cloned.local_opts["eager_from_alias"] = alias
1333 return cloned
1336@contains_eager._add_unbound_fn
1337def contains_eager(*keys, **kw):
1338 return _UnboundLoad()._from_keys(
1339 _UnboundLoad.contains_eager, keys, True, kw
1340 )
1343@loader_option()
1344def load_only(loadopt, *attrs):
1345 """Indicate that for a particular entity, only the given list
1346 of column-based attribute names should be loaded; all others will be
1347 deferred.
1349 This function is part of the :class:`_orm.Load` interface and supports
1350 both method-chained and standalone operation.
1352 Example - given a class ``User``, load only the ``name`` and ``fullname``
1353 attributes::
1355 session.query(User).options(load_only(User.name, User.fullname))
1357 Example - given a relationship ``User.addresses -> Address``, specify
1358 subquery loading for the ``User.addresses`` collection, but on each
1359 ``Address`` object load only the ``email_address`` attribute::
1361 session.query(User).options(
1362 subqueryload(User.addresses).load_only(Address.email_address)
1363 )
1365 For a :class:`_query.Query` that has multiple entities,
1366 the lead entity can be
1367 specifically referred to using the :class:`_orm.Load` constructor::
1369 session.query(User, Address).join(User.addresses).options(
1370 Load(User).load_only(User.name, User.fullname),
1371 Load(Address).load_only(Address.email_address)
1372 )
1374 .. note:: This method will still load a :class:`_schema.Column` even
1375 if the column property is defined with ``deferred=True``
1376 for the :func:`.column_property` function.
1378 .. versionadded:: 0.9.0
1380 """
1381 cloned = loadopt.set_column_strategy(
1382 attrs, {"deferred": False, "instrument": True}
1383 )
1384 cloned.set_column_strategy(
1385 "*", {"deferred": True, "instrument": True}, {"undefer_pks": True}
1386 )
1387 return cloned
1390@load_only._add_unbound_fn
1391def load_only(*attrs):
1392 return _UnboundLoad().load_only(*attrs)
1395@loader_option()
1396def joinedload(loadopt, attr, innerjoin=None):
1397 """Indicate that the given attribute should be loaded using joined
1398 eager loading.
1400 This function is part of the :class:`_orm.Load` interface and supports
1401 both method-chained and standalone operation.
1403 examples::
1405 # joined-load the "orders" collection on "User"
1406 query(User).options(joinedload(User.orders))
1408 # joined-load Order.items and then Item.keywords
1409 query(Order).options(
1410 joinedload(Order.items).joinedload(Item.keywords))
1412 # lazily load Order.items, but when Items are loaded,
1413 # joined-load the keywords collection
1414 query(Order).options(
1415 lazyload(Order.items).joinedload(Item.keywords))
1417 :param innerjoin: if ``True``, indicates that the joined eager load should
1418 use an inner join instead of the default of left outer join::
1420 query(Order).options(joinedload(Order.user, innerjoin=True))
1422 In order to chain multiple eager joins together where some may be
1423 OUTER and others INNER, right-nested joins are used to link them::
1425 query(A).options(
1426 joinedload(A.bs, innerjoin=False).
1427 joinedload(B.cs, innerjoin=True)
1428 )
1430 The above query, linking A.bs via "outer" join and B.cs via "inner" join
1431 would render the joins as "a LEFT OUTER JOIN (b JOIN c)". When using
1432 older versions of SQLite (< 3.7.16), this form of JOIN is translated to
1433 use full subqueries as this syntax is otherwise not directly supported.
1435 The ``innerjoin`` flag can also be stated with the term ``"unnested"``.
1436 This indicates that an INNER JOIN should be used, *unless* the join
1437 is linked to a LEFT OUTER JOIN to the left, in which case it
1438 will render as LEFT OUTER JOIN. For example, supposing ``A.bs``
1439 is an outerjoin::
1441 query(A).options(
1442 joinedload(A.bs).
1443 joinedload(B.cs, innerjoin="unnested")
1444 )
1446 The above join will render as "a LEFT OUTER JOIN b LEFT OUTER JOIN c",
1447 rather than as "a LEFT OUTER JOIN (b JOIN c)".
1449 .. note:: The "unnested" flag does **not** affect the JOIN rendered
1450 from a many-to-many association table, e.g. a table configured
1451 as :paramref:`_orm.relationship.secondary`, to the target table; for
1452 correctness of results, these joins are always INNER and are
1453 therefore right-nested if linked to an OUTER join.
1455 .. versionchanged:: 1.0.0 ``innerjoin=True`` now implies
1456 ``innerjoin="nested"``, whereas in 0.9 it implied
1457 ``innerjoin="unnested"``. In order to achieve the pre-1.0 "unnested"
1458 inner join behavior, use the value ``innerjoin="unnested"``.
1459 See :ref:`migration_3008`.
1461 .. note::
1463 The joins produced by :func:`_orm.joinedload` are **anonymously
1464 aliased**. The criteria by which the join proceeds cannot be
1465 modified, nor can the :class:`_query.Query`
1466 refer to these joins in any way,
1467 including ordering. See :ref:`zen_of_eager_loading` for further
1468 detail.
1470 To produce a specific SQL JOIN which is explicitly available, use
1471 :meth:`_query.Query.join`.
1472 To combine explicit JOINs with eager loading
1473 of collections, use :func:`_orm.contains_eager`; see
1474 :ref:`contains_eager`.
1476 .. seealso::
1478 :ref:`loading_toplevel`
1480 :ref:`joined_eager_loading`
1482 """
1483 loader = loadopt.set_relationship_strategy(attr, {"lazy": "joined"})
1484 if innerjoin is not None:
1485 loader.local_opts["innerjoin"] = innerjoin
1486 return loader
1489@joinedload._add_unbound_fn
1490def joinedload(*keys, **kw):
1491 return _UnboundLoad._from_keys(_UnboundLoad.joinedload, keys, False, kw)
1494@loader_option()
1495def subqueryload(loadopt, attr):
1496 """Indicate that the given attribute should be loaded using
1497 subquery eager loading.
1499 This function is part of the :class:`_orm.Load` interface and supports
1500 both method-chained and standalone operation.
1502 examples::
1504 # subquery-load the "orders" collection on "User"
1505 query(User).options(subqueryload(User.orders))
1507 # subquery-load Order.items and then Item.keywords
1508 query(Order).options(
1509 subqueryload(Order.items).subqueryload(Item.keywords))
1511 # lazily load Order.items, but when Items are loaded,
1512 # subquery-load the keywords collection
1513 query(Order).options(
1514 lazyload(Order.items).subqueryload(Item.keywords))
1517 .. seealso::
1519 :ref:`loading_toplevel`
1521 :ref:`subquery_eager_loading`
1523 """
1524 return loadopt.set_relationship_strategy(attr, {"lazy": "subquery"})
1527@subqueryload._add_unbound_fn
1528def subqueryload(*keys):
1529 return _UnboundLoad._from_keys(_UnboundLoad.subqueryload, keys, False, {})
1532@loader_option()
1533def selectinload(loadopt, attr):
1534 """Indicate that the given attribute should be loaded using
1535 SELECT IN eager loading.
1537 This function is part of the :class:`_orm.Load` interface and supports
1538 both method-chained and standalone operation.
1540 examples::
1542 # selectin-load the "orders" collection on "User"
1543 query(User).options(selectinload(User.orders))
1545 # selectin-load Order.items and then Item.keywords
1546 query(Order).options(
1547 selectinload(Order.items).selectinload(Item.keywords))
1549 # lazily load Order.items, but when Items are loaded,
1550 # selectin-load the keywords collection
1551 query(Order).options(
1552 lazyload(Order.items).selectinload(Item.keywords))
1554 .. versionadded:: 1.2
1556 .. seealso::
1558 :ref:`loading_toplevel`
1560 :ref:`selectin_eager_loading`
1562 """
1563 return loadopt.set_relationship_strategy(attr, {"lazy": "selectin"})
1566@selectinload._add_unbound_fn
1567def selectinload(*keys):
1568 return _UnboundLoad._from_keys(_UnboundLoad.selectinload, keys, False, {})
1571@loader_option()
1572def lazyload(loadopt, attr):
1573 """Indicate that the given attribute should be loaded using "lazy"
1574 loading.
1576 This function is part of the :class:`_orm.Load` interface and supports
1577 both method-chained and standalone operation.
1579 .. seealso::
1581 :ref:`loading_toplevel`
1583 :ref:`lazy_loading`
1585 """
1586 return loadopt.set_relationship_strategy(attr, {"lazy": "select"})
1589@lazyload._add_unbound_fn
1590def lazyload(*keys):
1591 return _UnboundLoad._from_keys(_UnboundLoad.lazyload, keys, False, {})
1594@loader_option()
1595def immediateload(loadopt, attr):
1596 """Indicate that the given attribute should be loaded using
1597 an immediate load with a per-attribute SELECT statement.
1599 The load is achieved using the "lazyloader" strategy and does not
1600 fire off any additional eager loaders.
1602 The :func:`.immediateload` option is superseded in general
1603 by the :func:`.selectinload` option, which performs the same task
1604 more efficiently by emitting a SELECT for all loaded objects.
1606 This function is part of the :class:`_orm.Load` interface and supports
1607 both method-chained and standalone operation.
1609 .. seealso::
1611 :ref:`loading_toplevel`
1613 :ref:`selectin_eager_loading`
1615 """
1616 loader = loadopt.set_relationship_strategy(attr, {"lazy": "immediate"})
1617 return loader
1620@immediateload._add_unbound_fn
1621def immediateload(*keys):
1622 return _UnboundLoad._from_keys(_UnboundLoad.immediateload, keys, False, {})
1625@loader_option()
1626def noload(loadopt, attr):
1627 """Indicate that the given relationship attribute should remain unloaded.
1629 The relationship attribute will return ``None`` when accessed without
1630 producing any loading effect.
1632 This function is part of the :class:`_orm.Load` interface and supports
1633 both method-chained and standalone operation.
1635 :func:`_orm.noload` applies to :func:`_orm.relationship` attributes; for
1636 column-based attributes, see :func:`_orm.defer`.
1638 .. note:: Setting this loading strategy as the default strategy
1639 for a relationship using the :paramref:`.orm.relationship.lazy`
1640 parameter may cause issues with flushes, such if a delete operation
1641 needs to load related objects and instead ``None`` was returned.
1643 .. seealso::
1645 :ref:`loading_toplevel`
1647 """
1649 return loadopt.set_relationship_strategy(attr, {"lazy": "noload"})
1652@noload._add_unbound_fn
1653def noload(*keys):
1654 return _UnboundLoad._from_keys(_UnboundLoad.noload, keys, False, {})
1657@loader_option()
1658def raiseload(loadopt, attr, sql_only=False):
1659 """Indicate that the given attribute should raise an error if accessed.
1661 A relationship attribute configured with :func:`_orm.raiseload` will
1662 raise an :exc:`~sqlalchemy.exc.InvalidRequestError` upon access. The
1663 typical way this is useful is when an application is attempting to ensure
1664 that all relationship attributes that are accessed in a particular context
1665 would have been already loaded via eager loading. Instead of having
1666 to read through SQL logs to ensure lazy loads aren't occurring, this
1667 strategy will cause them to raise immediately.
1669 :func:`_orm.raiseload` applies to :func:`_orm.relationship`
1670 attributes only.
1671 In order to apply raise-on-SQL behavior to a column-based attribute,
1672 use the :paramref:`.orm.defer.raiseload` parameter on the :func:`.defer`
1673 loader option.
1675 :param sql_only: if True, raise only if the lazy load would emit SQL, but
1676 not if it is only checking the identity map, or determining that the
1677 related value should just be None due to missing keys. When False, the
1678 strategy will raise for all varieties of relationship loading.
1680 This function is part of the :class:`_orm.Load` interface and supports
1681 both method-chained and standalone operation.
1684 .. versionadded:: 1.1
1686 .. seealso::
1688 :ref:`loading_toplevel`
1690 :ref:`prevent_lazy_with_raiseload`
1692 :ref:`deferred_raiseload`
1694 """
1696 return loadopt.set_relationship_strategy(
1697 attr, {"lazy": "raise_on_sql" if sql_only else "raise"}
1698 )
1701@raiseload._add_unbound_fn
1702def raiseload(*keys, **kw):
1703 return _UnboundLoad._from_keys(_UnboundLoad.raiseload, keys, False, kw)
1706@loader_option()
1707def defaultload(loadopt, attr):
1708 """Indicate an attribute should load using its default loader style.
1710 This method is used to link to other loader options further into
1711 a chain of attributes without altering the loader style of the links
1712 along the chain. For example, to set joined eager loading for an
1713 element of an element::
1715 session.query(MyClass).options(
1716 defaultload(MyClass.someattribute).
1717 joinedload(MyOtherClass.someotherattribute)
1718 )
1720 :func:`.defaultload` is also useful for setting column-level options
1721 on a related class, namely that of :func:`.defer` and :func:`.undefer`::
1723 session.query(MyClass).options(
1724 defaultload(MyClass.someattribute).
1725 defer("some_column").
1726 undefer("some_other_column")
1727 )
1729 .. seealso::
1731 :meth:`_orm.Load.options` - allows for complex hierarchical
1732 loader option structures with less verbosity than with individual
1733 :func:`.defaultload` directives.
1735 :ref:`relationship_loader_options`
1737 :ref:`deferred_loading_w_multiple`
1739 """
1740 return loadopt.set_relationship_strategy(attr, None)
1743@defaultload._add_unbound_fn
1744def defaultload(*keys):
1745 return _UnboundLoad._from_keys(_UnboundLoad.defaultload, keys, False, {})
1748@loader_option()
1749def defer(loadopt, key, raiseload=False):
1750 r"""Indicate that the given column-oriented attribute should be deferred,
1751 e.g. not loaded until accessed.
1753 This function is part of the :class:`_orm.Load` interface and supports
1754 both method-chained and standalone operation.
1756 e.g.::
1758 from sqlalchemy.orm import defer
1760 session.query(MyClass).options(
1761 defer("attribute_one"),
1762 defer("attribute_two"))
1764 session.query(MyClass).options(
1765 defer(MyClass.attribute_one),
1766 defer(MyClass.attribute_two))
1768 To specify a deferred load of an attribute on a related class,
1769 the path can be specified one token at a time, specifying the loading
1770 style for each link along the chain. To leave the loading style
1771 for a link unchanged, use :func:`_orm.defaultload`::
1773 session.query(MyClass).options(defaultload("someattr").defer("some_column"))
1775 A :class:`_orm.Load` object that is present on a certain path can have
1776 :meth:`_orm.Load.defer` called multiple times,
1777 each will operate on the same
1778 parent entity::
1781 session.query(MyClass).options(
1782 defaultload("someattr").
1783 defer("some_column").
1784 defer("some_other_column").
1785 defer("another_column")
1786 )
1788 :param key: Attribute to be deferred.
1790 :param raiseload: raise :class:`.InvalidRequestError` if the column
1791 value is to be loaded from emitting SQL. Used to prevent unwanted
1792 SQL from being emitted.
1794 .. versionadded:: 1.4
1796 .. seealso::
1798 :ref:`deferred_raiseload`
1800 :param \*addl_attrs: This option supports the old 0.8 style
1801 of specifying a path as a series of attributes, which is now superseded
1802 by the method-chained style.
1804 .. deprecated:: 0.9 The \*addl_attrs on :func:`_orm.defer` is
1805 deprecated and will be removed in a future release. Please
1806 use method chaining in conjunction with defaultload() to
1807 indicate a path.
1810 .. seealso::
1812 :ref:`deferred`
1814 :func:`_orm.undefer`
1816 """
1817 strategy = {"deferred": True, "instrument": True}
1818 if raiseload:
1819 strategy["raiseload"] = True
1820 return loadopt.set_column_strategy((key,), strategy)
1823@defer._add_unbound_fn
1824def defer(key, *addl_attrs, **kw):
1825 if addl_attrs:
1826 util.warn_deprecated(
1827 "The *addl_attrs on orm.defer is deprecated. Please use "
1828 "method chaining in conjunction with defaultload() to "
1829 "indicate a path.",
1830 version="1.3",
1831 )
1832 return _UnboundLoad._from_keys(
1833 _UnboundLoad.defer, (key,) + addl_attrs, False, kw
1834 )
1837@loader_option()
1838def undefer(loadopt, key):
1839 r"""Indicate that the given column-oriented attribute should be undeferred,
1840 e.g. specified within the SELECT statement of the entity as a whole.
1842 The column being undeferred is typically set up on the mapping as a
1843 :func:`.deferred` attribute.
1845 This function is part of the :class:`_orm.Load` interface and supports
1846 both method-chained and standalone operation.
1848 Examples::
1850 # undefer two columns
1851 session.query(MyClass).options(undefer("col1"), undefer("col2"))
1853 # undefer all columns specific to a single class using Load + *
1854 session.query(MyClass, MyOtherClass).options(
1855 Load(MyClass).undefer("*"))
1857 # undefer a column on a related object
1858 session.query(MyClass).options(
1859 defaultload(MyClass.items).undefer('text'))
1861 :param key: Attribute to be undeferred.
1863 :param \*addl_attrs: This option supports the old 0.8 style
1864 of specifying a path as a series of attributes, which is now superseded
1865 by the method-chained style.
1867 .. deprecated:: 0.9 The \*addl_attrs on :func:`_orm.undefer` is
1868 deprecated and will be removed in a future release. Please
1869 use method chaining in conjunction with defaultload() to
1870 indicate a path.
1872 .. seealso::
1874 :ref:`deferred`
1876 :func:`_orm.defer`
1878 :func:`_orm.undefer_group`
1880 """
1881 return loadopt.set_column_strategy(
1882 (key,), {"deferred": False, "instrument": True}
1883 )
1886@undefer._add_unbound_fn
1887def undefer(key, *addl_attrs):
1888 if addl_attrs:
1889 util.warn_deprecated(
1890 "The *addl_attrs on orm.undefer is deprecated. Please use "
1891 "method chaining in conjunction with defaultload() to "
1892 "indicate a path.",
1893 version="1.3",
1894 )
1895 return _UnboundLoad._from_keys(
1896 _UnboundLoad.undefer, (key,) + addl_attrs, False, {}
1897 )
1900@loader_option()
1901def undefer_group(loadopt, name):
1902 """Indicate that columns within the given deferred group name should be
1903 undeferred.
1905 The columns being undeferred are set up on the mapping as
1906 :func:`.deferred` attributes and include a "group" name.
1908 E.g::
1910 session.query(MyClass).options(undefer_group("large_attrs"))
1912 To undefer a group of attributes on a related entity, the path can be
1913 spelled out using relationship loader options, such as
1914 :func:`_orm.defaultload`::
1916 session.query(MyClass).options(
1917 defaultload("someattr").undefer_group("large_attrs"))
1919 .. versionchanged:: 0.9.0 :func:`_orm.undefer_group` is now specific to a
1920 particular entity load path.
1922 .. seealso::
1924 :ref:`deferred`
1926 :func:`_orm.defer`
1928 :func:`_orm.undefer`
1930 """
1931 return loadopt.set_column_strategy(
1932 "*", None, {"undefer_group_%s" % name: True}, opts_only=True
1933 )
1936@undefer_group._add_unbound_fn
1937def undefer_group(name):
1938 return _UnboundLoad().undefer_group(name)
1941@loader_option()
1942def with_expression(loadopt, key, expression):
1943 r"""Apply an ad-hoc SQL expression to a "deferred expression" attribute.
1945 This option is used in conjunction with the :func:`_orm.query_expression`
1946 mapper-level construct that indicates an attribute which should be the
1947 target of an ad-hoc SQL expression.
1949 E.g.::
1952 sess.query(SomeClass).options(
1953 with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y)
1954 )
1956 .. versionadded:: 1.2
1958 :param key: Attribute to be populated.
1960 :param expr: SQL expression to be applied to the attribute.
1962 .. versionchanged:: 1.4 Loader options such as
1963 :func:`_orm.with_expression`
1964 take effect only at the **outermost** query used, and should not be used
1965 within subqueries or inner elements of a UNION. See the change notes at
1966 :ref:`change_8879` for background on how to correctly add arbitrary
1967 columns to subqueries.
1969 .. note:: the target attribute is populated only if the target object
1970 is **not currently loaded** in the current :class:`_orm.Session`
1971 unless the :meth:`_query.Query.populate_existing` method is used.
1972 Please refer to :ref:`mapper_querytime_expression` for complete
1973 usage details.
1975 .. seealso::
1977 :ref:`mapper_querytime_expression`
1979 """
1981 expression = coercions.expect(
1982 roles.LabeledColumnExprRole, _orm_full_deannotate(expression)
1983 )
1985 return loadopt.set_column_strategy(
1986 (key,), {"query_expression": True}, opts={"expression": expression}
1987 )
1990@with_expression._add_unbound_fn
1991def with_expression(key, expression):
1992 return _UnboundLoad._from_keys(
1993 _UnboundLoad.with_expression, (key,), False, {"expression": expression}
1994 )
1997@loader_option()
1998def selectin_polymorphic(loadopt, classes):
1999 """Indicate an eager load should take place for all attributes
2000 specific to a subclass.
2002 This uses an additional SELECT with IN against all matched primary
2003 key values, and is the per-query analogue to the ``"selectin"``
2004 setting on the :paramref:`.mapper.polymorphic_load` parameter.
2006 .. versionadded:: 1.2
2008 .. seealso::
2010 :ref:`polymorphic_selectin`
2012 """
2013 loadopt.set_class_strategy(
2014 {"selectinload_polymorphic": True},
2015 opts={
2016 "entities": tuple(
2017 sorted((inspect(cls) for cls in classes), key=id)
2018 )
2019 },
2020 )
2021 return loadopt
2024@selectin_polymorphic._add_unbound_fn
2025def selectin_polymorphic(base_cls, classes):
2026 ul = _UnboundLoad()
2027 ul.is_class_strategy = True
2028 ul.path = (inspect(base_cls),)
2029 ul.selectin_polymorphic(classes)
2030 return ul