summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/__init__.py412
-rw-r--r--lib/sqlalchemy/orm/base.py13
-rw-r--r--lib/sqlalchemy/orm/descriptor_props.py3
-rw-r--r--lib/sqlalchemy/orm/dynamic.py2
-rw-r--r--lib/sqlalchemy/orm/interfaces.py321
-rw-r--r--lib/sqlalchemy/orm/path_registry.py89
-rw-r--r--lib/sqlalchemy/orm/properties.py7
-rw-r--r--lib/sqlalchemy/orm/query.py30
-rw-r--r--lib/sqlalchemy/orm/relationships.py11
-rw-r--r--lib/sqlalchemy/orm/strategies.py287
-rw-r--r--lib/sqlalchemy/orm/strategy_options.py893
11 files changed, 1190 insertions, 878 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 5cd0f2854..225311db6 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -148,19 +148,22 @@ def backref(name, **kwargs):
return (name, kwargs)
-def deferred(*columns, **kwargs):
- """Return a :class:`.DeferredColumnProperty`, which indicates this
- object attributes should only be loaded from its corresponding
- table column when first accessed.
+def deferred(*columns, **kw):
+ """Indicate a column-based mapped attribute that by default will
+ not load unless accessed.
- Used with the "properties" dictionary sent to :func:`mapper`.
+ :param \*columns: columns to be mapped. This is typically a single
+ :class:`.Column` object, however a collection is supported in order
+ to support multiple columns mapped under the same attribute.
- See also:
+ :param \**kw: additional keyword arguments passed to :class:`.ColumnProperty`.
- :ref:`deferred`
+ .. seealso::
+
+ :ref:`deferred`
"""
- return ColumnProperty(deferred=True, *columns, **kwargs)
+ return ColumnProperty(deferred=True, *columns, **kw)
mapper = public_factory(Mapper, ".orm.mapper")
@@ -213,107 +216,24 @@ def clear_mappers():
finally:
mapperlib._CONFIGURE_MUTEX.release()
-
-def joinedload(*keys, **kw):
- """Return a ``MapperOption`` that will convert the property of the given
- name or series of mapped attributes into an joined eager load.
-
- .. versionchanged:: 0.6beta3
- This function is known as :func:`eagerload` in all versions
- of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
- series. :func:`eagerload` will remain available for the foreseeable
- future in order to enable cross-compatibility.
-
- Used with :meth:`~sqlalchemy.orm.query.Query.options`.
-
- examples::
-
- # joined-load the "orders" collection on "User"
- query(User).options(joinedload(User.orders))
-
- # joined-load the "keywords" collection on each "Item",
- # but not the "items" collection on "Order" - those
- # remain lazily loaded.
- query(Order).options(joinedload(Order.items, Item.keywords))
-
- # to joined-load across both, use joinedload_all()
- query(Order).options(joinedload_all(Order.items, Item.keywords))
-
- # set the default strategy to be 'joined'
- query(Order).options(joinedload('*'))
-
- :func:`joinedload` also accepts a keyword argument `innerjoin=True` which
- indicates using an inner join instead of an outer::
-
- query(Order).options(joinedload(Order.user, innerjoin=True))
-
- .. note::
-
- The join created by :func:`joinedload` is anonymously aliased such that
- it **does not affect the query results**. An :meth:`.Query.order_by`
- or :meth:`.Query.filter` call **cannot** reference these aliased
- tables - so-called "user space" joins are constructed using
- :meth:`.Query.join`. The rationale for this is that
- :func:`joinedload` is only applied in order to affect how related
- objects or collections are loaded as an optimizing detail - it can be
- added or removed with no impact on actual results. See the section
- :ref:`zen_of_eager_loading` for a detailed description of how this is
- used, including how to use a single explicit JOIN for
- filtering/ordering and eager loading simultaneously.
-
- See also: :func:`subqueryload`, :func:`lazyload`
-
- """
- innerjoin = kw.pop('innerjoin', None)
- if innerjoin is not None:
- return (
- _strategies.EagerLazyOption(keys, lazy='joined'),
- _strategies.EagerJoinOption(keys, innerjoin)
- )
- else:
- return _strategies.EagerLazyOption(keys, lazy='joined')
-
-
-def joinedload_all(*keys, **kw):
- """Return a ``MapperOption`` that will convert all properties along the
- given dot-separated path or series of mapped attributes
- into an joined eager load.
-
- .. versionchanged:: 0.6beta3
- This function is known as :func:`eagerload_all` in all versions
- of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
- series. :func:`eagerload_all` will remain available for the
- foreseeable future in order to enable cross-compatibility.
-
- Used with :meth:`~sqlalchemy.orm.query.Query.options`.
-
- For example::
-
- query.options(joinedload_all('orders.items.keywords'))...
-
- will set all of ``orders``, ``orders.items``, and
- ``orders.items.keywords`` to load in one joined eager load.
-
- Individual descriptors are accepted as arguments as well::
-
- query.options(joinedload_all(User.orders, Order.items, Item.keywords))
-
- The keyword arguments accept a flag `innerjoin=True|False` which will
- override the value of the `innerjoin` flag specified on the
- relationship().
-
- See also: :func:`subqueryload_all`, :func:`lazyload`
-
- """
- innerjoin = kw.pop('innerjoin', None)
- if innerjoin is not None:
- return (
- _strategies.EagerLazyOption(keys, lazy='joined', chained=True),
- _strategies.EagerJoinOption(keys, innerjoin, chained=True)
- )
- else:
- return _strategies.EagerLazyOption(keys, lazy='joined', chained=True)
-
+from . import strategy_options
+
+joinedload = strategy_options.joinedload._unbound_fn
+joinedload_all = strategy_options.joinedload._unbound_all_fn
+contains_eager = strategy_options.contains_eager._unbound_fn
+defer = strategy_options.defer._unbound_fn
+undefer = strategy_options.undefer._unbound_fn
+undefer_group = strategy_options.undefer_group._unbound_fn
+load_only = strategy_options.load_only._unbound_fn
+lazyload = strategy_options.lazyload._unbound_fn
+lazyload_all = strategy_options.lazyload_all._unbound_all_fn
+subqueryload = strategy_options.subqueryload._unbound_fn
+subqueryload_all = strategy_options.subqueryload_all._unbound_all_fn
+immediateload = strategy_options.immediateload._unbound_fn
+noload = strategy_options.noload._unbound_fn
+defaultload = strategy_options.defaultload._unbound_fn
+
+from .strategy_options import Load
def eagerload(*args, **kwargs):
"""A synonym for :func:`joinedload()`."""
@@ -325,285 +245,11 @@ def eagerload_all(*args, **kwargs):
return joinedload_all(*args, **kwargs)
-def subqueryload(*keys):
- """Return a ``MapperOption`` that will convert the property
- of the given name or series of mapped attributes
- into an subquery eager load.
-
- Used with :meth:`~sqlalchemy.orm.query.Query.options`.
-
- examples::
-
- # subquery-load the "orders" collection on "User"
- query(User).options(subqueryload(User.orders))
-
- # subquery-load the "keywords" collection on each "Item",
- # but not the "items" collection on "Order" - those
- # remain lazily loaded.
- query(Order).options(subqueryload(Order.items, Item.keywords))
- # to subquery-load across both, use subqueryload_all()
- query(Order).options(subqueryload_all(Order.items, Item.keywords))
-
- # set the default strategy to be 'subquery'
- query(Order).options(subqueryload('*'))
-
- See also: :func:`joinedload`, :func:`lazyload`
-
- """
- return _strategies.EagerLazyOption(keys, lazy="subquery")
-
-
-def subqueryload_all(*keys):
- """Return a ``MapperOption`` that will convert all properties along the
- given dot-separated path or series of mapped attributes
- into a subquery eager load.
-
- Used with :meth:`~sqlalchemy.orm.query.Query.options`.
-
- For example::
-
- query.options(subqueryload_all('orders.items.keywords'))...
-
- will set all of ``orders``, ``orders.items``, and
- ``orders.items.keywords`` to load in one subquery eager load.
-
- Individual descriptors are accepted as arguments as well::
-
- query.options(subqueryload_all(User.orders, Order.items,
- Item.keywords))
-
- See also: :func:`joinedload_all`, :func:`lazyload`, :func:`immediateload`
-
- """
- return _strategies.EagerLazyOption(keys, lazy="subquery", chained=True)
-
-
-def lazyload(*keys):
- """Return a ``MapperOption`` that will convert the property of the given
- name or series of mapped attributes into a lazy load.
-
- Used with :meth:`~sqlalchemy.orm.query.Query.options`.
-
- See also: :func:`eagerload`, :func:`subqueryload`, :func:`immediateload`
-
- """
- return _strategies.EagerLazyOption(keys, lazy=True)
-
-
-def lazyload_all(*keys):
- """Return a ``MapperOption`` that will convert all the properties
- along the given dot-separated path or series of mapped attributes
- into a lazy load.
-
- Used with :meth:`~sqlalchemy.orm.query.Query.options`.
-
- See also: :func:`eagerload`, :func:`subqueryload`, :func:`immediateload`
-
- """
- return _strategies.EagerLazyOption(keys, lazy=True, chained=True)
-
-
-def noload(*keys):
- """Return a ``MapperOption`` that will convert the property of the
- given name or series of mapped attributes into a non-load.
-
- Used with :meth:`~sqlalchemy.orm.query.Query.options`.
-
- See also: :func:`lazyload`, :func:`eagerload`,
- :func:`subqueryload`, :func:`immediateload`
-
- """
- return _strategies.EagerLazyOption(keys, lazy=None)
-
-
-def immediateload(*keys):
- """Return a ``MapperOption`` that will convert the property of the given
- name or series of mapped attributes into an immediate load.
-
- The "immediate" load means the attribute will be fetched
- with a separate SELECT statement per parent in the
- same way as lazy loading - except the loader is guaranteed
- to be called at load time before the parent object
- is returned in the result.
-
- The normal behavior of lazy loading applies - if
- the relationship is a simple many-to-one, and the child
- object is already present in the :class:`.Session`,
- no SELECT statement will be emitted.
-
- Used with :meth:`~sqlalchemy.orm.query.Query.options`.
-
- See also: :func:`lazyload`, :func:`eagerload`, :func:`subqueryload`
-
- .. versionadded:: 0.6.5
-
- """
- return _strategies.EagerLazyOption(keys, lazy='immediate')
contains_alias = public_factory(AliasOption, ".orm.contains_alias")
-def contains_eager(*keys, **kwargs):
- """Return a ``MapperOption`` that will indicate to the query that
- the given attribute should be eagerly loaded from columns currently
- in the query.
-
- Used with :meth:`~sqlalchemy.orm.query.Query.options`.
-
- 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))
-
- See also :func:`eagerload` for the "automatic" version of this
- functionality.
-
- For additional examples of :func:`contains_eager` see
- :ref:`contains_eager`.
-
- """
- alias = kwargs.pop('alias', None)
- if kwargs:
- raise exc.ArgumentError(
- 'Invalid kwargs for contains_eager: %r' % list(kwargs.keys()))
- return _strategies.EagerLazyOption(keys, lazy='joined',
- propagate_to_loaders=False, chained=True), \
- _strategies.LoadEagerFromAliasOption(keys, alias=alias, chained=True)
-
-
-def defer(*key):
- """Return a :class:`.MapperOption` that will convert the column property
- of the given name into a deferred load.
-
- Used with :meth:`.Query.options`.
-
- e.g.::
-
- from sqlalchemy.orm import defer
-
- query(MyClass).options(defer("attribute_one"),
- defer("attribute_two"))
-
- A class bound descriptor is also accepted::
-
- query(MyClass).options(
- defer(MyClass.attribute_one),
- defer(MyClass.attribute_two))
-
- A "path" can be specified onto a related or collection object using a
- dotted name. The :func:`.orm.defer` option will be applied to that object
- when loaded::
-
- query(MyClass).options(
- defer("related.attribute_one"),
- defer("related.attribute_two"))
-
- To specify a path via class, send multiple arguments::
-
- query(MyClass).options(
- defer(MyClass.related, MyOtherClass.attribute_one),
- defer(MyClass.related, MyOtherClass.attribute_two))
-
- See also:
-
- :ref:`deferred`
-
- :param \*key: A key representing an individual path. Multiple entries
- are accepted to allow a multiple-token path for a single target, not
- multiple targets.
-
- """
- return _strategies.DeferredOption(key, defer=True)
-
-
-def undefer(*key):
- """Return a :class:`.MapperOption` that will convert the column property
- of the given name into a non-deferred (regular column) load.
-
- Used with :meth:`.Query.options`.
-
- e.g.::
-
- from sqlalchemy.orm import undefer
-
- query(MyClass).options(
- undefer("attribute_one"),
- undefer("attribute_two"))
-
- A class bound descriptor is also accepted::
-
- query(MyClass).options(
- undefer(MyClass.attribute_one),
- undefer(MyClass.attribute_two))
-
- A "path" can be specified onto a related or collection object using a
- dotted name. The :func:`.orm.undefer` option will be applied to that
- object when loaded::
-
- query(MyClass).options(
- undefer("related.attribute_one"),
- undefer("related.attribute_two"))
-
- To specify a path via class, send multiple arguments::
-
- query(MyClass).options(
- undefer(MyClass.related, MyOtherClass.attribute_one),
- undefer(MyClass.related, MyOtherClass.attribute_two))
-
- See also:
-
- :func:`.orm.undefer_group` as a means to "undefer" a group
- of attributes at once.
-
- :ref:`deferred`
-
- :param \*key: A key representing an individual path. Multiple entries
- are accepted to allow a multiple-token path for a single target, not
- multiple targets.
-
- """
- return _strategies.DeferredOption(key, defer=False)
-
-
-def undefer_group(name):
- """Return a :class:`.MapperOption` that will convert the given group of
- deferred column properties into a non-deferred (regular column) load.
-
- Used with :meth:`.Query.options`.
-
- e.g.::
-
- query(MyClass).options(undefer("group_one"))
-
- See also:
-
- :ref:`deferred`
-
- :param name: String name of the deferred group. This name is
- established using the "group" name to the :func:`.orm.deferred`
- configurational function.
-
- """
- return _strategies.UndeferGroupOption(name)
-
-
def __go(lcls):
global __all__
diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py
index f7d9dd4fe..47d8796b8 100644
--- a/lib/sqlalchemy/orm/base.py
+++ b/lib/sqlalchemy/orm/base.py
@@ -129,6 +129,19 @@ NOT_EXTENSION = util.symbol('NOT_EXTENSION')
_none_set = frozenset([None])
+def _generative(*assertions):
+ """Mark a method as generative, e.g. method-chained."""
+
+ @util.decorator
+ def generate(fn, *args, **kw):
+ self = args[0]._clone()
+ for assertion in assertions:
+ assertion(self, fn.__name__)
+ fn(self, *args[1:], **kw)
+ return self
+ return generate
+
+
# these can be replaced by sqlalchemy.ext.instrumentation
# if augmented class instrumentation is enabled.
def manager_of_class(cls):
diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py
index bbfe602d0..daf125ea2 100644
--- a/lib/sqlalchemy/orm/descriptor_props.py
+++ b/lib/sqlalchemy/orm/descriptor_props.py
@@ -261,7 +261,8 @@ class CompositeProperty(DescriptorProperty):
if self.deferred:
prop.deferred = self.deferred
prop.strategy_class = prop._strategy_lookup(
- deferred=True, instrument=True)
+ ("deferred", True),
+ ("instrument", True))
prop.group = self.group
def _setup_event_handlers(self):
diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py
index 4631e806f..b419d2a07 100644
--- a/lib/sqlalchemy/orm/dynamic.py
+++ b/lib/sqlalchemy/orm/dynamic.py
@@ -20,7 +20,7 @@ from . import (
from .query import Query
@log.class_logger
-@properties.RelationshipProperty._strategy_for(dict(lazy="dynamic"))
+@properties.RelationshipProperty.strategy_for(lazy="dynamic")
class DynaLoader(strategies.AbstractRelationshipLoader):
def init_class_attribute(self, mapper):
self.is_class_level = True
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index 2f4aa5208..18723e4f6 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -21,7 +21,6 @@ from __future__ import absolute_import
from .. import exc as sa_exc, util, inspect
from ..sql import operators
from collections import deque
-from .base import _is_aliased_class, _class_to_mapper
from .base import ONETOMANY, MANYTOONE, MANYTOMANY, EXT_CONTINUE, EXT_STOP, NOT_EXTENSION
from .base import _InspectionAttr, _MappedAttribute
from .path_registry import PathRegistry
@@ -424,51 +423,57 @@ class StrategizedProperty(MapperProperty):
strategy_wildcard_key = None
- @util.memoized_property
- def _wildcard_path(self):
- if self.strategy_wildcard_key:
- return ('loaderstrategy', (self.strategy_wildcard_key,))
- else:
- return None
+ def _get_context_loader(self, context, path):
+ load = None
- def _get_context_strategy(self, context, path):
- strategy_cls = path._inlined_get_for(self, context, 'loaderstrategy')
+ # use EntityRegistry.__getitem__()->PropRegistry here so
+ # that the path is stated in terms of our base
+ search_path = dict.__getitem__(path, self)
- if not strategy_cls:
- wc_key = self._wildcard_path
- if wc_key and wc_key in context.attributes:
- strategy_cls = context.attributes[wc_key]
+ # search among: exact match, "attr.*", "default" strategy
+ # if any.
+ for path_key in (
+ search_path._loader_key,
+ search_path._wildcard_path_loader_key,
+ search_path._default_path_loader_key
+ ):
+ if path_key in context.attributes:
+ load = context.attributes[path_key]
+ break
- if strategy_cls:
- try:
- return self._strategies[strategy_cls]
- except KeyError:
- return self.__init_strategy(strategy_cls)
- return self.strategy
+ return load
- def _get_strategy(self, cls):
+ def _get_strategy(self, key):
try:
- return self._strategies[cls]
+ return self._strategies[key]
except KeyError:
- return self.__init_strategy(cls)
+ cls = self._strategy_lookup(*key)
+ self._strategies[key] = self._strategies[cls] = strategy = cls(self)
+ return strategy
- def __init_strategy(self, cls):
- self._strategies[cls] = strategy = cls(self)
- return strategy
+ def _get_strategy_by_cls(self, cls):
+ return self._get_strategy(cls._strategy_keys[0])
def setup(self, context, entity, path, adapter, **kwargs):
- self._get_context_strategy(context, path).\
- setup_query(context, entity, path,
- adapter, **kwargs)
+ loader = self._get_context_loader(context, path)
+ if loader and loader.strategy:
+ strat = self._get_strategy(loader.strategy)
+ else:
+ strat = self.strategy
+ strat.setup_query(context, entity, path, loader, adapter, **kwargs)
def create_row_processor(self, context, path, mapper, row, adapter):
- return self._get_context_strategy(context, path).\
- create_row_processor(context, path,
+ loader = self._get_context_loader(context, path)
+ if loader and loader.strategy:
+ strat = self._get_strategy(loader.strategy)
+ else:
+ strat = self.strategy
+ return strat.create_row_processor(context, path, loader,
mapper, row, adapter)
def do_init(self):
self._strategies = {}
- self.strategy = self.__init_strategy(self.strategy_class)
+ self.strategy = self._get_strategy_by_cls(self.strategy_class)
def post_instrument_class(self, mapper):
if self.is_primary() and \
@@ -479,17 +484,17 @@ class StrategizedProperty(MapperProperty):
_strategies = collections.defaultdict(dict)
@classmethod
- def _strategy_for(cls, *keys):
+ def strategy_for(cls, **kw):
def decorate(dec_cls):
- for key in keys:
- key = tuple(sorted(key.items()))
- cls._strategies[cls][key] = dec_cls
+ dec_cls._strategy_keys = []
+ key = tuple(sorted(kw.items()))
+ cls._strategies[cls][key] = dec_cls
+ dec_cls._strategy_keys.append(key)
return dec_cls
return decorate
@classmethod
- def _strategy_lookup(cls, **kw):
- key = tuple(sorted(kw.items()))
+ def _strategy_lookup(cls, *key):
for prop_cls in cls.__mro__:
if prop_cls in cls._strategies:
strategies = cls._strategies[prop_cls]
@@ -497,7 +502,7 @@ class StrategizedProperty(MapperProperty):
return strategies[key]
except KeyError:
pass
- raise Exception("can't locate strategy for %s %s" % (cls, kw))
+ raise Exception("can't locate strategy for %s %s" % (cls, key))
class MapperOption(object):
@@ -521,242 +526,6 @@ class MapperOption(object):
self.process_query(query)
-class PropertyOption(MapperOption):
- """A MapperOption that is applied to a property off the mapper or
- one of its child mappers, identified by a dot-separated key
- or list of class-bound attributes. """
-
- def __init__(self, key, mapper=None):
- self.key = key
- self.mapper = mapper
-
- def process_query(self, query):
- self._process(query, True)
-
- def process_query_conditionally(self, query):
- self._process(query, False)
-
- def _process(self, query, raiseerr):
- paths = self._process_paths(query, raiseerr)
- if paths:
- self.process_query_property(query, paths)
-
- def process_query_property(self, query, paths):
- pass
-
- def __getstate__(self):
- d = self.__dict__.copy()
- d['key'] = ret = []
- for token in util.to_list(self.key):
- 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['key']:
- if isinstance(key, tuple):
- cls, propkey = key
- ret.append(getattr(cls, propkey))
- else:
- ret.append(key)
- state['key'] = tuple(ret)
- self.__dict__ = state
-
- 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):
- 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
-
- @util.dependencies("sqlalchemy.orm.util")
- def _process_paths(self, orm_util, query, raiseerr):
- """reconcile the 'key' for this PropertyOption with
- the current path and entities of the query.
-
- Return a list of affected paths.
-
- """
- path = PathRegistry.root
- entity = None
- paths = []
- no_result = []
-
- # _current_path implies we're in a
- # secondary load with an existing path
- current_path = list(query._current_path.path)
-
- tokens = deque(self.key)
- while tokens:
- token = tokens.popleft()
- if isinstance(token, str):
- # wildcard token
- if token.endswith(':*'):
- return [path.token(token)]
- sub_tokens = token.split(".", 1)
- token = sub_tokens[0]
- tokens.extendleft(sub_tokens[1:])
-
- # exhaust current_path before
- # matching tokens to entities
- if current_path:
- if current_path[1].key == token:
- current_path = current_path[2:]
- continue
- else:
- return no_result
-
- if not entity:
- entity = self._find_entity_basestring(
- query,
- token,
- raiseerr)
- if entity is None:
- return no_result
- path_element = entity.entity_zero
- mapper = entity.mapper
-
- if hasattr(mapper.class_, token):
- prop = getattr(mapper.class_, token).property
- else:
- if raiseerr:
- raise sa_exc.ArgumentError(
- "Can't find property named '%s' on the "
- "mapped entity %s in this Query. " % (
- token, mapper)
- )
- else:
- return no_result
- elif isinstance(token, PropComparator):
- prop = token.property
-
- # exhaust current_path before
- # matching tokens to entities
- if current_path:
- if current_path[0:2] == \
- [token._parententity, prop]:
- current_path = current_path[2:]
- continue
- else:
- return no_result
-
- if not entity:
- entity = self._find_entity_prop_comparator(
- query,
- prop.key,
- token._parententity,
- raiseerr)
- if not entity:
- return no_result
-
- path_element = entity.entity_zero
- mapper = entity.mapper
- else:
- raise sa_exc.ArgumentError(
- "mapper option expects "
- "string key or list of attributes")
- assert prop is not None
- if raiseerr and not prop.parent.common_parent(mapper):
- raise sa_exc.ArgumentError("Attribute '%s' does not "
- "link from element '%s'" % (token, path_element))
-
- path = path[path_element][prop]
-
- paths.append(path)
-
- if getattr(token, '_of_type', None):
- ac = token._of_type
- ext_info = inspect(ac)
- path_element = mapper = 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)
- ext_info = inspect(ac)
- path.set(query._attributes, "path_with_polymorphic", ext_info)
- else:
- path_element = mapper = getattr(prop, 'mapper', None)
- if mapper is None and tokens:
- raise sa_exc.ArgumentError(
- "Attribute '%s' of entity '%s' does not "
- "refer to a mapped entity" %
- (token, entity)
- )
-
- if current_path:
- # ran out of tokens before
- # current_path was exhausted.
- assert not tokens
- return no_result
-
- return paths
-
-
-class StrategizedOption(PropertyOption):
- """A MapperOption that affects which LoaderStrategy will be used
- for an operation by a StrategizedProperty.
- """
-
- chained = False
-
- def process_query_property(self, query, paths):
- strategy = self.get_strategy_class()
- if self.chained:
- for path in paths:
- path.set(
- query._attributes,
- "loaderstrategy",
- strategy
- )
- else:
- paths[-1].set(
- query._attributes,
- "loaderstrategy",
- strategy
- )
-
- def get_strategy_class(self):
- raise NotImplementedError()
class LoaderStrategy(object):
@@ -791,10 +560,10 @@ class LoaderStrategy(object):
def init_class_attribute(self, mapper):
pass
- def setup_query(self, context, entity, path, adapter, **kwargs):
+ def setup_query(self, context, entity, path, loadopt, adapter, **kwargs):
pass
- def create_row_processor(self, context, path, mapper,
+ def create_row_processor(self, context, path, loadopt, mapper,
row, adapter):
"""Return row processing functions which fulfill the contract
specified by MapperProperty.create_row_processor.
diff --git a/lib/sqlalchemy/orm/path_registry.py b/lib/sqlalchemy/orm/path_registry.py
index c9c91f905..fdc4f5654 100644
--- a/lib/sqlalchemy/orm/path_registry.py
+++ b/lib/sqlalchemy/orm/path_registry.py
@@ -9,12 +9,17 @@
from .. import inspection
from .. import util
+from .. import exc
from itertools import chain
from .base import class_mapper
def _unreduce_path(path):
return PathRegistry.deserialize(path)
+
+_WILDCARD_TOKEN = "*"
+_DEFAULT_TOKEN = "_sa_default"
+
class PathRegistry(object):
"""Represent query load paths and registry functions.
@@ -116,9 +121,13 @@ class PathRegistry(object):
def coerce(cls, raw):
return util.reduce(lambda prev, next: prev[next], raw, cls.root)
- @classmethod
- def token(cls, token):
- return TokenRegistry(cls.root, token)
+ def token(self, token):
+ if token.endswith(':' + _WILDCARD_TOKEN):
+ return TokenRegistry(self, token)
+ elif token.endswith(":" + _DEFAULT_TOKEN):
+ return TokenRegistry(self.root, token)
+ else:
+ raise exc.ArgumentError("invalid token: %s" % token)
def __add__(self, other):
return util.reduce(
@@ -135,9 +144,10 @@ class RootRegistry(PathRegistry):
"""
path = ()
-
+ has_entity = False
def __getitem__(self, entity):
return entity._path_registry
+
PathRegistry.root = RootRegistry()
class TokenRegistry(PathRegistry):
@@ -146,6 +156,8 @@ class TokenRegistry(PathRegistry):
self.parent = parent
self.path = parent.path + (token,)
+ has_entity = False
+
def __getitem__(self, entity):
raise NotImplementedError()
@@ -166,6 +178,47 @@ class PropRegistry(PathRegistry):
self.parent = parent
self.path = parent.path + (prop,)
+ @util.memoized_property
+ def has_entity(self):
+ return hasattr(self.prop, "mapper")
+
+ @util.memoized_property
+ def entity(self):
+ return self.prop.mapper
+
+ @util.memoized_property
+ def _wildcard_path_loader_key(self):
+ """Given a path (mapper A, prop X), replace the prop with the wildcard,
+ e.g. (mapper A, 'relationship:.*') or (mapper A, 'column:.*'), then
+ return within the ("loader", path) structure.
+
+ """
+ return ("loader",
+ self.parent.token(
+ "%s:%s" % (self.prop.strategy_wildcard_key, _WILDCARD_TOKEN)
+ ).path
+ )
+
+ @util.memoized_property
+ def _default_path_loader_key(self):
+ return ("loader",
+ self.parent.token(
+ "%s:%s" % (self.prop.strategy_wildcard_key, _DEFAULT_TOKEN)
+ ).path
+ )
+
+ @util.memoized_property
+ def _loader_key(self):
+ return ("loader", self.path)
+
+ @property
+ def mapper(self):
+ return self.entity
+
+ @property
+ def entity_path(self):
+ return self[self.entity]
+
def __getitem__(self, entity):
if isinstance(entity, (int, slice)):
return self.path[entity]
@@ -174,16 +227,21 @@ class PropRegistry(PathRegistry):
self, entity
)
-
class EntityRegistry(PathRegistry, dict):
is_aliased_class = False
+ has_entity = True
def __init__(self, parent, entity):
self.key = entity
self.parent = parent
self.is_aliased_class = entity.is_aliased_class
-
+ self.entity = entity
self.path = parent.path + (entity,)
+ self.entity_path = self
+
+ @property
+ def mapper(self):
+ return inspection.inspect(self.entity).mapper
def __bool__(self):
return True
@@ -195,26 +253,9 @@ class EntityRegistry(PathRegistry, dict):
else:
return dict.__getitem__(self, entity)
- def _inlined_get_for(self, prop, context, key):
- """an inlined version of:
-
- cls = path[mapperproperty].get(context, key)
-
- Skips the isinstance() check in __getitem__
- and the extra method call for get().
- Used by StrategizedProperty for its
- very frequent lookup.
-
- """
- path = dict.__getitem__(self, prop)
- path_key = (key, path.path)
- if path_key in context.attributes:
- return context.attributes[path_key]
- else:
- return None
-
def __missing__(self, key):
self[key] = item = PropRegistry(self, key)
return item
+
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index ef71d663c..c6eccf944 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -31,6 +31,8 @@ class ColumnProperty(StrategizedProperty):
"""
+ strategy_wildcard_key = 'column'
+
def __init__(self, *columns, **kwargs):
"""Provide a column-level property for use with a Mapper.
@@ -142,8 +144,9 @@ class ColumnProperty(StrategizedProperty):
util.set_creation_order(self)
self.strategy_class = self._strategy_lookup(
- deferred=self.deferred,
- instrument=self.instrument)
+ ("deferred", self.deferred),
+ ("instrument", self.instrument)
+ )
@property
def expression(self):
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index beabc5811..ebfcf1087 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -24,7 +24,8 @@ from . import (
attributes, interfaces, object_mapper, persistence,
exc as orm_exc, loading
)
-from .base import _entity_descriptor, _is_aliased_class, _is_mapped_class, _orm_columns
+from .base import _entity_descriptor, _is_aliased_class, \
+ _is_mapped_class, _orm_columns, _generative
from .path_registry import PathRegistry
from .util import (
AliasedClass, ORMAdapter, join as orm_join, with_parent, aliased
@@ -42,18 +43,6 @@ from . import properties
__all__ = ['Query', 'QueryContext', 'aliased']
-def _generative(*assertions):
- """Mark a method as generative."""
-
- @util.decorator
- def generate(fn, *args, **kw):
- self = args[0]._clone()
- for assertion in assertions:
- assertion(self, fn.__name__)
- fn(self, *args[1:], **kw)
- return self
- return generate
-
_path_registry = PathRegistry.root
@inspection._self_inspects
@@ -3438,28 +3427,29 @@ class QueryContext(object):
class AliasOption(interfaces.MapperOption):
def __init__(self, alias):
- """Return a :class:`.MapperOption` that will indicate to the query that
- the main table has been aliased.
+ """Return a :class:`.MapperOption` that will indicate to the :class:`.Query`
+ that the main table has been aliased.
- This is used in the very rare case that :func:`.contains_eager`
+ This is a seldom-used option to suit the
+ very rare case that :func:`.contains_eager`
is being used in conjunction with a user-defined SELECT
statement that aliases the parent table. E.g.::
# define an aliased UNION called 'ulist'
- statement = users.select(users.c.user_id==7).\\
+ ulist = users.select(users.c.user_id==7).\\
union(users.select(users.c.user_id>7)).\\
alias('ulist')
# add on an eager load of "addresses"
- statement = statement.outerjoin(addresses).\\
+ statement = ulist.outerjoin(addresses).\\
select().apply_labels()
# create query, indicating "ulist" will be an
# alias for the main table, "addresses"
# property should be eager loaded
query = session.query(User).options(
- contains_alias('ulist'),
- contains_eager('addresses'))
+ contains_alias(ulist),
+ contains_eager(User.addresses))
# then get results via the statement
results = query.from_statement(statement).all()
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index f37bb8a4d..2393df26b 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -83,7 +83,7 @@ class RelationshipProperty(StrategizedProperty):
"""
- strategy_wildcard_key = 'relationship:*'
+ strategy_wildcard_key = 'relationship'
_dependency_processor = None
@@ -638,8 +638,7 @@ class RelationshipProperty(StrategizedProperty):
if strategy_class:
self.strategy_class = strategy_class
else:
- self.strategy_class = self._strategy_lookup(lazy=self.lazy)
- self._lazy_strategy = self._strategy_lookup(lazy="select")
+ self.strategy_class = self._strategy_lookup(("lazy", self.lazy))
self._reverse_property = set()
@@ -1149,7 +1148,7 @@ class RelationshipProperty(StrategizedProperty):
alias_secondary=True):
if value is not None:
value = attributes.instance_state(value)
- return self._get_strategy(self._lazy_strategy).lazy_clause(value,
+ return self._lazy_strategy.lazy_clause(value,
reverse_direction=not value_is_parent,
alias_secondary=alias_secondary,
adapt_source=adapt_source)
@@ -1361,6 +1360,8 @@ class RelationshipProperty(StrategizedProperty):
self._post_init()
self._generate_backref()
super(RelationshipProperty, self).do_init()
+ self._lazy_strategy = self._get_strategy((("lazy", "select"),))
+
def _process_dependent_arguments(self):
"""Convert incoming configuration arguments to their
@@ -1602,7 +1603,7 @@ class RelationshipProperty(StrategizedProperty):
"""memoize the 'use_get' attribute of this RelationshipLoader's
lazyloader."""
- strategy = self._get_strategy(self._lazy_strategy)
+ strategy = self._lazy_strategy
return strategy.use_get
@util.memoized_property
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 761e6b999..6ca737c64 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -18,8 +18,7 @@ from .state import InstanceState
from .util import _none_set
from . import properties
from .interfaces import (
- LoaderStrategy, StrategizedOption, MapperOption, PropertyOption,
- StrategizedProperty
+ LoaderStrategy, StrategizedProperty
)
from .session import _state_session
import itertools
@@ -88,7 +87,7 @@ def _register_attribute(strategy, mapper, useobject,
for hook in listen_hooks:
hook(desc, prop)
-@properties.ColumnProperty._strategy_for(dict(instrument=False, deferred=False))
+@properties.ColumnProperty.strategy_for(instrument=False, deferred=False)
class UninstrumentedColumnLoader(LoaderStrategy):
"""Represent the a non-instrumented MapperProperty.
@@ -100,19 +99,19 @@ class UninstrumentedColumnLoader(LoaderStrategy):
super(UninstrumentedColumnLoader, self).__init__(parent)
self.columns = self.parent_property.columns
- def setup_query(self, context, entity, path, adapter,
+ def setup_query(self, context, entity, path, loadopt, adapter,
column_collection=None, **kwargs):
for c in self.columns:
if adapter:
c = adapter.columns[c]
column_collection.append(c)
- def create_row_processor(self, context, path, mapper, row, adapter):
+ def create_row_processor(self, context, path, loadopt, mapper, row, adapter):
return None, None, None
@log.class_logger
-@properties.ColumnProperty._strategy_for(dict(instrument=True, deferred=False))
+@properties.ColumnProperty.strategy_for(instrument=True, deferred=False)
class ColumnLoader(LoaderStrategy):
"""Provide loading behavior for a :class:`.ColumnProperty`."""
@@ -121,7 +120,7 @@ class ColumnLoader(LoaderStrategy):
self.columns = self.parent_property.columns
self.is_composite = hasattr(self.parent_property, 'composite_class')
- def setup_query(self, context, entity, path,
+ def setup_query(self, context, entity, path, loadopt,
adapter, column_collection, **kwargs):
for c in self.columns:
if adapter:
@@ -142,7 +141,7 @@ class ColumnLoader(LoaderStrategy):
)
def create_row_processor(self, context, path,
- mapper, row, adapter):
+ loadopt, mapper, row, adapter):
key = self.key
# look through list of columns represented here
# to see which, if any, is present in the row.
@@ -161,7 +160,7 @@ class ColumnLoader(LoaderStrategy):
@log.class_logger
-@properties.ColumnProperty._strategy_for(dict(deferred=True, instrument=True))
+@properties.ColumnProperty.strategy_for(deferred=True, instrument=True)
class DeferredColumnLoader(LoaderStrategy):
"""Provide loading behavior for a deferred :class:`.ColumnProperty`."""
@@ -173,16 +172,16 @@ class DeferredColumnLoader(LoaderStrategy):
self.columns = self.parent_property.columns
self.group = self.parent_property.group
- def create_row_processor(self, context, path, mapper, row, adapter):
+ def create_row_processor(self, context, path, loadopt, mapper, row, adapter):
col = self.columns[0]
if adapter:
col = adapter.columns[col]
key = self.key
if col in row:
- return self.parent_property._get_strategy(ColumnLoader).\
+ return self.parent_property._get_strategy_by_cls(ColumnLoader).\
create_row_processor(
- context, path, mapper, row, adapter)
+ context, path, loadopt, mapper, row, adapter)
elif not self.is_class_level:
set_deferred_for_local_state = InstanceState._row_processor(
@@ -205,15 +204,15 @@ class DeferredColumnLoader(LoaderStrategy):
expire_missing=False
)
- def setup_query(self, context, entity, path, adapter,
+ def setup_query(self, context, entity, path, loadopt, adapter,
only_load_props=None, **kwargs):
if (
- self.group is not None and
- context.attributes.get(('undefer', self.group), False)
+ loadopt and self.group and
+ loadopt.local_opts.get('undefer_group', False) == self.group
) or (only_load_props and self.key in only_load_props):
- self.parent_property._get_strategy(ColumnLoader).\
+ self.parent_property._get_strategy_by_cls(ColumnLoader).\
setup_query(context, entity,
- path, adapter, **kwargs)
+ path, loadopt, adapter, **kwargs)
def _load_for_state(self, state, passive):
if not state.key:
@@ -270,29 +269,6 @@ class LoadDeferredColumns(object):
return strategy._load_for_state(state, passive)
-class DeferredOption(StrategizedOption):
- propagate_to_loaders = True
-
- def __init__(self, key, defer=False):
- super(DeferredOption, self).__init__(key)
- self.defer = defer
-
- def get_strategy_class(self):
- if self.defer:
- return DeferredColumnLoader
- else:
- return ColumnLoader
-
-
-class UndeferGroupOption(MapperOption):
- propagate_to_loaders = True
-
- def __init__(self, group):
- self.group = group
-
- def process_query(self, query):
- query._attributes[("undefer", self.group)] = True
-
class AbstractRelationshipLoader(LoaderStrategy):
"""LoaderStratgies which deal with related objects."""
@@ -306,7 +282,8 @@ class AbstractRelationshipLoader(LoaderStrategy):
@log.class_logger
-@properties.RelationshipProperty._strategy_for(dict(lazy=None), dict(lazy="noload"))
+@properties.RelationshipProperty.strategy_for(lazy="noload")
+@properties.RelationshipProperty.strategy_for(lazy=None)
class NoLoader(AbstractRelationshipLoader):
"""Provide loading behavior for a :class:`.RelationshipProperty`
with "lazy=None".
@@ -322,7 +299,7 @@ class NoLoader(AbstractRelationshipLoader):
typecallable=self.parent_property.collection_class,
)
- def create_row_processor(self, context, path, mapper, row, adapter):
+ def create_row_processor(self, context, path, loadopt, mapper, row, adapter):
def invoke_no_load(state, dict_, row):
state._initialize(self.key)
return invoke_no_load, None, None
@@ -330,7 +307,8 @@ class NoLoader(AbstractRelationshipLoader):
@log.class_logger
-@properties.RelationshipProperty._strategy_for(dict(lazy=True), dict(lazy="select"))
+@properties.RelationshipProperty.strategy_for(lazy=True)
+@properties.RelationshipProperty.strategy_for(lazy="select")
class LazyLoader(AbstractRelationshipLoader):
"""Provide loading behavior for a :class:`.RelationshipProperty`
with "lazy=True", that is loads when first accessed.
@@ -544,7 +522,8 @@ class LazyLoader(AbstractRelationshipLoader):
for pk in self.mapper.primary_key
]
- def _emit_lazyload(self, session, state, ident_key, passive):
+ @util.dependencies("sqlalchemy.orm.strategy_options")
+ def _emit_lazyload(self, strategy_options, session, state, ident_key, passive):
q = session.query(self.mapper)._adapt_all_clauses()
q = q._with_invoke_all_eagers(False)
@@ -573,7 +552,7 @@ class LazyLoader(AbstractRelationshipLoader):
if rev.direction is interfaces.MANYTOONE and \
rev._use_get and \
not isinstance(rev.strategy, LazyLoader):
- q = q.options(EagerLazyOption((rev.key,), lazy='select'))
+ q = q.options(strategy_options.Load(rev.parent).lazyload(rev.key))
lazy_clause = self.lazy_clause(state, passive=passive)
@@ -600,7 +579,7 @@ class LazyLoader(AbstractRelationshipLoader):
else:
return None
- def create_row_processor(self, context, path,
+ def create_row_processor(self, context, path, loadopt,
mapper, row, adapter):
key = self.key
if not self.is_class_level:
@@ -648,19 +627,19 @@ class LoadLazyAttribute(object):
return strategy._load_for_state(state, passive)
-@properties.RelationshipProperty._strategy_for(dict(lazy="immediate"))
+@properties.RelationshipProperty.strategy_for(lazy="immediate")
class ImmediateLoader(AbstractRelationshipLoader):
def init_class_attribute(self, mapper):
self.parent_property.\
- _get_strategy(LazyLoader).\
+ _get_strategy_by_cls(LazyLoader).\
init_class_attribute(mapper)
def setup_query(self, context, entity,
- path, adapter, column_collection=None,
+ path, loadopt, adapter, column_collection=None,
parentmapper=None, **kwargs):
pass
- def create_row_processor(self, context, path,
+ def create_row_processor(self, context, path, loadopt,
mapper, row, adapter):
def load_immediate(state, dict_, row):
state.get_impl(self.key).get(state, dict_)
@@ -669,7 +648,7 @@ class ImmediateLoader(AbstractRelationshipLoader):
@log.class_logger
-@properties.RelationshipProperty._strategy_for(dict(lazy="subquery"))
+@properties.RelationshipProperty.strategy_for(lazy="subquery")
class SubqueryLoader(AbstractRelationshipLoader):
def __init__(self, parent):
super(SubqueryLoader, self).__init__(parent)
@@ -677,11 +656,11 @@ class SubqueryLoader(AbstractRelationshipLoader):
def init_class_attribute(self, mapper):
self.parent_property.\
- _get_strategy(LazyLoader).\
+ _get_strategy_by_cls(LazyLoader).\
init_class_attribute(mapper)
def setup_query(self, context, entity,
- path, adapter,
+ path, loadopt, adapter,
column_collection=None,
parentmapper=None, **kwargs):
@@ -706,7 +685,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
# if not via query option, check for
# a cycle
- if not path.contains(context.attributes, "loaderstrategy"):
+ if not path.contains(context.attributes, "loader"):
if self.join_depth:
if path.length / 2 > self.join_depth:
return
@@ -919,7 +898,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
q = q.order_by(*eager_order_by)
return q
- def create_row_processor(self, context, path,
+ def create_row_processor(self, context, path, loadopt,
mapper, row, adapter):
if not self.parent.class_manager[self.key].impl.supports_population:
raise sa_exc.InvalidRequestError(
@@ -989,7 +968,8 @@ class SubqueryLoader(AbstractRelationshipLoader):
@log.class_logger
-@properties.RelationshipProperty._strategy_for(dict(lazy=False), dict(lazy="joined"))
+@properties.RelationshipProperty.strategy_for(lazy="joined")
+@properties.RelationshipProperty.strategy_for(lazy=False)
class JoinedLoader(AbstractRelationshipLoader):
"""Provide loading behavior for a :class:`.RelationshipProperty`
using joined eager loading.
@@ -1001,9 +981,9 @@ class JoinedLoader(AbstractRelationshipLoader):
def init_class_attribute(self, mapper):
self.parent_property.\
- _get_strategy(LazyLoader).init_class_attribute(mapper)
+ _get_strategy_by_cls(LazyLoader).init_class_attribute(mapper)
- def setup_query(self, context, entity, path, adapter, \
+ def setup_query(self, context, entity, path, loadopt, adapter, \
column_collection=None, parentmapper=None,
allow_innerjoin=True,
**kwargs):
@@ -1016,19 +996,19 @@ class JoinedLoader(AbstractRelationshipLoader):
with_polymorphic = None
- user_defined_adapter = path.get(context.attributes,
- "user_defined_eager_row_processor",
- False)
+ user_defined_adapter = self._init_user_defined_eager_proc(
+ loadopt, context) if loadopt else False
+
if user_defined_adapter is not False:
clauses, adapter, add_to_collection = \
- self._get_user_defined_adapter(
+ self._setup_query_on_user_defined_adapter(
context, entity, path, adapter,
user_defined_adapter
)
else:
# if not via query option, check for
# a cycle
- if not path.contains(context.attributes, "loaderstrategy"):
+ if not path.contains(context.attributes, "loader"):
if self.join_depth:
if path.length / 2 > self.join_depth:
return
@@ -1037,7 +1017,7 @@ class JoinedLoader(AbstractRelationshipLoader):
clauses, adapter, add_to_collection, \
allow_innerjoin = self._generate_row_adapter(
- context, entity, path, adapter,
+ context, entity, path, loadopt, adapter,
column_collection, parentmapper, allow_innerjoin
)
@@ -1072,24 +1052,74 @@ class JoinedLoader(AbstractRelationshipLoader):
"when using joined loading with with_polymorphic()."
)
- def _get_user_defined_adapter(self, context, entity,
+ def _init_user_defined_eager_proc(self, loadopt, context):
+
+ # check if the opt applies at all
+ if "eager_from_alias" not in loadopt.local_opts:
+ # nope
+ return False
+
+ path = loadopt.path.parent
+
+ # the option applies. check if the "user_defined_eager_row_processor"
+ # has been built up.
+ adapter = path.get(context.attributes,
+ "user_defined_eager_row_processor", False)
+ if adapter is not False:
+ # just return it
+ return adapter
+
+ # otherwise figure it out.
+ alias = loadopt.local_opts["eager_from_alias"]
+
+ root_mapper, prop = path[-2:]
+
+ #from .mapper import Mapper
+ #from .interfaces import MapperProperty
+ #assert isinstance(root_mapper, Mapper)
+ #assert isinstance(prop, MapperProperty)
+
+ if alias is not None:
+ if isinstance(alias, str):
+ alias = prop.target.alias(alias)
+ adapter = sql_util.ColumnAdapter(alias,
+ equivalents=prop.mapper._equivalent_columns)
+ else:
+ if path.contains(context.attributes, "path_with_polymorphic"):
+ with_poly_info = path.get(context.attributes,
+ "path_with_polymorphic")
+ adapter = orm_util.ORMAdapter(
+ with_poly_info.entity,
+ equivalents=prop.mapper._equivalent_columns)
+ else:
+ adapter = context.query._polymorphic_adapters.get(prop.mapper, None)
+ path.set(context.attributes,
+ "user_defined_eager_row_processor",
+ adapter)
+
+ return adapter
+
+ def _setup_query_on_user_defined_adapter(self, context, entity,
path, adapter, user_defined_adapter):
- adapter = entity._get_entity_clauses(context.query, context)
- if adapter and user_defined_adapter:
- user_defined_adapter = user_defined_adapter.wrap(adapter)
- path.set(context.attributes, "user_defined_eager_row_processor",
- user_defined_adapter)
- elif adapter:
- user_defined_adapter = adapter
- path.set(context.attributes, "user_defined_eager_row_processor",
- user_defined_adapter)
+ # apply some more wrapping to the "user defined adapter"
+ # if we are setting up the query for SQL render.
+ adapter = entity._get_entity_clauses(context.query, context)
+
+ if adapter and user_defined_adapter:
+ user_defined_adapter = user_defined_adapter.wrap(adapter)
+ path.set(context.attributes, "user_defined_eager_row_processor",
+ user_defined_adapter)
+ elif adapter:
+ user_defined_adapter = adapter
+ path.set(context.attributes, "user_defined_eager_row_processor",
+ user_defined_adapter)
- add_to_collection = context.primary_columns
- return user_defined_adapter, adapter, add_to_collection
+ add_to_collection = context.primary_columns
+ return user_defined_adapter, adapter, add_to_collection
def _generate_row_adapter(self,
- context, entity, path, adapter,
+ context, entity, path, loadopt, adapter,
column_collection, parentmapper, allow_innerjoin
):
with_poly_info = path.get(
@@ -1112,9 +1142,12 @@ class JoinedLoader(AbstractRelationshipLoader):
if self.parent_property.direction != interfaces.MANYTOONE:
context.multi_row_eager_loaders = True
- innerjoin = allow_innerjoin and path.get(context.attributes,
- "eager_join_type",
- self.parent_property.innerjoin)
+ innerjoin = allow_innerjoin and (
+ loadopt.local_opts.get(
+ 'innerjoin', self.parent_property.innerjoin)
+ if loadopt is not None
+ else self.parent_property.innerjoin
+ )
if not innerjoin:
# if this is an outer join, all eager joins from
# here must also be outer joins
@@ -1221,10 +1254,10 @@ class JoinedLoader(AbstractRelationshipLoader):
)
)
- def _create_eager_adapter(self, context, row, adapter, path):
- user_defined_adapter = path.get(context.attributes,
- "user_defined_eager_row_processor",
- False)
+ def _create_eager_adapter(self, context, row, adapter, path, loadopt):
+ user_defined_adapter = self._init_user_defined_eager_proc(
+ loadopt, context) if loadopt else False
+
if user_defined_adapter is not False:
decorator = user_defined_adapter
# user defined eagerloads are part of the "primary"
@@ -1247,7 +1280,7 @@ class JoinedLoader(AbstractRelationshipLoader):
# processor, will cause a degrade to lazy
return False
- def create_row_processor(self, context, path, mapper, row, adapter):
+ def create_row_processor(self, context, path, loadopt, mapper, row, adapter):
if not self.parent.class_manager[self.key].impl.supports_population:
raise sa_exc.InvalidRequestError(
"'%s' does not support object "
@@ -1259,7 +1292,7 @@ class JoinedLoader(AbstractRelationshipLoader):
eager_adapter = self._create_eager_adapter(
context,
row,
- adapter, our_path)
+ adapter, our_path, loadopt)
if eager_adapter is not False:
key = self.key
@@ -1276,9 +1309,9 @@ class JoinedLoader(AbstractRelationshipLoader):
return self._create_collection_loader(context, key, _instance)
else:
return self.parent_property.\
- _get_strategy(LazyLoader).\
+ _get_strategy_by_cls(LazyLoader).\
create_row_processor(
- context, path,
+ context, path, loadopt,
mapper, row, adapter)
def _create_collection_loader(self, context, key, _instance):
@@ -1339,84 +1372,6 @@ class JoinedLoader(AbstractRelationshipLoader):
None, load_scalar_from_joined_exec
-class EagerLazyOption(StrategizedOption):
- def __init__(self, key, lazy=True, chained=False,
- propagate_to_loaders=True
- ):
- if isinstance(key[0], str) and key[0] == '*':
- if len(key) != 1:
- raise sa_exc.ArgumentError(
- "Wildcard identifier '*' must "
- "be specified alone.")
- key = ("relationship:*",)
- propagate_to_loaders = False
- super(EagerLazyOption, self).__init__(key)
- self.lazy = lazy
- self.chained = chained
- self.propagate_to_loaders = propagate_to_loaders
- self.strategy_cls = properties.RelationshipProperty._strategy_lookup(lazy=lazy)
-
- def get_strategy_class(self):
- return self.strategy_cls
-
-
-class EagerJoinOption(PropertyOption):
-
- def __init__(self, key, innerjoin, chained=False):
- super(EagerJoinOption, self).__init__(key)
- self.innerjoin = innerjoin
- self.chained = chained
-
- def process_query_property(self, query, paths):
- if self.chained:
- for path in paths:
- path.set(query._attributes, "eager_join_type", self.innerjoin)
- else:
- paths[-1].set(query._attributes, "eager_join_type", self.innerjoin)
-
-
-class LoadEagerFromAliasOption(PropertyOption):
-
- def __init__(self, key, alias=None, chained=False):
- super(LoadEagerFromAliasOption, self).__init__(key)
- if alias is not None:
- if not isinstance(alias, str):
- info = inspect(alias)
- alias = info.selectable
- self.alias = alias
- self.chained = chained
-
- def process_query_property(self, query, paths):
- if self.chained:
- for path in paths[0:-1]:
- (root_mapper, prop) = path.path[-2:]
- adapter = query._polymorphic_adapters.get(prop.mapper, None)
- path.setdefault(query._attributes,
- "user_defined_eager_row_processor",
- adapter)
-
- root_mapper, prop = paths[-1].path[-2:]
- if self.alias is not None:
- if isinstance(self.alias, str):
- self.alias = prop.target.alias(self.alias)
- paths[-1].set(query._attributes,
- "user_defined_eager_row_processor",
- sql_util.ColumnAdapter(self.alias,
- equivalents=prop.mapper._equivalent_columns)
- )
- else:
- if paths[-1].contains(query._attributes, "path_with_polymorphic"):
- with_poly_info = paths[-1].get(query._attributes,
- "path_with_polymorphic")
- adapter = orm_util.ORMAdapter(
- with_poly_info.entity,
- equivalents=prop.mapper._equivalent_columns)
- else:
- adapter = query._polymorphic_adapters.get(prop.mapper, None)
- paths[-1].set(query._attributes,
- "user_defined_eager_row_processor",
- adapter)
-
def single_parent_validator(desc, prop):
def _do_check(state, value, oldvalue, initiator):
diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py
new file mode 100644
index 000000000..5f7eb2c25
--- /dev/null
+++ b/lib/sqlalchemy/orm/strategy_options.py
@@ -0,0 +1,893 @@
+# orm/strategy_options.py
+# Copyright (C) 2005-2013 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):
+ 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):
+ if attr.endswith(_WILDCARD_TOKEN) or attr.endswith(_DEFAULT_TOKEN):
+ if wildcard_key:
+ attr = "%s:%s" % (wildcard_key, attr)
+ self.propagate_to_loaders = False
+ 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(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)
+
+
+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):
+ attr = "%s:%s" % (wildcard_key, attr)
+ self.propagate_to_loaders = False
+
+ 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 _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 _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 _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.")
+
+ 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))
+
+ :func:`.orm.joinedload` also accepts a keyword argument `innerjoin=True` which
+ indicates using an inner join instead of an outer::
+
+ query(Order).options(joinedload(Order.user, innerjoin=True))
+
+ .. 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
+ :class:`.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`
+
+ """
+ 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`
+
+ """
+ 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.
+
+ """
+ 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`
+
+ """
+ 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, *addl_attrs):
+ """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, ) + addl_attrs,
+ {"deferred": True, "instrument": True}
+ )
+
+
+@defer._add_unbound_fn
+def defer(*key):
+ return _UnboundLoad._from_keys(_UnboundLoad.defer, key, False, {})
+
+@loader_option()
+def undefer(loadopt, key, *addl_attrs):
+ """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, ) + addl_attrs,
+ {"deferred": False, "instrument": True}
+ )
+
+@undefer._add_unbound_fn
+def undefer(*key):
+ return _UnboundLoad._from_keys(_UnboundLoad.undefer, key, 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)
+