summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/strategy_options.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm/strategy_options.py')
-rw-r--r--lib/sqlalchemy/orm/strategy_options.py229
1 files changed, 135 insertions, 94 deletions
diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py
index 28130dab5..392f7cec2 100644
--- a/lib/sqlalchemy/orm/strategy_options.py
+++ b/lib/sqlalchemy/orm/strategy_options.py
@@ -16,17 +16,20 @@ 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
+ _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.
+ :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`.
+ :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
@@ -41,11 +44,12 @@ class Load(Generative, MapperOption):
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`::
+ 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")
@@ -55,11 +59,12 @@ class Load(Generative, MapperOption):
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"::
+ 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"),
@@ -67,11 +72,12 @@ class Load(Generative, MapperOption):
)
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.
+ ``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
@@ -106,7 +112,7 @@ class Load(Generative, MapperOption):
if raiseerr and not path.has_entity:
if isinstance(path, TokenRegistry):
raise sa_exc.ArgumentError(
- "Wildcard token cannot be followed by another entity")
+ "Wildcard token cannot be followed by another entity")
else:
raise sa_exc.ArgumentError(
"Attribute '%s' of entity '%s' does not "
@@ -145,8 +151,9 @@ class Load(Generative, MapperOption):
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))
+ raise sa_exc.ArgumentError(
+ "Attribute '%s' does not "
+ "link from element '%s'" % (attr, path.entity))
else:
return None
@@ -157,11 +164,11 @@ class Load(Generative, MapperOption):
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))
+ 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]
@@ -176,7 +183,8 @@ class Load(Generative, MapperOption):
return strategy
@_generative
- def set_relationship_strategy(self, attr, strategy, propagate_to_loaders=True):
+ def set_relationship_strategy(
+ self, attr, strategy, propagate_to_loaders=True):
strategy = self._coerce_strat(strategy)
self.propagate_to_loaders = propagate_to_loaders
@@ -225,14 +233,15 @@ class Load(Generative, MapperOption):
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:
+ 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:]
+ return to_chop[i + 1:]
class _UnboundLoad(Load):
@@ -245,6 +254,7 @@ class _UnboundLoad(Load):
of freestanding options, e.g. ``joinedload('x.y.z')``.
"""
+
def __init__(self):
self.path = ()
self._to_bind = set()
@@ -318,14 +328,15 @@ class _UnboundLoad(Load):
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())):
+ 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:
+ 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:
@@ -335,7 +346,6 @@ class _UnboundLoad(Load):
return to_chop[i:]
-
def _bind_loader(self, query, context, raiseerr):
start_path = self.path
# _current_path implies we're in a
@@ -354,15 +364,15 @@ class _UnboundLoad(Load):
elif isinstance(token, PropComparator):
prop = token.property
entity = self._find_entity_prop_comparator(
- query,
- prop.key,
- token._parententity,
- raiseerr)
+ query,
+ prop.key,
+ token._parententity,
+ raiseerr)
else:
raise sa_exc.ArgumentError(
- "mapper option expects "
- "string key or list of attributes")
+ "mapper option expects "
+ "string key or list of attributes")
if not entity:
return
@@ -378,7 +388,7 @@ class _UnboundLoad(Load):
path = loader.path
for token in start_path:
loader.path = path = loader._generate_path(
- loader.path, token, None, raiseerr)
+ loader.path, token, None, raiseerr)
if path is None:
return
@@ -390,8 +400,8 @@ class _UnboundLoad(Load):
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")
+ # 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:
@@ -411,7 +421,7 @@ class _UnboundLoad(Load):
raise sa_exc.ArgumentError(
"Query has only expression-based entities - "
"can't find property named '%s'."
- % (token, )
+ % (token, )
)
else:
raise sa_exc.ArgumentError(
@@ -419,7 +429,7 @@ class _UnboundLoad(Load):
"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))
+ x in query._mapper_entities))
)
else:
return None
@@ -429,9 +439,9 @@ class _UnboundLoad(Load):
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.")
+ "Wildcard loader can only be used with exactly "
+ "one entity. Use Load(ent) to specify "
+ "specific entities.")
elif token.endswith(_DEFAULT_TOKEN):
raiseerr = False
@@ -445,13 +455,12 @@ class _UnboundLoad(Load):
raise sa_exc.ArgumentError(
"Query has only expression-based entities - "
"can't find property named '%s'."
- % (token, )
+ % (token, )
)
else:
return None
-
class loader_option(object):
def __init__(self):
pass
@@ -493,6 +502,7 @@ See :func:`.orm.%(name)s` for usage examples.
""" % {"name": self.name}
return self
+
@loader_option()
def contains_eager(loadopt, attr, alias=None):
"""Indicate that the given attribute should be eagerly loaded from
@@ -533,16 +543,19 @@ def contains_eager(loadopt, attr, alias=None):
alias = info.selectable
cloned = loadopt.set_relationship_strategy(
- attr,
- {"lazy": "joined"},
- propagate_to_loaders=False
- )
+ 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)
+ return _UnboundLoad()._from_keys(
+ _UnboundLoad.contains_eager, keys, True, kw)
+
@loader_option()
def load_only(loadopt, *attrs):
@@ -559,8 +572,8 @@ def load_only(loadopt, *attrs):
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::
+ 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")
@@ -579,18 +592,20 @@ def load_only(loadopt, *attrs):
"""
cloned = loadopt.set_column_strategy(
- attrs,
- {"deferred": False, "instrument": True}
- )
+ attrs,
+ {"deferred": False, "instrument": True}
+ )
cloned.set_column_strategy("*",
- {"deferred": True, "instrument": True},
- {"undefer_pks": True})
+ {"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
@@ -618,22 +633,25 @@ def joinedload(loadopt, attr, innerjoin=None):
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)".
+ 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.
+ 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`.
+ of collections, use :func:`.orm.contains_eager`; see
+ :ref:`contains_eager`.
.. seealso::
@@ -647,8 +665,8 @@ def joinedload(loadopt, attr, innerjoin=None):
:paramref:`.relationship.lazy`
- :paramref:`.relationship.innerjoin` - :func:`.relationship`-level version
- of the :paramref:`.joinedload.innerjoin` option.
+ :paramref:`.relationship.innerjoin` - :func:`.relationship`-level
+ version of the :paramref:`.joinedload.innerjoin` option.
"""
loader = loadopt.set_relationship_strategy(attr, {"lazy": "joined"})
@@ -656,15 +674,17 @@ def joinedload(loadopt, attr, innerjoin=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)
+ _UnboundLoad.joinedload, keys, False, kw)
+
@joinedload._add_unbound_all_fn
def joinedload_all(*keys, **kw):
return _UnboundLoad._from_keys(
- _UnboundLoad.joinedload, keys, True, kw)
+ _UnboundLoad.joinedload, keys, True, kw)
@loader_option()
@@ -701,14 +721,17 @@ def subqueryload(loadopt, attr):
"""
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"
@@ -724,14 +747,17 @@ def lazyload(loadopt, attr):
"""
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
@@ -754,9 +780,11 @@ def immediateload(loadopt, attr):
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, {})
+ return _UnboundLoad._from_keys(
+ _UnboundLoad.immediateload, keys, False, {})
@loader_option()
@@ -773,10 +801,12 @@ def noload(loadopt, attr):
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.
@@ -797,14 +827,16 @@ def defaultload(loadopt, attr):
"""
return loadopt.set_relationship_strategy(
- attr,
- None
- )
+ 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.
@@ -858,19 +890,21 @@ def defer(loadopt, key):
"""
return loadopt.set_column_strategy(
- (key, ),
- {"deferred": True, "instrument": True}
- )
+ (key, ),
+ {"deferred": True, "instrument": True}
+ )
@defer._add_unbound_fn
def defer(key, *addl_attrs):
- return _UnboundLoad._from_keys(_UnboundLoad.defer, (key, ) + addl_attrs, False, {})
+ 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.
+ """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.
@@ -884,7 +918,8 @@ def undefer(loadopt, key):
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("*"))
+ session.query(MyClass, MyOtherClass).options(
+ Load(MyClass).undefer("*"))
:param key: Attribute to be undeferred.
@@ -902,17 +937,21 @@ def undefer(loadopt, key):
"""
return loadopt.set_column_strategy(
- (key, ),
- {"deferred": False, "instrument": True}
- )
+ (key, ),
+ {"deferred": False, "instrument": True}
+ )
+
@undefer._add_unbound_fn
def undefer(key, *addl_attrs):
- return _UnboundLoad._from_keys(_UnboundLoad.undefer, (key, ) + addl_attrs, False, {})
+ 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.
+ """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.
@@ -922,9 +961,11 @@ def undefer_group(loadopt, name):
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`::
+ spelled out using relationship loader options, such as
+ :func:`.orm.defaultload`::
- session.query(MyClass).options(defaultload("someattr").undefer_group("large_attrs"))
+ 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.
@@ -939,12 +980,12 @@ def undefer_group(loadopt, name):
"""
return loadopt.set_column_strategy(
- "*",
- None,
- {"undefer_group": name}
- )
+ "*",
+ None,
+ {"undefer_group": name}
+ )
+
@undefer_group._add_unbound_fn
def undefer_group(name):
return _UnboundLoad().undefer_group(name)
-