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

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# Copyright (C) 2005-2020 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: http://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 MapperOption
18from .interfaces import PropComparator
19from .path_registry import _DEFAULT_TOKEN
20from .path_registry import _WILDCARD_TOKEN
21from .path_registry import PathRegistry
22from .path_registry import TokenRegistry
23from .util import _orm_full_deannotate
24from .. import exc as sa_exc
25from .. import inspect
26from .. import util
27from ..sql import expression as sql_expr
28from ..sql.base import _generative
29from ..sql.base import Generative
32class Load(Generative, MapperOption):
33 """Represents loader options which modify the state of a
34 :class:`_query.Query` in order to affect how various mapped attributes are
35 loaded.
37 The :class:`_orm.Load` object is in most cases used implicitly behind the
38 scenes when one makes use of a query option like :func:`_orm.joinedload`,
39 :func:`.defer`, or similar. However, the :class:`_orm.Load` object
40 can also be used directly, and in some cases can be useful.
42 To use :class:`_orm.Load` directly, instantiate it with the target mapped
43 class as the argument. This style of usage is
44 useful when dealing with a :class:`_query.Query`
45 that has multiple entities::
47 myopt = Load(MyClass).joinedload("widgets")
49 The above ``myopt`` can now be used with :meth:`_query.Query.options`,
50 where it
51 will only take effect for the ``MyClass`` entity::
53 session.query(MyClass, MyOtherClass).options(myopt)
55 One case where :class:`_orm.Load`
56 is useful as public API is when specifying
57 "wildcard" options that only take effect for a certain class::
59 session.query(Order).options(Load(Order).lazyload('*'))
61 Above, all relationships on ``Order`` will be lazy-loaded, but other
62 attributes on those descendant objects will load using their normal
63 loader strategy.
65 .. seealso::
67 :ref:`deferred_options`
69 :ref:`deferred_loading_w_multiple`
71 :ref:`relationship_loader_options`
73 """
75 def __init__(self, entity):
76 insp = inspect(entity)
77 self.path = insp._path_registry
78 # note that this .context is shared among all descendant
79 # Load objects
80 self.context = util.OrderedDict()
81 self.local_opts = {}
82 self.is_class_strategy = False
84 @classmethod
85 def for_existing_path(cls, path):
86 load = cls.__new__(cls)
87 load.path = path
88 load.context = {}
89 load.local_opts = {}
90 load._of_type = None
91 return load
93 def _generate_cache_key(self, path):
94 if path.path[0].is_aliased_class:
95 return False
97 serialized = []
98 for (key, loader_path), obj in self.context.items():
99 if key != "loader":
100 continue
102 for local_elem, obj_elem in zip(self.path.path, loader_path):
103 if local_elem is not obj_elem:
104 break
105 else:
106 endpoint = obj._of_type or obj.path.path[-1]
107 chopped = self._chop_path(loader_path, path)
109 if (
110 # means loader_path and path are unrelated,
111 # this does not need to be part of a cache key
112 chopped
113 is None
114 ) or (
115 # means no additional path with loader_path + path
116 # and the endpoint isn't using of_type so isn't modified
117 # into an alias or other unsafe entity
118 not chopped
119 and not obj._of_type
120 ):
121 continue
123 serialized_path = []
125 for token in chopped:
126 if isinstance(token, util.string_types):
127 serialized_path.append(token)
128 elif token.is_aliased_class:
129 return False
130 elif token.is_property:
131 serialized_path.append(token.key)
132 else:
133 assert token.is_mapper
134 serialized_path.append(token.class_)
136 if not serialized_path or endpoint != serialized_path[-1]:
137 if endpoint.is_mapper:
138 serialized_path.append(endpoint.class_)
139 elif endpoint.is_aliased_class:
140 return False
142 serialized.append(
143 (
144 tuple(serialized_path)
145 + (obj.strategy or ())
146 + (
147 tuple(
148 [
149 (key, obj.local_opts[key])
150 for key in sorted(obj.local_opts)
151 ]
152 )
153 if obj.local_opts
154 else ()
155 )
156 )
157 )
158 if not serialized:
159 return None
160 else:
161 return tuple(serialized)
163 def _generate(self):
164 cloned = super(Load, self)._generate()
165 cloned.local_opts = {}
166 return cloned
168 is_opts_only = False
169 is_class_strategy = False
170 strategy = None
171 propagate_to_loaders = False
172 _of_type = None
174 def process_query(self, query):
175 self._process(query, True)
177 def process_query_conditionally(self, query):
178 self._process(query, False)
180 def _process(self, query, raiseerr):
181 current_path = query._current_path
182 if current_path:
183 for (token, start_path), loader in self.context.items():
184 chopped_start_path = self._chop_path(start_path, current_path)
185 if chopped_start_path is not None:
186 query._attributes[(token, chopped_start_path)] = loader
187 else:
188 query._attributes.update(self.context)
190 def _generate_path(
191 self, path, attr, for_strategy, wildcard_key, raiseerr=True
192 ):
193 existing_of_type = self._of_type
194 self._of_type = None
195 if raiseerr and not path.has_entity:
196 if isinstance(path, TokenRegistry):
197 raise sa_exc.ArgumentError(
198 "Wildcard token cannot be followed by another entity"
199 )
200 else:
201 raise sa_exc.ArgumentError(
202 "Mapped attribute '%s' does not "
203 "refer to a mapped entity" % (path.prop,)
204 )
206 if isinstance(attr, util.string_types):
207 default_token = attr.endswith(_DEFAULT_TOKEN)
208 if attr.endswith(_WILDCARD_TOKEN) or default_token:
209 if default_token:
210 self.propagate_to_loaders = False
211 if wildcard_key:
212 attr = "%s:%s" % (wildcard_key, attr)
214 # TODO: AliasedInsp inside the path for of_type is not
215 # working for a with_polymorphic entity because the
216 # relationship loaders don't render the with_poly into the
217 # path. See #4469 which will try to improve this
218 if existing_of_type and not existing_of_type.is_aliased_class:
219 path = path.parent[existing_of_type]
220 path = path.token(attr)
221 self.path = path
222 return path
224 if existing_of_type:
225 ent = inspect(existing_of_type)
226 else:
227 ent = path.entity
229 try:
230 # use getattr on the class to work around
231 # synonyms, hybrids, etc.
232 attr = getattr(ent.class_, attr)
233 except AttributeError as err:
234 if raiseerr:
235 util.raise_(
236 sa_exc.ArgumentError(
237 'Can\'t find property named "%s" on '
238 "%s in this Query." % (attr, ent)
239 ),
240 replace_context=err,
241 )
242 else:
243 return None
244 else:
245 attr = found_property = attr.property
247 path = path[attr]
248 elif _is_mapped_class(attr):
249 # TODO: this does not appear to be a valid codepath. "attr"
250 # would never be a mapper. This block is present in 1.2
251 # as well however does not seem to be accessed in any tests.
252 if not orm_util._entity_corresponds_to_use_path_impl(
253 attr.parent, path[-1]
254 ):
255 if raiseerr:
256 raise sa_exc.ArgumentError(
257 "Attribute '%s' does not "
258 "link from element '%s'" % (attr, path.entity)
259 )
260 else:
261 return None
262 else:
263 prop = found_property = attr.property
265 if not orm_util._entity_corresponds_to_use_path_impl(
266 attr.parent, path[-1]
267 ):
268 if raiseerr:
269 raise sa_exc.ArgumentError(
270 'Attribute "%s" does not '
271 'link from element "%s".%s'
272 % (
273 attr,
274 path.entity,
275 (
276 " Did you mean to use "
277 "%s.of_type(%s)?"
278 % (path[-2], attr.class_.__name__)
279 if len(path) > 1
280 and path.entity.is_mapper
281 and attr.parent.is_aliased_class
282 else ""
283 ),
284 )
285 )
286 else:
287 return None
289 if getattr(attr, "_of_type", None):
290 ac = attr._of_type
291 ext_info = of_type_info = inspect(ac)
293 existing = path.entity_path[prop].get(
294 self.context, "path_with_polymorphic"
295 )
297 if not ext_info.is_aliased_class:
298 ac = orm_util.with_polymorphic(
299 ext_info.mapper.base_mapper,
300 ext_info.mapper,
301 aliased=True,
302 _use_mapper_path=True,
303 _existing_alias=inspect(existing)
304 if existing is not None
305 else None,
306 )
308 ext_info = inspect(ac)
310 path.entity_path[prop].set(
311 self.context, "path_with_polymorphic", ac
312 )
314 path = path[prop][ext_info]
316 self._of_type = of_type_info
318 else:
319 path = path[prop]
321 if for_strategy is not None:
322 found_property._get_strategy(for_strategy)
323 if path.has_entity:
324 path = path.entity_path
325 self.path = path
326 return path
328 def __str__(self):
329 return "Load(strategy=%r)" % (self.strategy,)
331 def _coerce_strat(self, strategy):
332 if strategy is not None:
333 strategy = tuple(sorted(strategy.items()))
334 return strategy
336 def _apply_to_parent(self, parent, applied, bound):
337 raise NotImplementedError(
338 "Only 'unbound' loader options may be used with the "
339 "Load.options() method"
340 )
342 @_generative
343 def options(self, *opts):
344 r"""Apply a series of options as sub-options to this
345 :class:`_orm.Load`
346 object.
348 E.g.::
350 query = session.query(Author)
351 query = query.options(
352 joinedload(Author.book).options(
353 load_only("summary", "excerpt"),
354 joinedload(Book.citations).options(
355 joinedload(Citation.author)
356 )
357 )
358 )
360 :param \*opts: A series of loader option objects (ultimately
361 :class:`_orm.Load` objects) which should be applied to the path
362 specified by this :class:`_orm.Load` object.
364 .. versionadded:: 1.3.6
366 .. seealso::
368 :func:`.defaultload`
370 :ref:`relationship_loader_options`
372 :ref:`deferred_loading_w_multiple`
374 """
375 apply_cache = {}
376 bound = not isinstance(self, _UnboundLoad)
377 if bound:
378 raise NotImplementedError(
379 "The options() method is currently only supported "
380 "for 'unbound' loader options"
381 )
382 for opt in opts:
383 opt._apply_to_parent(self, apply_cache, bound)
385 @_generative
386 def set_relationship_strategy(
387 self, attr, strategy, propagate_to_loaders=True
388 ):
389 strategy = self._coerce_strat(strategy)
391 self.propagate_to_loaders = propagate_to_loaders
392 cloned = self._clone_for_bind_strategy(attr, strategy, "relationship")
393 self.path = cloned.path
394 self._of_type = cloned._of_type
395 cloned.is_class_strategy = self.is_class_strategy = False
396 self.propagate_to_loaders = cloned.propagate_to_loaders
398 @_generative
399 def set_column_strategy(self, attrs, strategy, opts=None, opts_only=False):
400 strategy = self._coerce_strat(strategy)
402 self.is_class_strategy = False
403 for attr in attrs:
404 cloned = self._clone_for_bind_strategy(
405 attr, strategy, "column", opts_only=opts_only, opts=opts
406 )
407 cloned.propagate_to_loaders = True
409 @_generative
410 def set_generic_strategy(self, attrs, strategy):
411 strategy = self._coerce_strat(strategy)
413 for attr in attrs:
414 cloned = self._clone_for_bind_strategy(attr, strategy, None)
415 cloned.propagate_to_loaders = True
417 @_generative
418 def set_class_strategy(self, strategy, opts):
419 strategy = self._coerce_strat(strategy)
420 cloned = self._clone_for_bind_strategy(None, strategy, None)
421 cloned.is_class_strategy = True
422 cloned.propagate_to_loaders = True
423 cloned.local_opts.update(opts)
425 def _clone_for_bind_strategy(
426 self, attr, strategy, wildcard_key, opts_only=False, opts=None
427 ):
428 """Create an anonymous clone of the Load/_UnboundLoad that is suitable
429 to be placed in the context / _to_bind collection of this Load
430 object. The clone will then lose references to context/_to_bind
431 in order to not create reference cycles.
433 """
434 cloned = self._generate()
435 cloned._generate_path(self.path, attr, strategy, wildcard_key)
436 cloned.strategy = strategy
438 cloned.local_opts = self.local_opts
439 if opts:
440 cloned.local_opts.update(opts)
441 if opts_only:
442 cloned.is_opts_only = True
444 if strategy or cloned.is_opts_only:
445 cloned._set_path_strategy()
446 return cloned
448 def _set_for_path(self, context, path, replace=True, merge_opts=False):
449 if merge_opts or not replace:
450 existing = path.get(self.context, "loader")
452 if existing:
453 if merge_opts:
454 existing.local_opts.update(self.local_opts)
455 else:
456 path.set(context, "loader", self)
457 else:
458 existing = path.get(self.context, "loader")
459 path.set(context, "loader", self)
460 if existing and existing.is_opts_only:
461 self.local_opts.update(existing.local_opts)
463 def _set_path_strategy(self):
464 if not self.is_class_strategy and self.path.has_entity:
465 effective_path = self.path.parent
466 else:
467 effective_path = self.path
469 if effective_path.is_token:
470 for path in effective_path.generate_for_superclasses():
471 self._set_for_path(
472 self.context,
473 path,
474 replace=True,
475 merge_opts=self.is_opts_only,
476 )
477 else:
478 self._set_for_path(
479 self.context,
480 effective_path,
481 replace=True,
482 merge_opts=self.is_opts_only,
483 )
485 # remove cycles; _set_path_strategy is always invoked on an
486 # anonymous clone of the Load / UnboundLoad object since #5056
487 self.context = None
489 def __getstate__(self):
490 d = self.__dict__.copy()
491 if d["context"] is not None:
492 d["context"] = PathRegistry.serialize_context_dict(
493 d["context"], ("loader",)
494 )
495 d["path"] = self.path.serialize()
496 return d
498 def __setstate__(self, state):
499 self.__dict__.update(state)
500 self.path = PathRegistry.deserialize(self.path)
501 if self.context is not None:
502 self.context = PathRegistry.deserialize_context_dict(self.context)
504 def _chop_path(self, to_chop, path):
505 i = -1
507 for i, (c_token, p_token) in enumerate(zip(to_chop, path.path)):
508 if isinstance(c_token, util.string_types):
509 # TODO: this is approximated from the _UnboundLoad
510 # version and probably has issues, not fully covered.
512 if i == 0 and c_token.endswith(":" + _DEFAULT_TOKEN):
513 return to_chop
514 elif (
515 c_token != "relationship:%s" % (_WILDCARD_TOKEN,)
516 and c_token != p_token.key
517 ):
518 return None
520 if c_token is p_token:
521 continue
522 elif (
523 isinstance(c_token, InspectionAttr)
524 and c_token.is_mapper
525 and p_token.is_mapper
526 and c_token.isa(p_token)
527 ):
528 continue
529 else:
530 return None
531 return to_chop[i + 1 :]
534class _UnboundLoad(Load):
535 """Represent a loader option that isn't tied to a root entity.
537 The loader option will produce an entity-linked :class:`_orm.Load`
538 object when it is passed :meth:`_query.Query.options`.
540 This provides compatibility with the traditional system
541 of freestanding options, e.g. ``joinedload('x.y.z')``.
543 """
545 def __init__(self):
546 self.path = ()
547 self._to_bind = []
548 self.local_opts = {}
550 _is_chain_link = False
552 def _generate_cache_key(self, path):
553 serialized = ()
554 for val in self._to_bind:
555 for local_elem, val_elem in zip(self.path, val.path):
556 if local_elem is not val_elem:
557 break
558 else:
559 opt = val._bind_loader([path.path[0]], None, None, False)
560 if opt:
561 c_key = opt._generate_cache_key(path)
562 if c_key is False:
563 return False
564 elif c_key:
565 serialized += c_key
566 if not serialized:
567 return None
568 else:
569 return serialized
571 def _set_path_strategy(self):
572 self._to_bind.append(self)
574 # remove cycles; _set_path_strategy is always invoked on an
575 # anonymous clone of the Load / UnboundLoad object since #5056
576 self._to_bind = None
578 def _apply_to_parent(self, parent, applied, bound, to_bind=None):
579 if self in applied:
580 return applied[self]
582 if to_bind is None:
583 to_bind = self._to_bind
585 cloned = self._generate()
587 applied[self] = cloned
589 cloned.strategy = self.strategy
590 if self.path:
591 attr = self.path[-1]
592 if isinstance(attr, util.string_types) and attr.endswith(
593 _DEFAULT_TOKEN
594 ):
595 attr = attr.split(":")[0] + ":" + _WILDCARD_TOKEN
596 cloned._generate_path(
597 parent.path + self.path[0:-1], attr, self.strategy, None
598 )
600 # these assertions can go away once the "sub options" API is
601 # mature
602 assert cloned.propagate_to_loaders == self.propagate_to_loaders
603 assert cloned.is_class_strategy == self.is_class_strategy
604 assert cloned.is_opts_only == self.is_opts_only
606 new_to_bind = {
607 elem._apply_to_parent(parent, applied, bound, to_bind)
608 for elem in to_bind
609 }
610 cloned._to_bind = parent._to_bind
611 cloned._to_bind.extend(new_to_bind)
612 cloned.local_opts.update(self.local_opts)
614 return cloned
616 def _generate_path(self, path, attr, for_strategy, wildcard_key):
617 if (
618 wildcard_key
619 and isinstance(attr, util.string_types)
620 and attr in (_WILDCARD_TOKEN, _DEFAULT_TOKEN)
621 ):
622 if attr == _DEFAULT_TOKEN:
623 self.propagate_to_loaders = False
624 attr = "%s:%s" % (wildcard_key, attr)
625 if path and _is_mapped_class(path[-1]) and not self.is_class_strategy:
626 path = path[0:-1]
627 if attr:
628 path = path + (attr,)
629 self.path = path
630 return path
632 def __getstate__(self):
633 d = self.__dict__.copy()
634 d["path"] = self._serialize_path(self.path, filter_aliased_class=True)
635 return d
637 def __setstate__(self, state):
638 ret = []
639 for key in state["path"]:
640 if isinstance(key, tuple):
641 if len(key) == 2:
642 # support legacy
643 cls, propkey = key
644 of_type = None
645 else:
646 cls, propkey, of_type = key
647 prop = getattr(cls, propkey)
648 if of_type:
649 prop = prop.of_type(of_type)
650 ret.append(prop)
651 else:
652 ret.append(key)
653 state["path"] = tuple(ret)
654 self.__dict__ = state
656 def _process(self, query, raiseerr):
657 dedupes = query._attributes["_unbound_load_dedupes"]
658 for val in self._to_bind:
659 if val not in dedupes:
660 dedupes.add(val)
661 val._bind_loader(
662 [ent.entity_zero for ent in query._mapper_entities],
663 query._current_path,
664 query._attributes,
665 raiseerr,
666 )
668 @classmethod
669 def _from_keys(cls, meth, keys, chained, kw):
670 opt = _UnboundLoad()
672 def _split_key(key):
673 if isinstance(key, util.string_types):
674 # coerce fooload('*') into "default loader strategy"
675 if key == _WILDCARD_TOKEN:
676 return (_DEFAULT_TOKEN,)
677 # coerce fooload(".*") into "wildcard on default entity"
678 elif key.startswith("." + _WILDCARD_TOKEN):
679 key = key[1:]
680 return key.split(".")
681 else:
682 return (key,)
684 all_tokens = [token for key in keys for token in _split_key(key)]
686 for token in all_tokens[0:-1]:
687 # set _is_chain_link first so that clones of the
688 # object also inherit this flag
689 opt._is_chain_link = True
690 if chained:
691 opt = meth(opt, token, **kw)
692 else:
693 opt = opt.defaultload(token)
695 opt = meth(opt, all_tokens[-1], **kw)
696 opt._is_chain_link = False
698 return opt
700 def _chop_path(self, to_chop, path):
701 i = -1
702 for i, (c_token, (p_entity, p_prop)) in enumerate(
703 zip(to_chop, path.pairs())
704 ):
705 if isinstance(c_token, util.string_types):
706 if i == 0 and c_token.endswith(":" + _DEFAULT_TOKEN):
707 return to_chop
708 elif (
709 c_token != "relationship:%s" % (_WILDCARD_TOKEN,)
710 and c_token != p_prop.key
711 ):
712 return None
713 elif isinstance(c_token, PropComparator):
714 if c_token.property is not p_prop or (
715 c_token._parententity is not p_entity
716 and (
717 not c_token._parententity.is_mapper
718 or not c_token._parententity.isa(p_entity)
719 )
720 ):
721 return None
722 else:
723 i += 1
725 return to_chop[i:]
727 def _serialize_path(self, path, filter_aliased_class=False):
728 ret = []
729 for token in path:
730 if isinstance(token, QueryableAttribute):
731 if (
732 filter_aliased_class
733 and token._of_type
734 and inspect(token._of_type).is_aliased_class
735 ):
736 ret.append((token._parentmapper.class_, token.key, None))
737 else:
738 ret.append(
739 (token._parentmapper.class_, token.key, token._of_type)
740 )
741 elif isinstance(token, PropComparator):
742 ret.append((token._parentmapper.class_, token.key, None))
743 else:
744 ret.append(token)
745 return ret
747 def _bind_loader(self, entities, current_path, context, raiseerr):
748 """Convert from an _UnboundLoad() object into a Load() object.
750 The _UnboundLoad() uses an informal "path" and does not necessarily
751 refer to a lead entity as it may use string tokens. The Load()
752 OTOH refers to a complete path. This method reconciles from a
753 given Query into a Load.
755 Example::
758 query = session.query(User).options(
759 joinedload("orders").joinedload("items"))
761 The above options will be an _UnboundLoad object along the lines
762 of (note this is not the exact API of _UnboundLoad)::
764 _UnboundLoad(
765 _to_bind=[
766 _UnboundLoad(["orders"], {"lazy": "joined"}),
767 _UnboundLoad(["orders", "items"], {"lazy": "joined"}),
768 ]
769 )
771 After this method, we get something more like this (again this is
772 not exact API)::
774 Load(
775 User,
776 (User, User.orders.property))
777 Load(
778 User,
779 (User, User.orders.property, Order, Order.items.property))
781 """
783 start_path = self.path
785 if self.is_class_strategy and current_path:
786 start_path += (entities[0],)
788 # _current_path implies we're in a
789 # secondary load with an existing path
791 if current_path:
792 start_path = self._chop_path(start_path, current_path)
794 if not start_path:
795 return None
797 # look at the first token and try to locate within the Query
798 # what entity we are referring towards.
799 token = start_path[0]
801 if isinstance(token, util.string_types):
802 entity = self._find_entity_basestring(entities, token, raiseerr)
803 elif isinstance(token, PropComparator):
804 prop = token.property
805 entity = self._find_entity_prop_comparator(
806 entities, prop, token._parententity, raiseerr
807 )
808 elif self.is_class_strategy and _is_mapped_class(token):
809 entity = inspect(token)
810 if entity not in entities:
811 entity = None
812 else:
813 raise sa_exc.ArgumentError(
814 "mapper option expects " "string key or list of attributes"
815 )
817 if not entity:
818 return
820 path_element = entity
822 # transfer our entity-less state into a Load() object
823 # with a real entity path. Start with the lead entity
824 # we just located, then go through the rest of our path
825 # tokens and populate into the Load().
826 loader = Load(path_element)
827 if context is not None:
828 loader.context = context
829 else:
830 context = loader.context
832 loader.strategy = self.strategy
833 loader.is_opts_only = self.is_opts_only
834 loader.is_class_strategy = self.is_class_strategy
836 path = loader.path
838 if not loader.is_class_strategy:
839 for idx, token in enumerate(start_path):
840 if not loader._generate_path(
841 loader.path,
842 token,
843 self.strategy if idx == len(start_path) - 1 else None,
844 None,
845 raiseerr,
846 ):
847 return
849 loader.local_opts.update(self.local_opts)
851 if not loader.is_class_strategy and loader.path.has_entity:
852 effective_path = loader.path.parent
853 else:
854 effective_path = loader.path
856 # prioritize "first class" options over those
857 # that were "links in the chain", e.g. "x" and "y" in
858 # someload("x.y.z") versus someload("x") / someload("x.y")
860 if effective_path.is_token:
861 for path in effective_path.generate_for_superclasses():
862 loader._set_for_path(
863 context,
864 path,
865 replace=not self._is_chain_link,
866 merge_opts=self.is_opts_only,
867 )
868 else:
869 loader._set_for_path(
870 context,
871 effective_path,
872 replace=not self._is_chain_link,
873 merge_opts=self.is_opts_only,
874 )
876 return loader
878 def _find_entity_prop_comparator(self, entities, prop, mapper, raiseerr):
879 if _is_aliased_class(mapper):
880 searchfor = mapper
881 else:
882 searchfor = _class_to_mapper(mapper)
883 for ent in entities:
884 if orm_util._entity_corresponds_to(ent, searchfor):
885 return ent
886 else:
887 if raiseerr:
888 if not list(entities):
889 raise sa_exc.ArgumentError(
890 "Query has only expression-based entities, "
891 'which do not apply to %s "%s"'
892 % (util.clsname_as_plain_name(type(prop)), prop)
893 )
894 else:
895 raise sa_exc.ArgumentError(
896 'Mapped attribute "%s" does not apply to any of the '
897 "root entities in this query, e.g. %s. Please "
898 "specify the full path "
899 "from one of the root entities to the target "
900 "attribute. "
901 % (prop, ", ".join(str(x) for x in entities))
902 )
903 else:
904 return None
906 def _find_entity_basestring(self, entities, token, raiseerr):
907 if token.endswith(":" + _WILDCARD_TOKEN):
908 if len(list(entities)) != 1:
909 if raiseerr:
910 raise sa_exc.ArgumentError(
911 "Can't apply wildcard ('*') or load_only() "
912 "loader option to multiple entities %s. Specify "
913 "loader options for each entity individually, such "
914 "as %s."
915 % (
916 ", ".join(str(ent) for ent in entities),
917 ", ".join(
918 "Load(%s).some_option('*')" % ent
919 for ent in entities
920 ),
921 )
922 )
923 elif token.endswith(_DEFAULT_TOKEN):
924 raiseerr = False
926 for ent in entities:
927 # return only the first _MapperEntity when searching
928 # based on string prop name. Ideally object
929 # attributes are used to specify more exactly.
930 return ent
931 else:
932 if raiseerr:
933 raise sa_exc.ArgumentError(
934 "Query has only expression-based entities - "
935 'can\'t find property named "%s".' % (token,)
936 )
937 else:
938 return None
941class loader_option(object):
942 def __init__(self):
943 pass
945 def __call__(self, fn):
946 self.name = name = fn.__name__
947 self.fn = fn
948 if hasattr(Load, name):
949 raise TypeError("Load class already has a %s method." % (name))
950 setattr(Load, name, fn)
952 return self
954 def _add_unbound_fn(self, fn):
955 self._unbound_fn = fn
956 fn_doc = self.fn.__doc__
957 self.fn.__doc__ = """Produce a new :class:`_orm.Load` object with the
958:func:`_orm.%(name)s` option applied.
960See :func:`_orm.%(name)s` for usage examples.
962""" % {
963 "name": self.name
964 }
966 fn.__doc__ = fn_doc
967 return self
969 def _add_unbound_all_fn(self, fn):
970 fn.__doc__ = """Produce a standalone "all" option for
971:func:`_orm.%(name)s`.
973.. deprecated:: 0.9
975 The :func:`_orm.%(name)s_all` function is deprecated, and will be removed
976 in a future release. Please use method chaining with
977 :func:`_orm.%(name)s` instead, as in::
979 session.query(MyClass).options(
980 %(name)s("someattribute").%(name)s("anotherattribute")
981 )
983""" % {
984 "name": self.name
985 }
986 fn = util.deprecated(
987 "0.9",
988 "The :func:`.%(name)s_all` function is deprecated, and will be "
989 "removed in a future release. Please use method chaining with "
990 ":func:`.%(name)s` instead" % {"name": self.name},
991 add_deprecation_to_docstring=False,
992 )(fn)
994 self._unbound_all_fn = fn
995 return self
998@loader_option()
999def contains_eager(loadopt, attr, alias=None):
1000 r"""Indicate that the given attribute should be eagerly loaded from
1001 columns stated manually in the query.
1003 This function is part of the :class:`_orm.Load` interface and supports
1004 both method-chained and standalone operation.
1006 The option is used in conjunction with an explicit join that loads
1007 the desired rows, i.e.::
1009 sess.query(Order).\
1010 join(Order.user).\
1011 options(contains_eager(Order.user))
1013 The above query would join from the ``Order`` entity to its related
1014 ``User`` entity, and the returned ``Order`` objects would have the
1015 ``Order.user`` attribute pre-populated.
1017 When making use of aliases with :func:`.contains_eager`, the path
1018 should be specified using :meth:`.PropComparator.of_type`::
1020 user_alias = aliased(User)
1021 sess.query(Order).\
1022 join((user_alias, Order.user)).\
1023 options(contains_eager(Order.user.of_type(user_alias)))
1025 :meth:`.PropComparator.of_type` is also used to indicate a join
1026 against specific subclasses of an inherting mapper, or
1027 of a :func:`.with_polymorphic` construct::
1029 # employees of a particular subtype
1030 sess.query(Company).\
1031 outerjoin(Company.employees.of_type(Manager)).\
1032 options(
1033 contains_eager(
1034 Company.employees.of_type(Manager),
1035 )
1036 )
1038 # employees of a multiple subtypes
1039 wp = with_polymorphic(Employee, [Manager, Engineer])
1040 sess.query(Company).\
1041 outerjoin(Company.employees.of_type(wp)).\
1042 options(
1043 contains_eager(
1044 Company.employees.of_type(wp),
1045 )
1046 )
1048 The :paramref:`.contains_eager.alias` parameter is used for a similar
1049 purpose, however the :meth:`.PropComparator.of_type` approach should work
1050 in all cases and is more effective and explicit.
1052 .. seealso::
1054 :ref:`loading_toplevel`
1056 :ref:`contains_eager`
1058 """
1059 if alias is not None:
1060 if not isinstance(alias, str):
1061 info = inspect(alias)
1062 alias = info.selectable
1064 elif getattr(attr, "_of_type", None):
1065 ot = inspect(attr._of_type)
1066 alias = ot.selectable
1068 cloned = loadopt.set_relationship_strategy(
1069 attr, {"lazy": "joined"}, propagate_to_loaders=False
1070 )
1071 cloned.local_opts["eager_from_alias"] = alias
1072 return cloned
1075@contains_eager._add_unbound_fn
1076def contains_eager(*keys, **kw):
1077 return _UnboundLoad()._from_keys(
1078 _UnboundLoad.contains_eager, keys, True, kw
1079 )
1082@loader_option()
1083def load_only(loadopt, *attrs):
1084 """Indicate that for a particular entity, only the given list
1085 of column-based attribute names should be loaded; all others will be
1086 deferred.
1088 This function is part of the :class:`_orm.Load` interface and supports
1089 both method-chained and standalone operation.
1091 Example - given a class ``User``, load only the ``name`` and ``fullname``
1092 attributes::
1094 session.query(User).options(load_only("name", "fullname"))
1096 Example - given a relationship ``User.addresses -> Address``, specify
1097 subquery loading for the ``User.addresses`` collection, but on each
1098 ``Address`` object load only the ``email_address`` attribute::
1100 session.query(User).options(
1101 subqueryload("addresses").load_only("email_address")
1102 )
1104 For a :class:`_query.Query` that has multiple entities,
1105 the lead entity can be
1106 specifically referred to using the :class:`_orm.Load` constructor::
1108 session.query(User, Address).join(User.addresses).options(
1109 Load(User).load_only("name", "fullname"),
1110 Load(Address).load_only("email_address")
1111 )
1114 .. versionadded:: 0.9.0
1116 """
1117 cloned = loadopt.set_column_strategy(
1118 attrs, {"deferred": False, "instrument": True}
1119 )
1120 cloned.set_column_strategy(
1121 "*", {"deferred": True, "instrument": True}, {"undefer_pks": True}
1122 )
1123 return cloned
1126@load_only._add_unbound_fn
1127def load_only(*attrs):
1128 return _UnboundLoad().load_only(*attrs)
1131@loader_option()
1132def joinedload(loadopt, attr, innerjoin=None):
1133 """Indicate that the given attribute should be loaded using joined
1134 eager loading.
1136 This function is part of the :class:`_orm.Load` interface and supports
1137 both method-chained and standalone operation.
1139 examples::
1141 # joined-load the "orders" collection on "User"
1142 query(User).options(joinedload(User.orders))
1144 # joined-load Order.items and then Item.keywords
1145 query(Order).options(
1146 joinedload(Order.items).joinedload(Item.keywords))
1148 # lazily load Order.items, but when Items are loaded,
1149 # joined-load the keywords collection
1150 query(Order).options(
1151 lazyload(Order.items).joinedload(Item.keywords))
1153 :param innerjoin: if ``True``, indicates that the joined eager load should
1154 use an inner join instead of the default of left outer join::
1156 query(Order).options(joinedload(Order.user, innerjoin=True))
1158 In order to chain multiple eager joins together where some may be
1159 OUTER and others INNER, right-nested joins are used to link them::
1161 query(A).options(
1162 joinedload(A.bs, innerjoin=False).
1163 joinedload(B.cs, innerjoin=True)
1164 )
1166 The above query, linking A.bs via "outer" join and B.cs via "inner" join
1167 would render the joins as "a LEFT OUTER JOIN (b JOIN c)". When using
1168 older versions of SQLite (< 3.7.16), this form of JOIN is translated to
1169 use full subqueries as this syntax is otherwise not directly supported.
1171 The ``innerjoin`` flag can also be stated with the term ``"unnested"``.
1172 This indicates that an INNER JOIN should be used, *unless* the join
1173 is linked to a LEFT OUTER JOIN to the left, in which case it
1174 will render as LEFT OUTER JOIN. For example, supposing ``A.bs``
1175 is an outerjoin::
1177 query(A).options(
1178 joinedload(A.bs).
1179 joinedload(B.cs, innerjoin="unnested")
1180 )
1182 The above join will render as "a LEFT OUTER JOIN b LEFT OUTER JOIN c",
1183 rather than as "a LEFT OUTER JOIN (b JOIN c)".
1185 .. note:: The "unnested" flag does **not** affect the JOIN rendered
1186 from a many-to-many association table, e.g. a table configured
1187 as :paramref:`_orm.relationship.secondary`, to the target table; for
1188 correctness of results, these joins are always INNER and are
1189 therefore right-nested if linked to an OUTER join.
1191 .. versionchanged:: 1.0.0 ``innerjoin=True`` now implies
1192 ``innerjoin="nested"``, whereas in 0.9 it implied
1193 ``innerjoin="unnested"``. In order to achieve the pre-1.0 "unnested"
1194 inner join behavior, use the value ``innerjoin="unnested"``.
1195 See :ref:`migration_3008`.
1197 .. note::
1199 The joins produced by :func:`_orm.joinedload` are **anonymously
1200 aliased**. The criteria by which the join proceeds cannot be
1201 modified, nor can the :class:`_query.Query`
1202 refer to these joins in any way,
1203 including ordering. See :ref:`zen_of_eager_loading` for further
1204 detail.
1206 To produce a specific SQL JOIN which is explicitly available, use
1207 :meth:`_query.Query.join`.
1208 To combine explicit JOINs with eager loading
1209 of collections, use :func:`_orm.contains_eager`; see
1210 :ref:`contains_eager`.
1212 .. seealso::
1214 :ref:`loading_toplevel`
1216 :ref:`joined_eager_loading`
1218 """
1219 loader = loadopt.set_relationship_strategy(attr, {"lazy": "joined"})
1220 if innerjoin is not None:
1221 loader.local_opts["innerjoin"] = innerjoin
1222 return loader
1225@joinedload._add_unbound_fn
1226def joinedload(*keys, **kw):
1227 return _UnboundLoad._from_keys(_UnboundLoad.joinedload, keys, False, kw)
1230@joinedload._add_unbound_all_fn
1231def joinedload_all(*keys, **kw):
1232 return _UnboundLoad._from_keys(_UnboundLoad.joinedload, keys, True, kw)
1235@loader_option()
1236def subqueryload(loadopt, attr):
1237 """Indicate that the given attribute should be loaded using
1238 subquery eager loading.
1240 This function is part of the :class:`_orm.Load` interface and supports
1241 both method-chained and standalone operation.
1243 examples::
1245 # subquery-load the "orders" collection on "User"
1246 query(User).options(subqueryload(User.orders))
1248 # subquery-load Order.items and then Item.keywords
1249 query(Order).options(
1250 subqueryload(Order.items).subqueryload(Item.keywords))
1252 # lazily load Order.items, but when Items are loaded,
1253 # subquery-load the keywords collection
1254 query(Order).options(
1255 lazyload(Order.items).subqueryload(Item.keywords))
1258 .. seealso::
1260 :ref:`loading_toplevel`
1262 :ref:`subquery_eager_loading`
1264 """
1265 return loadopt.set_relationship_strategy(attr, {"lazy": "subquery"})
1268@subqueryload._add_unbound_fn
1269def subqueryload(*keys):
1270 return _UnboundLoad._from_keys(_UnboundLoad.subqueryload, keys, False, {})
1273@subqueryload._add_unbound_all_fn
1274def subqueryload_all(*keys):
1275 return _UnboundLoad._from_keys(_UnboundLoad.subqueryload, keys, True, {})
1278@loader_option()
1279def selectinload(loadopt, attr):
1280 """Indicate that the given attribute should be loaded using
1281 SELECT IN eager loading.
1283 This function is part of the :class:`_orm.Load` interface and supports
1284 both method-chained and standalone operation.
1286 examples::
1288 # selectin-load the "orders" collection on "User"
1289 query(User).options(selectinload(User.orders))
1291 # selectin-load Order.items and then Item.keywords
1292 query(Order).options(
1293 selectinload(Order.items).selectinload(Item.keywords))
1295 # lazily load Order.items, but when Items are loaded,
1296 # selectin-load the keywords collection
1297 query(Order).options(
1298 lazyload(Order.items).selectinload(Item.keywords))
1300 .. versionadded:: 1.2
1302 .. seealso::
1304 :ref:`loading_toplevel`
1306 :ref:`selectin_eager_loading`
1308 """
1309 return loadopt.set_relationship_strategy(attr, {"lazy": "selectin"})
1312@selectinload._add_unbound_fn
1313def selectinload(*keys):
1314 return _UnboundLoad._from_keys(_UnboundLoad.selectinload, keys, False, {})
1317@selectinload._add_unbound_all_fn
1318def selectinload_all(*keys):
1319 return _UnboundLoad._from_keys(_UnboundLoad.selectinload, keys, True, {})
1322@loader_option()
1323def lazyload(loadopt, attr):
1324 """Indicate that the given attribute should be loaded using "lazy"
1325 loading.
1327 This function is part of the :class:`_orm.Load` interface and supports
1328 both method-chained and standalone operation.
1330 .. seealso::
1332 :ref:`loading_toplevel`
1334 :ref:`lazy_loading`
1336 """
1337 return loadopt.set_relationship_strategy(attr, {"lazy": "select"})
1340@lazyload._add_unbound_fn
1341def lazyload(*keys):
1342 return _UnboundLoad._from_keys(_UnboundLoad.lazyload, keys, False, {})
1345@lazyload._add_unbound_all_fn
1346def lazyload_all(*keys):
1347 return _UnboundLoad._from_keys(_UnboundLoad.lazyload, keys, True, {})
1350@loader_option()
1351def immediateload(loadopt, attr):
1352 """Indicate that the given attribute should be loaded using
1353 an immediate load with a per-attribute SELECT statement.
1355 The :func:`.immediateload` option is superseded in general
1356 by the :func:`.selectinload` option, which performs the same task
1357 more efficiently by emitting a SELECT for all loaded objects.
1359 This function is part of the :class:`_orm.Load` interface and supports
1360 both method-chained and standalone operation.
1362 .. seealso::
1364 :ref:`loading_toplevel`
1366 :ref:`selectin_eager_loading`
1368 """
1369 loader = loadopt.set_relationship_strategy(attr, {"lazy": "immediate"})
1370 return loader
1373@immediateload._add_unbound_fn
1374def immediateload(*keys):
1375 return _UnboundLoad._from_keys(_UnboundLoad.immediateload, keys, False, {})
1378@loader_option()
1379def noload(loadopt, attr):
1380 """Indicate that the given relationship attribute should remain unloaded.
1382 This function is part of the :class:`_orm.Load` interface and supports
1383 both method-chained and standalone operation.
1385 :func:`_orm.noload` applies to :func:`_orm.relationship` attributes; for
1386 column-based attributes, see :func:`_orm.defer`.
1388 .. seealso::
1390 :ref:`loading_toplevel`
1392 """
1394 return loadopt.set_relationship_strategy(attr, {"lazy": "noload"})
1397@noload._add_unbound_fn
1398def noload(*keys):
1399 return _UnboundLoad._from_keys(_UnboundLoad.noload, keys, False, {})
1402@loader_option()
1403def raiseload(loadopt, attr, sql_only=False):
1404 """Indicate that the given relationship attribute should disallow lazy loads.
1406 A relationship attribute configured with :func:`_orm.raiseload` will
1407 raise an :exc:`~sqlalchemy.exc.InvalidRequestError` upon access. The
1408 typical way this is useful is when an application is attempting to ensure
1409 that all relationship attributes that are accessed in a particular context
1410 would have been already loaded via eager loading. Instead of having
1411 to read through SQL logs to ensure lazy loads aren't occurring, this
1412 strategy will cause them to raise immediately.
1414 :param sql_only: if True, raise only if the lazy load would emit SQL,
1415 but not if it is only checking the identity map, or determining that
1416 the related value should just be None due to missing keys. When False,
1417 the strategy will raise for all varieties of lazyload.
1419 This function is part of the :class:`_orm.Load` interface and supports
1420 both method-chained and standalone operation.
1422 :func:`_orm.raiseload` applies to :func:`_orm.relationship`
1423 attributes only.
1425 .. versionadded:: 1.1
1427 .. seealso::
1429 :ref:`loading_toplevel`
1431 :ref:`prevent_lazy_with_raiseload`
1433 """
1435 return loadopt.set_relationship_strategy(
1436 attr, {"lazy": "raise_on_sql" if sql_only else "raise"}
1437 )
1440@raiseload._add_unbound_fn
1441def raiseload(*keys, **kw):
1442 return _UnboundLoad._from_keys(_UnboundLoad.raiseload, keys, False, kw)
1445@loader_option()
1446def defaultload(loadopt, attr):
1447 """Indicate an attribute should load using its default loader style.
1449 This method is used to link to other loader options further into
1450 a chain of attributes without altering the loader style of the links
1451 along the chain. For example, to set joined eager loading for an
1452 element of an element::
1454 session.query(MyClass).options(
1455 defaultload(MyClass.someattribute).
1456 joinedload(MyOtherClass.someotherattribute)
1457 )
1459 :func:`.defaultload` is also useful for setting column-level options
1460 on a related class, namely that of :func:`.defer` and :func:`.undefer`::
1462 session.query(MyClass).options(
1463 defaultload(MyClass.someattribute).
1464 defer("some_column").
1465 undefer("some_other_column")
1466 )
1468 .. seealso::
1470 :meth:`_orm.Load.options` - allows for complex hierarchical
1471 loader option structures with less verbosity than with individual
1472 :func:`.defaultload` directives.
1474 :ref:`relationship_loader_options`
1476 :ref:`deferred_loading_w_multiple`
1478 """
1479 return loadopt.set_relationship_strategy(attr, None)
1482@defaultload._add_unbound_fn
1483def defaultload(*keys):
1484 return _UnboundLoad._from_keys(_UnboundLoad.defaultload, keys, False, {})
1487@loader_option()
1488def defer(loadopt, key):
1489 r"""Indicate that the given column-oriented attribute should be deferred,
1490 e.g. not loaded until accessed.
1492 This function is part of the :class:`_orm.Load` interface and supports
1493 both method-chained and standalone operation.
1495 e.g.::
1497 from sqlalchemy.orm import defer
1499 session.query(MyClass).options(
1500 defer("attribute_one"),
1501 defer("attribute_two"))
1503 session.query(MyClass).options(
1504 defer(MyClass.attribute_one),
1505 defer(MyClass.attribute_two))
1507 To specify a deferred load of an attribute on a related class,
1508 the path can be specified one token at a time, specifying the loading
1509 style for each link along the chain. To leave the loading style
1510 for a link unchanged, use :func:`_orm.defaultload`::
1512 session.query(MyClass).options(defaultload("someattr").defer("some_column"))
1514 A :class:`_orm.Load` object that is present on a certain path can have
1515 :meth:`_orm.Load.defer` called multiple times,
1516 each will operate on the same
1517 parent entity::
1520 session.query(MyClass).options(
1521 defaultload("someattr").
1522 defer("some_column").
1523 defer("some_other_column").
1524 defer("another_column")
1525 )
1527 :param key: Attribute to be deferred.
1529 :param \*addl_attrs: This option supports the old 0.8 style
1530 of specifying a path as a series of attributes, which is now superseded
1531 by the method-chained style.
1533 .. deprecated:: 0.9 The \*addl_attrs on :func:`_orm.defer` is
1534 deprecated and will be removed in a future release. Please
1535 use method chaining in conjunction with defaultload() to
1536 indicate a path.
1539 .. seealso::
1541 :ref:`deferred`
1543 :func:`_orm.undefer`
1545 """
1546 return loadopt.set_column_strategy(
1547 (key,), {"deferred": True, "instrument": True}
1548 )
1551@defer._add_unbound_fn
1552def defer(key, *addl_attrs):
1553 if addl_attrs:
1554 util.warn_deprecated(
1555 "The *addl_attrs on orm.defer is deprecated. Please use "
1556 "method chaining in conjunction with defaultload() to "
1557 "indicate a path."
1558 )
1559 return _UnboundLoad._from_keys(
1560 _UnboundLoad.defer, (key,) + addl_attrs, False, {}
1561 )
1564@loader_option()
1565def undefer(loadopt, key):
1566 r"""Indicate that the given column-oriented attribute should be undeferred,
1567 e.g. specified within the SELECT statement of the entity as a whole.
1569 The column being undeferred is typically set up on the mapping as a
1570 :func:`.deferred` attribute.
1572 This function is part of the :class:`_orm.Load` interface and supports
1573 both method-chained and standalone operation.
1575 Examples::
1577 # undefer two columns
1578 session.query(MyClass).options(undefer("col1"), undefer("col2"))
1580 # undefer all columns specific to a single class using Load + *
1581 session.query(MyClass, MyOtherClass).options(
1582 Load(MyClass).undefer("*"))
1584 # undefer a column on a related object
1585 session.query(MyClass).options(
1586 defaultload(MyClass.items).undefer('text'))
1588 :param key: Attribute to be undeferred.
1590 :param \*addl_attrs: This option supports the old 0.8 style
1591 of specifying a path as a series of attributes, which is now superseded
1592 by the method-chained style.
1594 .. deprecated:: 0.9 The \*addl_attrs on :func:`_orm.undefer` is
1595 deprecated and will be removed in a future release. Please
1596 use method chaining in conjunction with defaultload() to
1597 indicate a path.
1599 .. seealso::
1601 :ref:`deferred`
1603 :func:`_orm.defer`
1605 :func:`_orm.undefer_group`
1607 """
1608 return loadopt.set_column_strategy(
1609 (key,), {"deferred": False, "instrument": True}
1610 )
1613@undefer._add_unbound_fn
1614def undefer(key, *addl_attrs):
1615 if addl_attrs:
1616 util.warn_deprecated(
1617 "The *addl_attrs on orm.undefer is deprecated. Please use "
1618 "method chaining in conjunction with defaultload() to "
1619 "indicate a path."
1620 )
1621 return _UnboundLoad._from_keys(
1622 _UnboundLoad.undefer, (key,) + addl_attrs, False, {}
1623 )
1626@loader_option()
1627def undefer_group(loadopt, name):
1628 """Indicate that columns within the given deferred group name should be
1629 undeferred.
1631 The columns being undeferred are set up on the mapping as
1632 :func:`.deferred` attributes and include a "group" name.
1634 E.g::
1636 session.query(MyClass).options(undefer_group("large_attrs"))
1638 To undefer a group of attributes on a related entity, the path can be
1639 spelled out using relationship loader options, such as
1640 :func:`_orm.defaultload`::
1642 session.query(MyClass).options(
1643 defaultload("someattr").undefer_group("large_attrs"))
1645 .. versionchanged:: 0.9.0 :func:`_orm.undefer_group` is now specific to a
1646 particular entity load path.
1648 .. seealso::
1650 :ref:`deferred`
1652 :func:`_orm.defer`
1654 :func:`_orm.undefer`
1656 """
1657 return loadopt.set_column_strategy(
1658 "*", None, {"undefer_group_%s" % name: True}, opts_only=True
1659 )
1662@undefer_group._add_unbound_fn
1663def undefer_group(name):
1664 return _UnboundLoad().undefer_group(name)
1667@loader_option()
1668def with_expression(loadopt, key, expression):
1669 r"""Apply an ad-hoc SQL expression to a "deferred expression" attribute.
1671 This option is used in conjunction with the :func:`_orm.query_expression`
1672 mapper-level construct that indicates an attribute which should be the
1673 target of an ad-hoc SQL expression.
1675 E.g.::
1678 sess.query(SomeClass).options(
1679 with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y)
1680 )
1682 .. versionadded:: 1.2
1684 :param key: Attribute to be undeferred.
1686 :param expr: SQL expression to be applied to the attribute.
1688 .. seealso::
1690 :ref:`mapper_querytime_expression`
1692 """
1694 expression = sql_expr._labeled(_orm_full_deannotate(expression))
1696 return loadopt.set_column_strategy(
1697 (key,), {"query_expression": True}, opts={"expression": expression}
1698 )
1701@with_expression._add_unbound_fn
1702def with_expression(key, expression):
1703 return _UnboundLoad._from_keys(
1704 _UnboundLoad.with_expression, (key,), False, {"expression": expression}
1705 )
1708@loader_option()
1709def selectin_polymorphic(loadopt, classes):
1710 """Indicate an eager load should take place for all attributes
1711 specific to a subclass.
1713 This uses an additional SELECT with IN against all matched primary
1714 key values, and is the per-query analogue to the ``"selectin"``
1715 setting on the :paramref:`.mapper.polymorphic_load` parameter.
1717 .. versionadded:: 1.2
1719 .. seealso::
1721 :ref:`polymorphic_selectin`
1723 """
1724 loadopt.set_class_strategy(
1725 {"selectinload_polymorphic": True},
1726 opts={
1727 "entities": tuple(
1728 sorted((inspect(cls) for cls in classes), key=id)
1729 )
1730 },
1731 )
1732 return loadopt
1735@selectin_polymorphic._add_unbound_fn
1736def selectin_polymorphic(base_cls, classes):
1737 ul = _UnboundLoad()
1738 ul.is_class_strategy = True
1739 ul.path = (inspect(base_cls),)
1740 ul.selectin_polymorphic(classes)
1741 return ul