SickGear/lib/sqlalchemy/orm/strategy_options.py
2014-05-04 21:05:43 -07:00

948 lines
32 KiB
Python

# orm/strategy_options.py
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""
"""
from .interfaces import MapperOption, PropComparator
from .. import util
from ..sql.base import _generative, Generative
from .. import exc as sa_exc, inspect
from .base import _is_aliased_class, _class_to_mapper
from . import util as orm_util
from .path_registry import PathRegistry, TokenRegistry, \
_WILDCARD_TOKEN, _DEFAULT_TOKEN
class Load(Generative, MapperOption):
"""Represents loader options which modify the state of a
:class:`.Query` in order to affect how various mapped attributes are loaded.
.. versionadded:: 0.9.0 The :meth:`.Load` system is a new foundation for
the existing system of loader options, including options such as
:func:`.orm.joinedload`, :func:`.orm.defer`, and others. In particular,
it introduces a new method-chained system that replaces the need for
dot-separated paths as well as "_all()" options such as :func:`.orm.joinedload_all`.
A :class:`.Load` object can be used directly or indirectly. To use one
directly, instantiate given the parent class. This style of usage is
useful when dealing with a :class:`.Query` that has multiple entities,
or when producing a loader option that can be applied generically to
any style of query::
myopt = Load(MyClass).joinedload("widgets")
The above ``myopt`` can now be used with :meth:`.Query.options`::
session.query(MyClass).options(myopt)
The :class:`.Load` construct is invoked indirectly whenever one makes use
of the various loader options that are present in ``sqlalchemy.orm``, including
options such as :func:`.orm.joinedload`, :func:`.orm.defer`, :func:`.orm.subqueryload`,
and all the rest. These constructs produce an "anonymous" form of the
:class:`.Load` object which tracks attributes and options, but is not linked
to a parent class until it is associated with a parent :class:`.Query`::
# produce "unbound" Load object
myopt = joinedload("widgets")
# when applied using options(), the option is "bound" to the
# class observed in the given query, e.g. MyClass
session.query(MyClass).options(myopt)
Whether the direct or indirect style is used, the :class:`.Load` object
returned now represents a specific "path" along the entities of a :class:`.Query`.
This path can be traversed using a standard method-chaining approach.
Supposing a class hierarchy such as ``User``, ``User.addresses -> Address``,
``User.orders -> Order`` and ``Order.items -> Item``, we can specify a variety
of loader options along each element in the "path"::
session.query(User).options(
joinedload("addresses"),
subqueryload("orders").joinedload("items")
)
Where above, the ``addresses`` collection will be joined-loaded, the
``orders`` collection will be subquery-loaded, and within that subquery load
the ``items`` collection will be joined-loaded.
"""
def __init__(self, entity):
insp = inspect(entity)
self.path = insp._path_registry
self.context = {}
self.local_opts = {}
def _generate(self):
cloned = super(Load, self)._generate()
cloned.local_opts = {}
return cloned
strategy = None
propagate_to_loaders = False
def process_query(self, query):
self._process(query, True)
def process_query_conditionally(self, query):
self._process(query, False)
def _process(self, query, raiseerr):
current_path = query._current_path
if current_path:
for (token, start_path), loader in self.context.items():
chopped_start_path = self._chop_path(start_path, current_path)
if chopped_start_path is not None:
query._attributes[(token, chopped_start_path)] = loader
else:
query._attributes.update(self.context)
def _generate_path(self, path, attr, wildcard_key, raiseerr=True):
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(
"Attribute '%s' of entity '%s' does not "
"refer to a mapped entity" %
(path.prop.key, path.parent.entity)
)
if isinstance(attr, util.string_types):
default_token = attr.endswith(_DEFAULT_TOKEN)
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)
return path.token(attr)
try:
# use getattr on the class to work around
# synonyms, hybrids, etc.
attr = getattr(path.entity.class_, attr)
except AttributeError:
if raiseerr:
raise sa_exc.ArgumentError(
"Can't find property named '%s' on the "
"mapped entity %s in this Query. " % (
attr, path.entity)
)
else:
return None
else:
attr = attr.property
path = path[attr]
else:
prop = attr.property
if not prop.parent.common_parent(path.mapper):
if raiseerr:
raise sa_exc.ArgumentError("Attribute '%s' does not "
"link from element '%s'" % (attr, path.entity))
else:
return None
if getattr(attr, '_of_type', None):
ac = attr._of_type
ext_info = inspect(ac)
path_element = ext_info.mapper
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)
path.entity_path[prop].set(self.context,
"path_with_polymorphic", inspect(ac))
path = path[prop][path_element]
else:
path = path[prop]
if path.has_entity:
path = path.entity_path
return path
def _coerce_strat(self, strategy):
if strategy is not None:
strategy = tuple(sorted(strategy.items()))
return strategy
@_generative
def set_relationship_strategy(self, attr, strategy, propagate_to_loaders=True):
strategy = self._coerce_strat(strategy)
self.propagate_to_loaders = propagate_to_loaders
# if the path is a wildcard, this will set propagate_to_loaders=False
self.path = self._generate_path(self.path, attr, "relationship")
self.strategy = strategy
if strategy is not None:
self._set_path_strategy()
@_generative
def set_column_strategy(self, attrs, strategy, opts=None):
strategy = self._coerce_strat(strategy)
for attr in attrs:
path = self._generate_path(self.path, attr, "column")
cloned = self._generate()
cloned.strategy = strategy
cloned.path = path
cloned.propagate_to_loaders = True
if opts:
cloned.local_opts.update(opts)
cloned._set_path_strategy()
def _set_path_strategy(self):
if self.path.has_entity:
self.path.parent.set(self.context, "loader", self)
else:
self.path.set(self.context, "loader", self)
def __getstate__(self):
d = self.__dict__.copy()
d["path"] = self.path.serialize()
return d
def __setstate__(self, state):
self.__dict__.update(state)
self.path = PathRegistry.deserialize(self.path)
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
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:`.Load`
object when it is passed :meth:`.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 = set()
self.local_opts = {}
_is_chain_link = False
def _set_path_strategy(self):
self._to_bind.add(self)
def _generate_path(self, path, attr, 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)
return path + (attr, )
def __getstate__(self):
d = self.__dict__.copy()
d['path'] = ret = []
for token in util.to_list(self.path):
if isinstance(token, PropComparator):
ret.append((token._parentmapper.class_, token.key))
else:
ret.append(token)
return d
def __setstate__(self, state):
ret = []
for key in state['path']:
if isinstance(key, tuple):
cls, propkey = key
ret.append(getattr(cls, propkey))
else:
ret.append(key)
state['path'] = tuple(ret)
self.__dict__ = state
def _process(self, query, raiseerr):
for val in self._to_bind:
val._bind_loader(query, query._attributes, raiseerr)
@classmethod
def _from_keys(self, 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):
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]:
if chained:
opt = meth(opt, token, **kw)
else:
opt = opt.defaultload(token)
opt._is_chain_link = True
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_mapper, 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:
return None
else:
i += 1
return to_chop[i:]
def _bind_loader(self, query, context, raiseerr):
start_path = self.path
# _current_path implies we're in a
# secondary load with an existing path
current_path = query._current_path
if current_path:
start_path = self._chop_path(start_path, current_path)
if not start_path:
return None
token = start_path[0]
if isinstance(token, util.string_types):
entity = self._find_entity_basestring(query, token, raiseerr)
elif isinstance(token, PropComparator):
prop = token.property
entity = self._find_entity_prop_comparator(
query,
prop.key,
token._parententity,
raiseerr)
else:
raise sa_exc.ArgumentError(
"mapper option expects "
"string key or list of attributes")
if not entity:
return
path_element = entity.entity_zero
# transfer our entity-less state into a Load() object
# with a real entity path.
loader = Load(path_element)
loader.context = context
loader.strategy = self.strategy
path = loader.path
for token in start_path:
loader.path = path = loader._generate_path(
loader.path, token, None, raiseerr)
if path is None:
return
loader.local_opts.update(self.local_opts)
if 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 self._is_chain_link:
effective_path.setdefault(context, "loader", loader)
else:
effective_path.set(context, "loader", loader)
def _find_entity_prop_comparator(self, query, token, mapper, raiseerr):
if _is_aliased_class(mapper):
searchfor = mapper
else:
searchfor = _class_to_mapper(mapper)
for ent in query._mapper_entities:
if ent.corresponds_to(searchfor):
return ent
else:
if raiseerr:
if not list(query._mapper_entities):
raise sa_exc.ArgumentError(
"Query has only expression-based entities - "
"can't find property named '%s'."
% (token, )
)
else:
raise sa_exc.ArgumentError(
"Can't find property '%s' on any entity "
"specified in this Query. Note the full path "
"from root (%s) to target entity must be specified."
% (token, ",".join(str(x) for
x in query._mapper_entities))
)
else:
return None
def _find_entity_basestring(self, query, token, raiseerr):
if token.endswith(':' + _WILDCARD_TOKEN):
if len(list(query._mapper_entities)) != 1:
if raiseerr:
raise sa_exc.ArgumentError(
"Wildcard loader can only be used with exactly "
"one entity. Use Load(ent) to specify "
"specific entities.")
elif token.endswith(_DEFAULT_TOKEN):
raiseerr = False
for ent in query._mapper_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:`.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):
self._unbound_all_fn = fn
fn.__doc__ = """Produce a standalone "all" option for :func:`.orm.%(name)s`.
.. deprecated:: 0.9.0
The "_all()" style is replaced by method chaining, e.g.::
session.query(MyClass).options(
%(name)s("someattribute").%(name)s("anotherattribute")
)
""" % {"name": self.name}
return self
@loader_option()
def contains_eager(loadopt, attr, alias=None):
"""Indicate that the given attribute should be eagerly loaded from
columns stated manually in the query.
This function is part of the :class:`.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.
:func:`contains_eager` also accepts an `alias` argument, which is the
string name of an alias, an :func:`~sqlalchemy.sql.expression.alias`
construct, or an :func:`~sqlalchemy.orm.aliased` construct. Use this when
the eagerly-loaded rows are to come from an aliased table::
user_alias = aliased(User)
sess.query(Order).\\
join((user_alias, Order.user)).\\
options(contains_eager(Order.user, alias=user_alias))
.. seealso::
:ref:`contains_eager`
"""
if alias is not None:
if not isinstance(alias, str):
info = inspect(alias)
alias = info.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:`.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("name", "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("addreses").load_only("email_address")
)
For a :class:`.Query` that has multiple entities, the lead entity can be
specifically referred to using the :class:`.Load` constructor::
session.query(User, Address).join(User.addresses).options(
Load(User).load_only("name", "fullname"),
Load(Address).load_only("email_addres")
)
.. versionadded:: 0.9.0
"""
cloned = loadopt.set_column_strategy(
attrs,
{"deferred": False, "instrument": True}
)
cloned.set_column_strategy("*",
{"deferred": True, "instrument": 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:`.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))
If the joined-eager load is chained onto an existing LEFT OUTER JOIN,
``innerjoin=True`` will be bypassed and the join will continue to
chain as LEFT OUTER JOIN so that the results don't change. As an alternative,
specify the value ``"nested"``. This will instead nest the join
on the right side, e.g. using the form "a LEFT OUTER JOIN (b JOIN c)".
.. versionadded:: 0.9.4 Added ``innerjoin="nested"`` option to support
nesting of eager "inner" joins.
.. 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` refer to these joins in any way, including ordering.
To produce a specific SQL JOIN which is explicitly available, use
:meth:`.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:`contains_eager`
:func:`.orm.subqueryload`
:func:`.orm.lazyload`
:paramref:`.relationship.lazy`
:paramref:`.relationship.innerjoin` - :func:`.relationship`-level version
of the :paramref:`.joinedload.innerjoin` option.
"""
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)
@joinedload._add_unbound_all_fn
def joinedload_all(*keys, **kw):
return _UnboundLoad._from_keys(
_UnboundLoad.joinedload, keys, True, 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:`.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`
:func:`.orm.joinedload`
:func:`.orm.lazyload`
:paramref:`.relationship.lazy`
"""
return loadopt.set_relationship_strategy(attr, {"lazy": "subquery"})
@subqueryload._add_unbound_fn
def subqueryload(*keys):
return _UnboundLoad._from_keys(_UnboundLoad.subqueryload, keys, False, {})
@subqueryload._add_unbound_all_fn
def subqueryload_all(*keys):
return _UnboundLoad._from_keys(_UnboundLoad.subqueryload, keys, True, {})
@loader_option()
def lazyload(loadopt, attr):
"""Indicate that the given attribute should be loaded using "lazy"
loading.
This function is part of the :class:`.Load` interface and supports
both method-chained and standalone operation.
.. seealso::
:paramref:`.relationship.lazy`
"""
return loadopt.set_relationship_strategy(attr, {"lazy": "select"})
@lazyload._add_unbound_fn
def lazyload(*keys):
return _UnboundLoad._from_keys(_UnboundLoad.lazyload, keys, False, {})
@lazyload._add_unbound_all_fn
def lazyload_all(*keys):
return _UnboundLoad._from_keys(_UnboundLoad.lazyload, keys, True, {})
@loader_option()
def immediateload(loadopt, attr):
"""Indicate that the given attribute should be loaded using
an immediate load with a per-attribute SELECT statement.
This function is part of the :class:`.Load` interface and supports
both method-chained and standalone operation.
.. seealso::
:ref:`loading_toplevel`
:func:`.orm.joinedload`
:func:`.orm.lazyload`
:paramref:`.relationship.lazy`
"""
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.
This function is part of the :class:`.Load` interface and supports
both method-chained and standalone operation.
:func:`.orm.noload` applies to :func:`.relationship` attributes; for
column-based attributes, see :func:`.orm.defer`.
"""
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 defaultload(loadopt, attr):
"""Indicate an attribute should load using its default loader style.
This method is used to link to other loader options, such as
to set the :func:`.orm.defer` option on a class that is linked to
a relationship of the parent class being loaded, :func:`.orm.defaultload`
can be used to navigate this path without changing the loading style
of the relationship::
session.query(MyClass).options(defaultload("someattr").defer("some_column"))
.. seealso::
:func:`.orm.defer`
:func:`.orm.undefer`
"""
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):
"""Indicate that the given column-oriented attribute should be deferred, e.g.
not loaded until accessed.
This function is part of the :class:`.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:`.Load` object that is present on a certain path can have
:meth:`.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 \*addl_attrs: Deprecated; 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.
.. seealso::
:ref:`deferred`
:func:`.orm.undefer`
"""
return loadopt.set_column_strategy(
(key, ),
{"deferred": True, "instrument": True}
)
@defer._add_unbound_fn
def defer(key, *addl_attrs):
return _UnboundLoad._from_keys(_UnboundLoad.defer, (key, ) + addl_attrs, False, {})
@loader_option()
def undefer(loadopt, key):
"""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:`.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("*"))
:param key: Attribute to be undeferred.
:param \*addl_attrs: Deprecated; 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.
.. 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):
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
particiular entity load path.
.. seealso::
:ref:`deferred`
:func:`.orm.defer`
:func:`.orm.undefer`
"""
return loadopt.set_column_strategy(
"*",
None,
{"undefer_group": name}
)
@undefer_group._add_unbound_fn
def undefer_group(name):
return _UnboundLoad().undefer_group(name)