123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031 |
- # orm/strategy_options.py
- # Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: https://www.opensource.org/licenses/mit-license.php
- """
- """
- from . import util as orm_util
- from .attributes import QueryableAttribute
- from .base import _class_to_mapper
- from .base import _is_aliased_class
- from .base import _is_mapped_class
- from .base import InspectionAttr
- from .interfaces import LoaderOption
- from .interfaces import MapperProperty
- from .interfaces import PropComparator
- from .path_registry import _DEFAULT_TOKEN
- from .path_registry import _WILDCARD_TOKEN
- from .path_registry import PathRegistry
- from .path_registry import TokenRegistry
- from .util import _orm_full_deannotate
- from .. import exc as sa_exc
- from .. import inspect
- from .. import util
- from ..sql import and_
- from ..sql import coercions
- from ..sql import roles
- from ..sql import traversals
- from ..sql import visitors
- from ..sql.base import _generative
- from ..sql.base import Generative
- class Load(Generative, LoaderOption):
- """Represents loader options which modify the state of a
- :class:`_query.Query` in order to affect how various mapped attributes are
- loaded.
- The :class:`_orm.Load` object is in most cases used implicitly behind the
- scenes when one makes use of a query option like :func:`_orm.joinedload`,
- :func:`.defer`, or similar. However, the :class:`_orm.Load` object
- can also be used directly, and in some cases can be useful.
- To use :class:`_orm.Load` directly, instantiate it with the target mapped
- class as the argument. This style of usage is
- useful when dealing with a :class:`_query.Query`
- that has multiple entities::
- myopt = Load(MyClass).joinedload("widgets")
- The above ``myopt`` can now be used with :meth:`_query.Query.options`,
- where it
- will only take effect for the ``MyClass`` entity::
- session.query(MyClass, MyOtherClass).options(myopt)
- One case where :class:`_orm.Load`
- is useful as public API is when specifying
- "wildcard" options that only take effect for a certain class::
- session.query(Order).options(Load(Order).lazyload('*'))
- Above, all relationships on ``Order`` will be lazy-loaded, but other
- attributes on those descendant objects will load using their normal
- loader strategy.
- .. seealso::
- :ref:`deferred_options`
- :ref:`deferred_loading_w_multiple`
- :ref:`relationship_loader_options`
- """
- _is_strategy_option = True
- _cache_key_traversal = [
- ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key),
- ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj),
- ("_of_type", visitors.ExtendedInternalTraversal.dp_multi),
- ("_extra_criteria", visitors.InternalTraversal.dp_clauseelement_list),
- (
- "_context_cache_key",
- visitors.ExtendedInternalTraversal.dp_has_cache_key_tuples,
- ),
- (
- "local_opts",
- visitors.ExtendedInternalTraversal.dp_string_multi_dict,
- ),
- ]
- def __init__(self, entity):
- insp = inspect(entity)
- insp._post_inspect
- self.path = insp._path_registry
- # note that this .context is shared among all descendant
- # Load objects
- self.context = util.OrderedDict()
- self.local_opts = {}
- self.is_class_strategy = False
- @classmethod
- def for_existing_path(cls, path):
- load = cls.__new__(cls)
- load.path = path
- load.context = {}
- load.local_opts = {}
- load._of_type = None
- load._extra_criteria = ()
- return load
- def _adapt_cached_option_to_uncached_option(self, context, uncached_opt):
- return self._adjust_for_extra_criteria(context)
- def _generate_extra_criteria(self, context):
- """Apply the current bound parameters in a QueryContext to the
- immediate "extra_criteria" stored with this Load object.
- Load objects are typically pulled from the cached version of
- the statement from a QueryContext. The statement currently being
- executed will have new values (and keys) for bound parameters in the
- extra criteria which need to be applied by loader strategies when
- they handle this criteria for a result set.
- """
- assert (
- self._extra_criteria
- ), "this should only be called if _extra_criteria is present"
- orig_query = context.compile_state.select_statement
- current_query = context.query
- # NOTE: while it seems like we should not do the "apply" operation
- # here if orig_query is current_query, skipping it in the "optimized"
- # case causes the query to be different from a cache key perspective,
- # because we are creating a copy of the criteria which is no longer
- # the same identity of the _extra_criteria in the loader option
- # itself. cache key logic produces a different key for
- # (A, copy_of_A) vs. (A, A), because in the latter case it shortens
- # the second part of the key to just indicate on identity.
- # if orig_query is current_query:
- # not cached yet. just do the and_()
- # return and_(*self._extra_criteria)
- k1 = orig_query._generate_cache_key()
- k2 = current_query._generate_cache_key()
- return k2._apply_params_to_element(k1, and_(*self._extra_criteria))
- def _adjust_for_extra_criteria(self, context):
- """Apply the current bound parameters in a QueryContext to all
- occurrences "extra_criteria" stored within al this Load object;
- copying in place.
- """
- orig_query = context.compile_state.select_statement
- applied = {}
- ck = [None, None]
- def process(opt):
- if not opt._extra_criteria:
- return
- if ck[0] is None:
- ck[:] = (
- orig_query._generate_cache_key(),
- context.query._generate_cache_key(),
- )
- k1, k2 = ck
- opt._extra_criteria = tuple(
- k2._apply_params_to_element(k1, crit)
- for crit in opt._extra_criteria
- )
- return self._deep_clone(applied, process)
- def _deep_clone(self, applied, process):
- if self in applied:
- return applied[self]
- cloned = self._generate()
- applied[self] = cloned
- cloned.strategy = self.strategy
- assert cloned.propagate_to_loaders == self.propagate_to_loaders
- assert cloned.is_class_strategy == self.is_class_strategy
- assert cloned.is_opts_only == self.is_opts_only
- if self.context:
- cloned.context = util.OrderedDict(
- [
- (
- key,
- value._deep_clone(applied, process)
- if isinstance(value, Load)
- else value,
- )
- for key, value in self.context.items()
- ]
- )
- cloned.local_opts.update(self.local_opts)
- process(cloned)
- return cloned
- @property
- def _context_cache_key(self):
- serialized = []
- if self.context is None:
- return []
- for (key, loader_path), obj in self.context.items():
- if key != "loader":
- continue
- serialized.append(loader_path + (obj,))
- return serialized
- def _generate(self):
- cloned = super(Load, self)._generate()
- cloned.local_opts = {}
- return cloned
- is_opts_only = False
- is_class_strategy = False
- strategy = None
- propagate_to_loaders = False
- _of_type = None
- _extra_criteria = ()
- def process_compile_state_replaced_entities(
- self, compile_state, mapper_entities
- ):
- if not compile_state.compile_options._enable_eagerloads:
- return
- # process is being run here so that the options given are validated
- # against what the lead entities were, as well as to accommodate
- # for the entities having been replaced with equivalents
- self._process(
- compile_state,
- mapper_entities,
- not bool(compile_state.current_path),
- )
- def process_compile_state(self, compile_state):
- if not compile_state.compile_options._enable_eagerloads:
- return
- self._process(
- compile_state,
- compile_state._lead_mapper_entities,
- not bool(compile_state.current_path)
- and not compile_state.compile_options._for_refresh_state,
- )
- def _process(self, compile_state, mapper_entities, raiseerr):
- is_refresh = compile_state.compile_options._for_refresh_state
- current_path = compile_state.current_path
- if current_path:
- for (token, start_path), loader in self.context.items():
- if is_refresh and not loader.propagate_to_loaders:
- continue
- chopped_start_path = self._chop_path(start_path, current_path)
- if chopped_start_path is not None:
- compile_state.attributes[
- (token, chopped_start_path)
- ] = loader
- else:
- compile_state.attributes.update(self.context)
- def _generate_path(
- self,
- path,
- attr,
- for_strategy,
- wildcard_key,
- raiseerr=True,
- polymorphic_entity_context=None,
- ):
- existing_of_type = self._of_type
- self._of_type = None
- if raiseerr and not path.has_entity:
- if isinstance(path, TokenRegistry):
- raise sa_exc.ArgumentError(
- "Wildcard token cannot be followed by another entity"
- )
- else:
- raise sa_exc.ArgumentError(
- "Mapped attribute '%s' does not "
- "refer to a mapped entity" % (path.prop,)
- )
- if isinstance(attr, util.string_types):
- default_token = attr.endswith(_DEFAULT_TOKEN)
- attr_str_name = attr
- if attr.endswith(_WILDCARD_TOKEN) or default_token:
- if default_token:
- self.propagate_to_loaders = False
- if wildcard_key:
- attr = "%s:%s" % (wildcard_key, attr)
- # TODO: AliasedInsp inside the path for of_type is not
- # working for a with_polymorphic entity because the
- # relationship loaders don't render the with_poly into the
- # path. See #4469 which will try to improve this
- if existing_of_type and not existing_of_type.is_aliased_class:
- path = path.parent[existing_of_type]
- path = path.token(attr)
- self.path = path
- return path
- if existing_of_type:
- ent = inspect(existing_of_type)
- else:
- ent = path.entity
- util.warn_deprecated_20(
- "Using strings to indicate column or "
- "relationship paths in loader options is deprecated "
- "and will be removed in SQLAlchemy 2.0. Please use "
- "the class-bound attribute directly.",
- )
- try:
- # use getattr on the class to work around
- # synonyms, hybrids, etc.
- attr = getattr(ent.class_, attr)
- except AttributeError as err:
- if raiseerr:
- util.raise_(
- sa_exc.ArgumentError(
- 'Can\'t find property named "%s" on '
- "%s in this Query." % (attr, ent)
- ),
- replace_context=err,
- )
- else:
- return None
- else:
- try:
- attr = found_property = attr.property
- except AttributeError as ae:
- if not isinstance(attr, MapperProperty):
- util.raise_(
- sa_exc.ArgumentError(
- 'Expected attribute "%s" on %s to be a '
- "mapped attribute; "
- "instead got %s object."
- % (attr_str_name, ent, type(attr))
- ),
- replace_context=ae,
- )
- else:
- raise
- path = path[attr]
- else:
- insp = inspect(attr)
- if insp.is_mapper or insp.is_aliased_class:
- # TODO: this does not appear to be a valid codepath. "attr"
- # would never be a mapper. This block is present in 1.2
- # as well however does not seem to be accessed in any tests.
- if not orm_util._entity_corresponds_to_use_path_impl(
- attr.parent, path[-1]
- ):
- if raiseerr:
- raise sa_exc.ArgumentError(
- "Attribute '%s' does not "
- "link from element '%s'" % (attr, path.entity)
- )
- else:
- return None
- elif insp.is_property:
- prop = found_property = attr
- path = path[prop]
- elif insp.is_attribute:
- prop = found_property = attr.property
- if not orm_util._entity_corresponds_to_use_path_impl(
- attr.parent, path[-1]
- ):
- if raiseerr:
- raise sa_exc.ArgumentError(
- 'Attribute "%s" does not '
- 'link from element "%s".%s'
- % (
- attr,
- path.entity,
- (
- " Did you mean to use "
- "%s.of_type(%s)?"
- % (path[-2], attr.class_.__name__)
- if len(path) > 1
- and path.entity.is_mapper
- and attr.parent.is_aliased_class
- else ""
- ),
- )
- )
- else:
- return None
- if attr._extra_criteria and not self._extra_criteria:
- # in most cases, the process that brings us here will have
- # already established _extra_criteria. however if not,
- # and it's present on the attribute, then use that.
- self._extra_criteria = attr._extra_criteria
- if getattr(attr, "_of_type", None):
- ac = attr._of_type
- ext_info = of_type_info = inspect(ac)
- if polymorphic_entity_context is None:
- polymorphic_entity_context = self.context
- existing = path.entity_path[prop].get(
- polymorphic_entity_context, "path_with_polymorphic"
- )
- if not ext_info.is_aliased_class:
- ac = orm_util.with_polymorphic(
- ext_info.mapper.base_mapper,
- ext_info.mapper,
- aliased=True,
- _use_mapper_path=True,
- _existing_alias=inspect(existing)
- if existing is not None
- else None,
- )
- ext_info = inspect(ac)
- path.entity_path[prop].set(
- polymorphic_entity_context, "path_with_polymorphic", ac
- )
- path = path[prop][ext_info]
- self._of_type = of_type_info
- else:
- path = path[prop]
- if for_strategy is not None:
- found_property._get_strategy(for_strategy)
- if path.has_entity:
- path = path.entity_path
- self.path = path
- return path
- def __str__(self):
- return "Load(strategy=%r)" % (self.strategy,)
- def _coerce_strat(self, strategy):
- if strategy is not None:
- strategy = tuple(sorted(strategy.items()))
- return strategy
- def _apply_to_parent(self, parent, applied, bound):
- raise NotImplementedError(
- "Only 'unbound' loader options may be used with the "
- "Load.options() method"
- )
- @_generative
- def options(self, *opts):
- r"""Apply a series of options as sub-options to this
- :class:`_orm.Load`
- object.
- E.g.::
- query = session.query(Author)
- query = query.options(
- joinedload(Author.book).options(
- load_only(Book.summary, Book.excerpt),
- joinedload(Book.citations).options(
- joinedload(Citation.author)
- )
- )
- )
- :param \*opts: A series of loader option objects (ultimately
- :class:`_orm.Load` objects) which should be applied to the path
- specified by this :class:`_orm.Load` object.
- .. versionadded:: 1.3.6
- .. seealso::
- :func:`.defaultload`
- :ref:`relationship_loader_options`
- :ref:`deferred_loading_w_multiple`
- """
- apply_cache = {}
- bound = not isinstance(self, _UnboundLoad)
- if bound:
- raise NotImplementedError(
- "The options() method is currently only supported "
- "for 'unbound' loader options"
- )
- for opt in opts:
- try:
- opt._apply_to_parent(self, apply_cache, bound)
- except AttributeError as ae:
- if not isinstance(opt, Load):
- util.raise_(
- sa_exc.ArgumentError(
- "Loader option %s is not compatible with the "
- "Load.options() method." % (opt,)
- ),
- from_=ae,
- )
- else:
- raise
- @_generative
- def set_relationship_strategy(
- self, attr, strategy, propagate_to_loaders=True
- ):
- strategy = self._coerce_strat(strategy)
- self.propagate_to_loaders = propagate_to_loaders
- cloned = self._clone_for_bind_strategy(attr, strategy, "relationship")
- self.path = cloned.path
- self._of_type = cloned._of_type
- self._extra_criteria = cloned._extra_criteria
- cloned.is_class_strategy = self.is_class_strategy = False
- self.propagate_to_loaders = cloned.propagate_to_loaders
- @_generative
- def set_column_strategy(self, attrs, strategy, opts=None, opts_only=False):
- strategy = self._coerce_strat(strategy)
- self.is_class_strategy = False
- for attr in attrs:
- cloned = self._clone_for_bind_strategy(
- attr, strategy, "column", opts_only=opts_only, opts=opts
- )
- cloned.propagate_to_loaders = True
- @_generative
- def set_generic_strategy(self, attrs, strategy):
- strategy = self._coerce_strat(strategy)
- for attr in attrs:
- cloned = self._clone_for_bind_strategy(attr, strategy, None)
- cloned.propagate_to_loaders = True
- @_generative
- def set_class_strategy(self, strategy, opts):
- strategy = self._coerce_strat(strategy)
- cloned = self._clone_for_bind_strategy(None, strategy, None)
- cloned.is_class_strategy = True
- cloned.propagate_to_loaders = True
- cloned.local_opts.update(opts)
- def _clone_for_bind_strategy(
- self, attr, strategy, wildcard_key, opts_only=False, opts=None
- ):
- """Create an anonymous clone of the Load/_UnboundLoad that is suitable
- to be placed in the context / _to_bind collection of this Load
- object. The clone will then lose references to context/_to_bind
- in order to not create reference cycles.
- """
- cloned = self._generate()
- cloned._generate_path(self.path, attr, strategy, wildcard_key)
- cloned.strategy = strategy
- cloned.local_opts = self.local_opts
- if opts:
- cloned.local_opts.update(opts)
- if opts_only:
- cloned.is_opts_only = True
- if strategy or cloned.is_opts_only:
- cloned._set_path_strategy()
- return cloned
- def _set_for_path(self, context, path, replace=True, merge_opts=False):
- if merge_opts or not replace:
- existing = path.get(context, "loader")
- if existing:
- if merge_opts:
- existing.local_opts.update(self.local_opts)
- existing._extra_criteria += self._extra_criteria
- else:
- path.set(context, "loader", self)
- else:
- existing = path.get(context, "loader")
- path.set(context, "loader", self)
- if existing and existing.is_opts_only:
- self.local_opts.update(existing.local_opts)
- existing._extra_criteria += self._extra_criteria
- def _set_path_strategy(self):
- if not self.is_class_strategy and self.path.has_entity:
- effective_path = self.path.parent
- else:
- effective_path = self.path
- if effective_path.is_token:
- for path in effective_path.generate_for_superclasses():
- self._set_for_path(
- self.context,
- path,
- replace=True,
- merge_opts=self.is_opts_only,
- )
- else:
- self._set_for_path(
- self.context,
- effective_path,
- replace=True,
- merge_opts=self.is_opts_only,
- )
- # remove cycles; _set_path_strategy is always invoked on an
- # anonymous clone of the Load / UnboundLoad object since #5056
- self.context = None
- def __getstate__(self):
- d = self.__dict__.copy()
- # can't pickle this right now; warning is raised by strategies
- d["_extra_criteria"] = ()
- if d["context"] is not None:
- d["context"] = PathRegistry.serialize_context_dict(
- d["context"], ("loader",)
- )
- d["path"] = self.path.serialize()
- return d
- def __setstate__(self, state):
- self.__dict__.update(state)
- self.path = PathRegistry.deserialize(self.path)
- if self.context is not None:
- self.context = PathRegistry.deserialize_context_dict(self.context)
- def _chop_path(self, to_chop, path):
- i = -1
- for i, (c_token, p_token) in enumerate(zip(to_chop, path.path)):
- if isinstance(c_token, util.string_types):
- # TODO: this is approximated from the _UnboundLoad
- # version and probably has issues, not fully covered.
- if i == 0 and c_token.endswith(":" + _DEFAULT_TOKEN):
- return to_chop
- elif (
- c_token != "relationship:%s" % (_WILDCARD_TOKEN,)
- and c_token != p_token.key
- ):
- return None
- if c_token is p_token:
- continue
- elif (
- isinstance(c_token, InspectionAttr)
- and c_token.is_mapper
- and p_token.is_mapper
- and c_token.isa(p_token)
- ):
- continue
- else:
- return None
- return to_chop[i + 1 :]
- class _UnboundLoad(Load):
- """Represent a loader option that isn't tied to a root entity.
- The loader option will produce an entity-linked :class:`_orm.Load`
- object when it is passed :meth:`_query.Query.options`.
- This provides compatibility with the traditional system
- of freestanding options, e.g. ``joinedload('x.y.z')``.
- """
- def __init__(self):
- self.path = ()
- self._to_bind = []
- self.local_opts = {}
- self._extra_criteria = ()
- def _gen_cache_key(self, anon_map, bindparams, _unbound_option_seen=None):
- """Inlined gen_cache_key
- Original traversal is::
- _cache_key_traversal = [
- ("path", visitors.ExtendedInternalTraversal.dp_multi_list),
- ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj),
- (
- "_to_bind",
- visitors.ExtendedInternalTraversal.dp_has_cache_key_list,
- ),
- (
- "_extra_criteria",
- visitors.InternalTraversal.dp_clauseelement_list),
- (
- "local_opts",
- visitors.ExtendedInternalTraversal.dp_string_multi_dict,
- ),
- ]
- The inlining is so that the "_to_bind" list can be flattened to not
- repeat the same UnboundLoad options over and over again.
- See #6869
- """
- idself = id(self)
- cls = self.__class__
- if idself in anon_map:
- return (anon_map[idself], cls)
- else:
- id_ = anon_map[idself]
- vis = traversals._cache_key_traversal_visitor
- seen = _unbound_option_seen
- if seen is None:
- seen = set()
- return (
- (id_, cls)
- + vis.visit_multi_list(
- "path", self.path, self, anon_map, bindparams
- )
- + ("strategy", self.strategy)
- + (
- (
- "_to_bind",
- tuple(
- elem._gen_cache_key(
- anon_map, bindparams, _unbound_option_seen=seen
- )
- for elem in self._to_bind
- if elem not in seen and not seen.add(elem)
- ),
- )
- if self._to_bind
- else ()
- )
- + (
- (
- "_extra_criteria",
- tuple(
- elem._gen_cache_key(anon_map, bindparams)
- for elem in self._extra_criteria
- ),
- )
- if self._extra_criteria
- else ()
- )
- + (
- vis.visit_string_multi_dict(
- "local_opts", self.local_opts, self, anon_map, bindparams
- )
- if self.local_opts
- else ()
- )
- )
- _is_chain_link = False
- def _set_path_strategy(self):
- self._to_bind.append(self)
- # remove cycles; _set_path_strategy is always invoked on an
- # anonymous clone of the Load / UnboundLoad object since #5056
- self._to_bind = None
- def _deep_clone(self, applied, process):
- if self in applied:
- return applied[self]
- cloned = self._generate()
- applied[self] = cloned
- cloned.strategy = self.strategy
- assert cloned.propagate_to_loaders == self.propagate_to_loaders
- assert cloned.is_class_strategy == self.is_class_strategy
- assert cloned.is_opts_only == self.is_opts_only
- cloned._to_bind = [
- elem._deep_clone(applied, process) for elem in self._to_bind or ()
- ]
- cloned.local_opts.update(self.local_opts)
- process(cloned)
- return cloned
- def _apply_to_parent(self, parent, applied, bound, to_bind=None):
- if self in applied:
- return applied[self]
- if to_bind is None:
- to_bind = self._to_bind
- cloned = self._generate()
- applied[self] = cloned
- cloned.strategy = self.strategy
- if self.path:
- attr = self.path[-1]
- if isinstance(attr, util.string_types) and attr.endswith(
- _DEFAULT_TOKEN
- ):
- attr = attr.split(":")[0] + ":" + _WILDCARD_TOKEN
- cloned._generate_path(
- parent.path + self.path[0:-1], attr, self.strategy, None
- )
- # these assertions can go away once the "sub options" API is
- # mature
- assert cloned.propagate_to_loaders == self.propagate_to_loaders
- assert cloned.is_class_strategy == self.is_class_strategy
- assert cloned.is_opts_only == self.is_opts_only
- uniq = set()
- cloned._to_bind = parent._to_bind
- cloned._to_bind[:] = [
- elem
- for elem in cloned._to_bind
- if elem not in uniq and not uniq.add(elem)
- ] + [
- elem._apply_to_parent(parent, applied, bound, to_bind)
- for elem in to_bind
- if elem not in uniq and not uniq.add(elem)
- ]
- cloned.local_opts.update(self.local_opts)
- return cloned
- def _generate_path(self, path, attr, for_strategy, wildcard_key):
- if (
- wildcard_key
- and isinstance(attr, util.string_types)
- and attr in (_WILDCARD_TOKEN, _DEFAULT_TOKEN)
- ):
- if attr == _DEFAULT_TOKEN:
- self.propagate_to_loaders = False
- attr = "%s:%s" % (wildcard_key, attr)
- if path and _is_mapped_class(path[-1]) and not self.is_class_strategy:
- path = path[0:-1]
- if attr:
- path = path + (attr,)
- self.path = path
- self._extra_criteria = getattr(attr, "_extra_criteria", ())
- return path
- def __getstate__(self):
- d = self.__dict__.copy()
- # can't pickle this right now; warning is raised by strategies
- d["_extra_criteria"] = ()
- d["path"] = self._serialize_path(self.path, filter_aliased_class=True)
- return d
- def __setstate__(self, state):
- ret = []
- for key in state["path"]:
- if isinstance(key, tuple):
- if len(key) == 2:
- # support legacy
- cls, propkey = key
- of_type = None
- else:
- cls, propkey, of_type = key
- prop = getattr(cls, propkey)
- if of_type:
- prop = prop.of_type(of_type)
- ret.append(prop)
- else:
- ret.append(key)
- state["path"] = tuple(ret)
- self.__dict__ = state
- def _process(self, compile_state, mapper_entities, raiseerr):
- dedupes = compile_state.attributes["_unbound_load_dedupes"]
- is_refresh = compile_state.compile_options._for_refresh_state
- for val in self._to_bind:
- if val not in dedupes:
- dedupes.add(val)
- if is_refresh and not val.propagate_to_loaders:
- continue
- val._bind_loader(
- [ent.entity_zero for ent in mapper_entities],
- compile_state.current_path,
- compile_state.attributes,
- raiseerr,
- )
- @classmethod
- def _from_keys(cls, meth, keys, chained, kw):
- opt = _UnboundLoad()
- def _split_key(key):
- if isinstance(key, util.string_types):
- # coerce fooload('*') into "default loader strategy"
- if key == _WILDCARD_TOKEN:
- return (_DEFAULT_TOKEN,)
- # coerce fooload(".*") into "wildcard on default entity"
- elif key.startswith("." + _WILDCARD_TOKEN):
- util.warn_deprecated(
- "The undocumented `.{WILDCARD}` format is deprecated "
- "and will be removed in a future version as it is "
- "believed to be unused. "
- "If you have been using this functionality, please "
- "comment on Issue #4390 on the SQLAlchemy project "
- "tracker.",
- version="1.4",
- )
- key = key[1:]
- return key.split(".")
- else:
- return (key,)
- all_tokens = [token for key in keys for token in _split_key(key)]
- for token in all_tokens[0:-1]:
- # set _is_chain_link first so that clones of the
- # object also inherit this flag
- opt._is_chain_link = True
- if chained:
- opt = meth(opt, token, **kw)
- else:
- opt = opt.defaultload(token)
- opt = meth(opt, all_tokens[-1], **kw)
- opt._is_chain_link = False
- return opt
- def _chop_path(self, to_chop, path):
- i = -1
- for i, (c_token, (p_entity, p_prop)) in enumerate(
- zip(to_chop, path.pairs())
- ):
- if isinstance(c_token, util.string_types):
- if i == 0 and c_token.endswith(":" + _DEFAULT_TOKEN):
- return to_chop
- elif (
- c_token != "relationship:%s" % (_WILDCARD_TOKEN,)
- and c_token != p_prop.key
- ):
- return None
- elif isinstance(c_token, PropComparator):
- if c_token.property is not p_prop or (
- c_token._parententity is not p_entity
- and (
- not c_token._parententity.is_mapper
- or not c_token._parententity.isa(p_entity)
- )
- ):
- return None
- else:
- i += 1
- return to_chop[i:]
- def _serialize_path(self, path, filter_aliased_class=False):
- ret = []
- for token in path:
- if isinstance(token, QueryableAttribute):
- if (
- filter_aliased_class
- and token._of_type
- and inspect(token._of_type).is_aliased_class
- ):
- ret.append((token._parentmapper.class_, token.key, None))
- else:
- ret.append(
- (
- token._parentmapper.class_,
- token.key,
- token._of_type.entity if token._of_type else None,
- )
- )
- elif isinstance(token, PropComparator):
- ret.append((token._parentmapper.class_, token.key, None))
- else:
- ret.append(token)
- return ret
- def _bind_loader(self, entities, current_path, context, raiseerr):
- """Convert from an _UnboundLoad() object into a Load() object.
- The _UnboundLoad() uses an informal "path" and does not necessarily
- refer to a lead entity as it may use string tokens. The Load()
- OTOH refers to a complete path. This method reconciles from a
- given Query into a Load.
- Example::
- query = session.query(User).options(
- joinedload("orders").joinedload("items"))
- The above options will be an _UnboundLoad object along the lines
- of (note this is not the exact API of _UnboundLoad)::
- _UnboundLoad(
- _to_bind=[
- _UnboundLoad(["orders"], {"lazy": "joined"}),
- _UnboundLoad(["orders", "items"], {"lazy": "joined"}),
- ]
- )
- After this method, we get something more like this (again this is
- not exact API)::
- Load(
- User,
- (User, User.orders.property))
- Load(
- User,
- (User, User.orders.property, Order, Order.items.property))
- """
- start_path = self.path
- if self.is_class_strategy and current_path:
- start_path += (entities[0],)
- # _current_path implies we're in a
- # secondary load with an existing path
- if current_path:
- start_path = self._chop_path(start_path, current_path)
- if not start_path:
- return None
- # look at the first token and try to locate within the Query
- # what entity we are referring towards.
- token = start_path[0]
- if isinstance(token, util.string_types):
- entity = self._find_entity_basestring(entities, token, raiseerr)
- elif isinstance(token, PropComparator):
- prop = token.property
- entity = self._find_entity_prop_comparator(
- entities, prop, token._parententity, raiseerr
- )
- elif self.is_class_strategy and _is_mapped_class(token):
- entity = inspect(token)
- if entity not in entities:
- entity = None
- else:
- raise sa_exc.ArgumentError(
- "mapper option expects " "string key or list of attributes"
- )
- if not entity:
- return
- path_element = entity
- # transfer our entity-less state into a Load() object
- # with a real entity path. Start with the lead entity
- # we just located, then go through the rest of our path
- # tokens and populate into the Load().
- loader = Load(path_element)
- if context is None:
- context = loader.context
- loader.strategy = self.strategy
- loader.is_opts_only = self.is_opts_only
- loader.is_class_strategy = self.is_class_strategy
- loader._extra_criteria = self._extra_criteria
- path = loader.path
- if not loader.is_class_strategy:
- for idx, token in enumerate(start_path):
- if not loader._generate_path(
- loader.path,
- token,
- self.strategy if idx == len(start_path) - 1 else None,
- None,
- raiseerr,
- polymorphic_entity_context=context,
- ):
- return
- loader.local_opts.update(self.local_opts)
- if not loader.is_class_strategy and loader.path.has_entity:
- effective_path = loader.path.parent
- else:
- effective_path = loader.path
- # prioritize "first class" options over those
- # that were "links in the chain", e.g. "x" and "y" in
- # someload("x.y.z") versus someload("x") / someload("x.y")
- if effective_path.is_token:
- for path in effective_path.generate_for_superclasses():
- loader._set_for_path(
- context,
- path,
- replace=not self._is_chain_link,
- merge_opts=self.is_opts_only,
- )
- else:
- loader._set_for_path(
- context,
- effective_path,
- replace=not self._is_chain_link,
- merge_opts=self.is_opts_only,
- )
- return loader
- def _find_entity_prop_comparator(self, entities, prop, mapper, raiseerr):
- if _is_aliased_class(mapper):
- searchfor = mapper
- else:
- searchfor = _class_to_mapper(mapper)
- for ent in entities:
- if orm_util._entity_corresponds_to(ent, searchfor):
- return ent
- else:
- if raiseerr:
- if not list(entities):
- raise sa_exc.ArgumentError(
- "Query has only expression-based entities, "
- 'which do not apply to %s "%s"'
- % (util.clsname_as_plain_name(type(prop)), prop)
- )
- else:
- raise sa_exc.ArgumentError(
- 'Mapped attribute "%s" does not apply to any of the '
- "root entities in this query, e.g. %s. Please "
- "specify the full path "
- "from one of the root entities to the target "
- "attribute. "
- % (prop, ", ".join(str(x) for x in entities))
- )
- else:
- return None
- def _find_entity_basestring(self, entities, token, raiseerr):
- if token.endswith(":" + _WILDCARD_TOKEN):
- if len(list(entities)) != 1:
- if raiseerr:
- raise sa_exc.ArgumentError(
- "Can't apply wildcard ('*') or load_only() "
- "loader option to multiple entities %s. Specify "
- "loader options for each entity individually, such "
- "as %s."
- % (
- ", ".join(str(ent) for ent in entities),
- ", ".join(
- "Load(%s).some_option('*')" % ent
- for ent in entities
- ),
- )
- )
- elif token.endswith(_DEFAULT_TOKEN):
- raiseerr = False
- for ent in entities:
- # return only the first _MapperEntity when searching
- # based on string prop name. Ideally object
- # attributes are used to specify more exactly.
- return ent
- else:
- if raiseerr:
- raise sa_exc.ArgumentError(
- "Query has only expression-based entities - "
- 'can\'t find property named "%s".' % (token,)
- )
- else:
- return None
- class loader_option(object):
- def __init__(self):
- pass
- def __call__(self, fn):
- self.name = name = fn.__name__
- self.fn = fn
- if hasattr(Load, name):
- raise TypeError("Load class already has a %s method." % (name))
- setattr(Load, name, fn)
- return self
- def _add_unbound_fn(self, fn):
- self._unbound_fn = fn
- fn_doc = self.fn.__doc__
- self.fn.__doc__ = """Produce a new :class:`_orm.Load` object with the
- :func:`_orm.%(name)s` option applied.
- See :func:`_orm.%(name)s` for usage examples.
- """ % {
- "name": self.name
- }
- fn.__doc__ = fn_doc
- return self
- def _add_unbound_all_fn(self, fn):
- fn.__doc__ = """Produce a standalone "all" option for
- :func:`_orm.%(name)s`.
- .. deprecated:: 0.9
- The :func:`_orm.%(name)s_all` function is deprecated, and will be removed
- in a future release. Please use method chaining with
- :func:`_orm.%(name)s` instead, as in::
- session.query(MyClass).options(
- %(name)s("someattribute").%(name)s("anotherattribute")
- )
- """ % {
- "name": self.name
- }
- fn = util.deprecated(
- # This is used by `baked_lazyload_all` was only deprecated in
- # version 1.2 so this must stick around until that is removed
- "0.9",
- "The :func:`.%(name)s_all` function is deprecated, and will be "
- "removed in a future release. Please use method chaining with "
- ":func:`.%(name)s` instead" % {"name": self.name},
- add_deprecation_to_docstring=False,
- )(fn)
- self._unbound_all_fn = fn
- return self
- @loader_option()
- def contains_eager(loadopt, attr, alias=None):
- r"""Indicate that the given attribute should be eagerly loaded from
- columns stated manually in the query.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- The option is used in conjunction with an explicit join that loads
- the desired rows, i.e.::
- sess.query(Order).\
- join(Order.user).\
- options(contains_eager(Order.user))
- The above query would join from the ``Order`` entity to its related
- ``User`` entity, and the returned ``Order`` objects would have the
- ``Order.user`` attribute pre-populated.
- It may also be used for customizing the entries in an eagerly loaded
- collection; queries will normally want to use the
- :meth:`_query.Query.populate_existing` method assuming the primary
- collection of parent objects may already have been loaded::
- sess.query(User).\
- join(User.addresses).\
- filter(Address.email_address.like('%@aol.com')).\
- options(contains_eager(User.addresses)).\
- populate_existing()
- See the section :ref:`contains_eager` for complete usage details.
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`contains_eager`
- """
- if alias is not None:
- if not isinstance(alias, str):
- info = inspect(alias)
- alias = info.selectable
- else:
- util.warn_deprecated(
- "Passing a string name for the 'alias' argument to "
- "'contains_eager()` is deprecated, and will not work in a "
- "future release. Please use a sqlalchemy.alias() or "
- "sqlalchemy.orm.aliased() construct.",
- version="1.4",
- )
- elif getattr(attr, "_of_type", None):
- ot = inspect(attr._of_type)
- alias = ot.selectable
- cloned = loadopt.set_relationship_strategy(
- attr, {"lazy": "joined"}, propagate_to_loaders=False
- )
- cloned.local_opts["eager_from_alias"] = alias
- return cloned
- @contains_eager._add_unbound_fn
- def contains_eager(*keys, **kw):
- return _UnboundLoad()._from_keys(
- _UnboundLoad.contains_eager, keys, True, kw
- )
- @loader_option()
- def load_only(loadopt, *attrs):
- """Indicate that for a particular entity, only the given list
- of column-based attribute names should be loaded; all others will be
- deferred.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- Example - given a class ``User``, load only the ``name`` and ``fullname``
- attributes::
- session.query(User).options(load_only(User.name, User.fullname))
- Example - given a relationship ``User.addresses -> Address``, specify
- subquery loading for the ``User.addresses`` collection, but on each
- ``Address`` object load only the ``email_address`` attribute::
- session.query(User).options(
- subqueryload(User.addresses).load_only(Address.email_address)
- )
- For a :class:`_query.Query` that has multiple entities,
- the lead entity can be
- specifically referred to using the :class:`_orm.Load` constructor::
- session.query(User, Address).join(User.addresses).options(
- Load(User).load_only(User.name, User.fullname),
- Load(Address).load_only(Address.email_address)
- )
- .. note:: This method will still load a :class:`_schema.Column` even
- if the column property is defined with ``deferred=True``
- for the :func:`.column_property` function.
- .. versionadded:: 0.9.0
- """
- cloned = loadopt.set_column_strategy(
- attrs, {"deferred": False, "instrument": True}
- )
- cloned.set_column_strategy(
- "*", {"deferred": True, "instrument": True}, {"undefer_pks": True}
- )
- return cloned
- @load_only._add_unbound_fn
- def load_only(*attrs):
- return _UnboundLoad().load_only(*attrs)
- @loader_option()
- def joinedload(loadopt, attr, innerjoin=None):
- """Indicate that the given attribute should be loaded using joined
- eager loading.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- examples::
- # joined-load the "orders" collection on "User"
- query(User).options(joinedload(User.orders))
- # joined-load Order.items and then Item.keywords
- query(Order).options(
- joinedload(Order.items).joinedload(Item.keywords))
- # lazily load Order.items, but when Items are loaded,
- # joined-load the keywords collection
- query(Order).options(
- lazyload(Order.items).joinedload(Item.keywords))
- :param innerjoin: if ``True``, indicates that the joined eager load should
- use an inner join instead of the default of left outer join::
- query(Order).options(joinedload(Order.user, innerjoin=True))
- In order to chain multiple eager joins together where some may be
- OUTER and others INNER, right-nested joins are used to link them::
- query(A).options(
- joinedload(A.bs, innerjoin=False).
- joinedload(B.cs, innerjoin=True)
- )
- The above query, linking A.bs via "outer" join and B.cs via "inner" join
- would render the joins as "a LEFT OUTER JOIN (b JOIN c)". When using
- older versions of SQLite (< 3.7.16), this form of JOIN is translated to
- use full subqueries as this syntax is otherwise not directly supported.
- The ``innerjoin`` flag can also be stated with the term ``"unnested"``.
- This indicates that an INNER JOIN should be used, *unless* the join
- is linked to a LEFT OUTER JOIN to the left, in which case it
- will render as LEFT OUTER JOIN. For example, supposing ``A.bs``
- is an outerjoin::
- query(A).options(
- joinedload(A.bs).
- joinedload(B.cs, innerjoin="unnested")
- )
- The above join will render as "a LEFT OUTER JOIN b LEFT OUTER JOIN c",
- rather than as "a LEFT OUTER JOIN (b JOIN c)".
- .. note:: The "unnested" flag does **not** affect the JOIN rendered
- from a many-to-many association table, e.g. a table configured
- as :paramref:`_orm.relationship.secondary`, to the target table; for
- correctness of results, these joins are always INNER and are
- therefore right-nested if linked to an OUTER join.
- .. versionchanged:: 1.0.0 ``innerjoin=True`` now implies
- ``innerjoin="nested"``, whereas in 0.9 it implied
- ``innerjoin="unnested"``. In order to achieve the pre-1.0 "unnested"
- inner join behavior, use the value ``innerjoin="unnested"``.
- See :ref:`migration_3008`.
- .. note::
- The joins produced by :func:`_orm.joinedload` are **anonymously
- aliased**. The criteria by which the join proceeds cannot be
- modified, nor can the :class:`_query.Query`
- refer to these joins in any way,
- including ordering. See :ref:`zen_of_eager_loading` for further
- detail.
- To produce a specific SQL JOIN which is explicitly available, use
- :meth:`_query.Query.join`.
- To combine explicit JOINs with eager loading
- of collections, use :func:`_orm.contains_eager`; see
- :ref:`contains_eager`.
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`joined_eager_loading`
- """
- loader = loadopt.set_relationship_strategy(attr, {"lazy": "joined"})
- if innerjoin is not None:
- loader.local_opts["innerjoin"] = innerjoin
- return loader
- @joinedload._add_unbound_fn
- def joinedload(*keys, **kw):
- return _UnboundLoad._from_keys(_UnboundLoad.joinedload, keys, False, kw)
- @loader_option()
- def subqueryload(loadopt, attr):
- """Indicate that the given attribute should be loaded using
- subquery eager loading.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- examples::
- # subquery-load the "orders" collection on "User"
- query(User).options(subqueryload(User.orders))
- # subquery-load Order.items and then Item.keywords
- query(Order).options(
- subqueryload(Order.items).subqueryload(Item.keywords))
- # lazily load Order.items, but when Items are loaded,
- # subquery-load the keywords collection
- query(Order).options(
- lazyload(Order.items).subqueryload(Item.keywords))
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`subquery_eager_loading`
- """
- return loadopt.set_relationship_strategy(attr, {"lazy": "subquery"})
- @subqueryload._add_unbound_fn
- def subqueryload(*keys):
- return _UnboundLoad._from_keys(_UnboundLoad.subqueryload, keys, False, {})
- @loader_option()
- def selectinload(loadopt, attr):
- """Indicate that the given attribute should be loaded using
- SELECT IN eager loading.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- examples::
- # selectin-load the "orders" collection on "User"
- query(User).options(selectinload(User.orders))
- # selectin-load Order.items and then Item.keywords
- query(Order).options(
- selectinload(Order.items).selectinload(Item.keywords))
- # lazily load Order.items, but when Items are loaded,
- # selectin-load the keywords collection
- query(Order).options(
- lazyload(Order.items).selectinload(Item.keywords))
- .. versionadded:: 1.2
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`selectin_eager_loading`
- """
- return loadopt.set_relationship_strategy(attr, {"lazy": "selectin"})
- @selectinload._add_unbound_fn
- def selectinload(*keys):
- return _UnboundLoad._from_keys(_UnboundLoad.selectinload, keys, False, {})
- @loader_option()
- def lazyload(loadopt, attr):
- """Indicate that the given attribute should be loaded using "lazy"
- loading.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`lazy_loading`
- """
- return loadopt.set_relationship_strategy(attr, {"lazy": "select"})
- @lazyload._add_unbound_fn
- def lazyload(*keys):
- return _UnboundLoad._from_keys(_UnboundLoad.lazyload, keys, False, {})
- @loader_option()
- def immediateload(loadopt, attr):
- """Indicate that the given attribute should be loaded using
- an immediate load with a per-attribute SELECT statement.
- The load is achieved using the "lazyloader" strategy and does not
- fire off any additional eager loaders.
- The :func:`.immediateload` option is superseded in general
- by the :func:`.selectinload` option, which performs the same task
- more efficiently by emitting a SELECT for all loaded objects.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`selectin_eager_loading`
- """
- loader = loadopt.set_relationship_strategy(attr, {"lazy": "immediate"})
- return loader
- @immediateload._add_unbound_fn
- def immediateload(*keys):
- return _UnboundLoad._from_keys(_UnboundLoad.immediateload, keys, False, {})
- @loader_option()
- def noload(loadopt, attr):
- """Indicate that the given relationship attribute should remain unloaded.
- The relationship attribute will return ``None`` when accessed without
- producing any loading effect.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- :func:`_orm.noload` applies to :func:`_orm.relationship` attributes; for
- column-based attributes, see :func:`_orm.defer`.
- .. note:: Setting this loading strategy as the default strategy
- for a relationship using the :paramref:`.orm.relationship.lazy`
- parameter may cause issues with flushes, such if a delete operation
- needs to load related objects and instead ``None`` was returned.
- .. seealso::
- :ref:`loading_toplevel`
- """
- return loadopt.set_relationship_strategy(attr, {"lazy": "noload"})
- @noload._add_unbound_fn
- def noload(*keys):
- return _UnboundLoad._from_keys(_UnboundLoad.noload, keys, False, {})
- @loader_option()
- def raiseload(loadopt, attr, sql_only=False):
- """Indicate that the given attribute should raise an error if accessed.
- A relationship attribute configured with :func:`_orm.raiseload` will
- raise an :exc:`~sqlalchemy.exc.InvalidRequestError` upon access. The
- typical way this is useful is when an application is attempting to ensure
- that all relationship attributes that are accessed in a particular context
- would have been already loaded via eager loading. Instead of having
- to read through SQL logs to ensure lazy loads aren't occurring, this
- strategy will cause them to raise immediately.
- :func:`_orm.raiseload` applies to :func:`_orm.relationship`
- attributes only.
- In order to apply raise-on-SQL behavior to a column-based attribute,
- use the :paramref:`.orm.defer.raiseload` parameter on the :func:`.defer`
- loader option.
- :param sql_only: if True, raise only if the lazy load would emit SQL, but
- not if it is only checking the identity map, or determining that the
- related value should just be None due to missing keys. When False, the
- strategy will raise for all varieties of relationship loading.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`prevent_lazy_with_raiseload`
- :ref:`deferred_raiseload`
- """
- return loadopt.set_relationship_strategy(
- attr, {"lazy": "raise_on_sql" if sql_only else "raise"}
- )
- @raiseload._add_unbound_fn
- def raiseload(*keys, **kw):
- return _UnboundLoad._from_keys(_UnboundLoad.raiseload, keys, False, kw)
- @loader_option()
- def defaultload(loadopt, attr):
- """Indicate an attribute should load using its default loader style.
- This method is used to link to other loader options further into
- a chain of attributes without altering the loader style of the links
- along the chain. For example, to set joined eager loading for an
- element of an element::
- session.query(MyClass).options(
- defaultload(MyClass.someattribute).
- joinedload(MyOtherClass.someotherattribute)
- )
- :func:`.defaultload` is also useful for setting column-level options
- on a related class, namely that of :func:`.defer` and :func:`.undefer`::
- session.query(MyClass).options(
- defaultload(MyClass.someattribute).
- defer("some_column").
- undefer("some_other_column")
- )
- .. seealso::
- :meth:`_orm.Load.options` - allows for complex hierarchical
- loader option structures with less verbosity than with individual
- :func:`.defaultload` directives.
- :ref:`relationship_loader_options`
- :ref:`deferred_loading_w_multiple`
- """
- return loadopt.set_relationship_strategy(attr, None)
- @defaultload._add_unbound_fn
- def defaultload(*keys):
- return _UnboundLoad._from_keys(_UnboundLoad.defaultload, keys, False, {})
- @loader_option()
- def defer(loadopt, key, raiseload=False):
- r"""Indicate that the given column-oriented attribute should be deferred,
- e.g. not loaded until accessed.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- e.g.::
- from sqlalchemy.orm import defer
- session.query(MyClass).options(
- defer("attribute_one"),
- defer("attribute_two"))
- session.query(MyClass).options(
- defer(MyClass.attribute_one),
- defer(MyClass.attribute_two))
- To specify a deferred load of an attribute on a related class,
- the path can be specified one token at a time, specifying the loading
- style for each link along the chain. To leave the loading style
- for a link unchanged, use :func:`_orm.defaultload`::
- session.query(MyClass).options(defaultload("someattr").defer("some_column"))
- A :class:`_orm.Load` object that is present on a certain path can have
- :meth:`_orm.Load.defer` called multiple times,
- each will operate on the same
- parent entity::
- session.query(MyClass).options(
- defaultload("someattr").
- defer("some_column").
- defer("some_other_column").
- defer("another_column")
- )
- :param key: Attribute to be deferred.
- :param raiseload: raise :class:`.InvalidRequestError` if the column
- value is to be loaded from emitting SQL. Used to prevent unwanted
- SQL from being emitted.
- .. versionadded:: 1.4
- .. seealso::
- :ref:`deferred_raiseload`
- :param \*addl_attrs: This option supports the old 0.8 style
- of specifying a path as a series of attributes, which is now superseded
- by the method-chained style.
- .. deprecated:: 0.9 The \*addl_attrs on :func:`_orm.defer` is
- deprecated and will be removed in a future release. Please
- use method chaining in conjunction with defaultload() to
- indicate a path.
- .. seealso::
- :ref:`deferred`
- :func:`_orm.undefer`
- """
- strategy = {"deferred": True, "instrument": True}
- if raiseload:
- strategy["raiseload"] = True
- return loadopt.set_column_strategy((key,), strategy)
- @defer._add_unbound_fn
- def defer(key, *addl_attrs, **kw):
- if addl_attrs:
- util.warn_deprecated(
- "The *addl_attrs on orm.defer is deprecated. Please use "
- "method chaining in conjunction with defaultload() to "
- "indicate a path.",
- version="1.3",
- )
- return _UnboundLoad._from_keys(
- _UnboundLoad.defer, (key,) + addl_attrs, False, kw
- )
- @loader_option()
- def undefer(loadopt, key):
- r"""Indicate that the given column-oriented attribute should be undeferred,
- e.g. specified within the SELECT statement of the entity as a whole.
- The column being undeferred is typically set up on the mapping as a
- :func:`.deferred` attribute.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- Examples::
- # undefer two columns
- session.query(MyClass).options(undefer("col1"), undefer("col2"))
- # undefer all columns specific to a single class using Load + *
- session.query(MyClass, MyOtherClass).options(
- Load(MyClass).undefer("*"))
- # undefer a column on a related object
- session.query(MyClass).options(
- defaultload(MyClass.items).undefer('text'))
- :param key: Attribute to be undeferred.
- :param \*addl_attrs: This option supports the old 0.8 style
- of specifying a path as a series of attributes, which is now superseded
- by the method-chained style.
- .. deprecated:: 0.9 The \*addl_attrs on :func:`_orm.undefer` is
- deprecated and will be removed in a future release. Please
- use method chaining in conjunction with defaultload() to
- indicate a path.
- .. seealso::
- :ref:`deferred`
- :func:`_orm.defer`
- :func:`_orm.undefer_group`
- """
- return loadopt.set_column_strategy(
- (key,), {"deferred": False, "instrument": True}
- )
- @undefer._add_unbound_fn
- def undefer(key, *addl_attrs):
- if addl_attrs:
- util.warn_deprecated(
- "The *addl_attrs on orm.undefer is deprecated. Please use "
- "method chaining in conjunction with defaultload() to "
- "indicate a path.",
- version="1.3",
- )
- return _UnboundLoad._from_keys(
- _UnboundLoad.undefer, (key,) + addl_attrs, False, {}
- )
- @loader_option()
- def undefer_group(loadopt, name):
- """Indicate that columns within the given deferred group name should be
- undeferred.
- The columns being undeferred are set up on the mapping as
- :func:`.deferred` attributes and include a "group" name.
- E.g::
- session.query(MyClass).options(undefer_group("large_attrs"))
- To undefer a group of attributes on a related entity, the path can be
- spelled out using relationship loader options, such as
- :func:`_orm.defaultload`::
- session.query(MyClass).options(
- defaultload("someattr").undefer_group("large_attrs"))
- .. versionchanged:: 0.9.0 :func:`_orm.undefer_group` is now specific to a
- particular entity load path.
- .. seealso::
- :ref:`deferred`
- :func:`_orm.defer`
- :func:`_orm.undefer`
- """
- return loadopt.set_column_strategy(
- "*", None, {"undefer_group_%s" % name: True}, opts_only=True
- )
- @undefer_group._add_unbound_fn
- def undefer_group(name):
- return _UnboundLoad().undefer_group(name)
- @loader_option()
- def with_expression(loadopt, key, expression):
- r"""Apply an ad-hoc SQL expression to a "deferred expression" attribute.
- This option is used in conjunction with the :func:`_orm.query_expression`
- mapper-level construct that indicates an attribute which should be the
- target of an ad-hoc SQL expression.
- E.g.::
- sess.query(SomeClass).options(
- with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y)
- )
- .. versionadded:: 1.2
- :param key: Attribute to be populated.
- :param expr: SQL expression to be applied to the attribute.
- .. versionchanged:: 1.4 Loader options such as
- :func:`_orm.with_expression`
- take effect only at the **outermost** query used, and should not be used
- within subqueries or inner elements of a UNION. See the change notes at
- :ref:`change_8879` for background on how to correctly add arbitrary
- columns to subqueries.
- .. note:: the target attribute is populated only if the target object
- is **not currently loaded** in the current :class:`_orm.Session`
- unless the :meth:`_query.Query.populate_existing` method is used.
- Please refer to :ref:`mapper_querytime_expression` for complete
- usage details.
- .. seealso::
- :ref:`mapper_querytime_expression`
- """
- expression = coercions.expect(
- roles.LabeledColumnExprRole, _orm_full_deannotate(expression)
- )
- return loadopt.set_column_strategy(
- (key,), {"query_expression": True}, opts={"expression": expression}
- )
- @with_expression._add_unbound_fn
- def with_expression(key, expression):
- return _UnboundLoad._from_keys(
- _UnboundLoad.with_expression, (key,), False, {"expression": expression}
- )
- @loader_option()
- def selectin_polymorphic(loadopt, classes):
- """Indicate an eager load should take place for all attributes
- specific to a subclass.
- This uses an additional SELECT with IN against all matched primary
- key values, and is the per-query analogue to the ``"selectin"``
- setting on the :paramref:`.mapper.polymorphic_load` parameter.
- .. versionadded:: 1.2
- .. seealso::
- :ref:`polymorphic_selectin`
- """
- loadopt.set_class_strategy(
- {"selectinload_polymorphic": True},
- opts={
- "entities": tuple(
- sorted((inspect(cls) for cls in classes), key=id)
- )
- },
- )
- return loadopt
- @selectin_polymorphic._add_unbound_fn
- def selectin_polymorphic(base_cls, classes):
- ul = _UnboundLoad()
- ul.is_class_strategy = True
- ul.path = (inspect(base_cls),)
- ul.selectin_polymorphic(classes)
- return ul
|