diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-06-20 15:37:59 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-06-27 23:28:01 -0400 |
| commit | f0d1a5364fa8a9585b709239f85c4092439c4cd8 (patch) | |
| tree | 23c3cd404682505f35f87ef6186530bea571103d /lib/sqlalchemy/orm | |
| parent | ec2c32bf93cc6fd60db87009643ece32c7926021 (diff) | |
| download | sqlalchemy-f0d1a5364fa8a9585b709239f85c4092439c4cd8.tar.gz | |
Add Load.options() for hierchical construction of loader options
Added new loader option method :meth:`.Load.options` which allows loader
options to be constructed hierarchically, so that many sub-options can be
applied to a particular path without needing to call :func:`.defaultload`
many times. Thanks to Alessio Bogon for the idea.
Also applies a large pass to the loader option documentation which
needed improvement.
Fixes: #4736
Change-Id: I93c453e30a20c074f27e87cf7e95b13dd3f2b494
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 12 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategy_options.py | 94 |
2 files changed, 100 insertions, 6 deletions
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 8f05a47b8..c73a8147c 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1522,13 +1522,17 @@ class Query(object): return self.add_columns(column) def options(self, *args): - """Return a new Query object, applying the given list of + """Return a new :class:`.Query` object, applying the given list of mapper options. Most supplied options regard changing how column- and - relationship-mapped attributes are loaded. See the sections - :ref:`deferred` and :doc:`/orm/loading_relationships` for reference - documentation. + relationship-mapped attributes are loaded. + + .. seealso:: + + :ref:`deferred_options` + + :ref:`relationship_loader_options` """ return self._options(False, *args) diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index ebcb101ad..d3ecb2b5b 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -62,7 +62,11 @@ class Load(Generative, MapperOption): .. seealso:: - :ref:`loading_toplevel` + :ref:`deferred_options` + + :ref:`deferred_loading_w_multiple` + + :ref:`relationship_loader_options` """ @@ -320,12 +324,59 @@ class Load(Generative, MapperOption): strategy = tuple(sorted(strategy.items())) return strategy + def _apply_to_parent(self, parent, applied, bound): + raise NotImplementedError( + "Only 'unbound' loader options may be used with the " + "Load.options() method" + ) + + @_generative + def options(self, *opts): + r"""Apply a series of options as sub-options to this :class:`.Load` + object. + + E.g.:: + + query = session.query(Author) + query = query.options( + joinedload(Author.book).options( + load_only("summary", "excerpt"), + joinedload(Book.citations).options( + joinedload(Citation.author) + ) + ) + ) + + :param \*opts: A series of loader option objects (ultimately + :class:`.Load` objects) which should be applied to the path + specified by this :class:`.Load` object. + + .. versionadded:: 1.3.6 + + .. seealso:: + + :func:`.defaultload` + + :ref:`relationship_loader_options` + + :ref:`deferred_loading_w_multiple` + + """ + apply_cache = {} + bound = not isinstance(self, _UnboundLoad) + if bound: + raise NotImplementedError( + "The options() method is currently only supported " + "for 'unbound' loader options" + ) + for opt in opts: + opt._apply_to_parent(self, apply_cache, bound) + @_generative def set_relationship_strategy( self, attr, strategy, propagate_to_loaders=True ): strategy = self._coerce_strat(strategy) - self.is_class_strategy = False self.propagate_to_loaders = propagate_to_loaders # if the path is a wildcard, this will set propagate_to_loaders=False @@ -491,6 +542,41 @@ class _UnboundLoad(Load): def _set_path_strategy(self): self._to_bind.append(self) + def _apply_to_parent(self, parent, applied, bound): + if self in applied: + return applied[self] + + cloned = self._generate() + + applied[self] = cloned + + cloned.strategy = self.strategy + if self.path: + attr = self.path[-1] + if isinstance(attr, util.string_types) and attr.endswith( + _DEFAULT_TOKEN + ): + attr = attr.split(":")[0] + ":" + _WILDCARD_TOKEN + cloned._generate_path( + parent.path + self.path[0:-1], attr, self.strategy, None + ) + + # these assertions can go away once the "sub options" API is + # mature + assert cloned.propagate_to_loaders == self.propagate_to_loaders + assert cloned.is_class_strategy == self.is_class_strategy + assert cloned.is_opts_only == self.is_opts_only + + new_to_bind = { + elem._apply_to_parent(parent, applied, bound) + for elem in self._to_bind + } + cloned._to_bind = parent._to_bind + cloned._to_bind.extend(new_to_bind) + cloned.local_opts.update(self.local_opts) + + return cloned + def _generate_path(self, path, attr, for_strategy, wildcard_key): if ( wildcard_key @@ -1325,6 +1411,10 @@ def defaultload(loadopt, attr): .. seealso:: + :meth:`.Load.options` - allows for complex hierarchical + loader option structures with less verbosity than with individual + :func:`.defaultload` directives. + :ref:`relationship_loader_options` :ref:`deferred_loading_w_multiple` |
