- Added a new "lazyload" option "immediateload".

Issues the usual "lazy" load operation automatically
as the object is populated.   The use case
here is when loading objects to be placed in
an offline cache, or otherwise used after
the session isn't available, and straight 'select'
loading, not 'joined' or 'subquery', is desired.
[ticket:1914]
This commit is contained in:
Mike Bayer
2010-10-15 11:59:02 -04:00
parent 35508a30d7
commit 295fd90125
7 changed files with 80 additions and 58 deletions
+9
View File
@@ -6,6 +6,15 @@ CHANGES
0.6.5
=====
- orm
- Added a new "lazyload" option "immediateload".
Issues the usual "lazy" load operation automatically
as the object is populated. The use case
here is when loading objects to be placed in
an offline cache, or otherwise used after
the session isn't available, and straight 'select'
loading, not 'joined' or 'subquery', is desired.
[ticket:1914]
- Fixed recursion bug which could occur when moving
an object from one reference to another, with
backrefs involved, where the initiating parent
+23 -4
View File
@@ -84,6 +84,7 @@ __all__ = (
'eagerload',
'eagerload_all',
'extension',
'immediateload',
'join',
'joinedload',
'joinedload_all',
@@ -335,7 +336,12 @@ def relationship(argument, secondary=None, **kwargs):
``select``. Values include:
* ``select`` - items should be loaded lazily when the property is first
accessed, using a separate SELECT statement.
accessed, using a separate SELECT statement, or identity map
fetch for simple many-to-one references.
* ``immediate`` - items should be loaded as the parents are loaded,
using a separate SELECT statement, or identity map fetch for
simple many-to-one references. (new as of 0.6.5)
* ``joined`` - items should be loaded "eagerly" in the same query as
that of the parent, using a JOIN or LEFT OUTER JOIN. Whether
@@ -1122,7 +1128,7 @@ def subqueryload_all(*keys):
query.options(subqueryload_all(User.orders, Order.items,
Item.keywords))
See also: :func:`joinedload_all`, :func:`lazyload`
See also: :func:`joinedload_all`, :func:`lazyload`, :func:`immediateload`
"""
return strategies.EagerLazyOption(keys, lazy="subquery", chained=True)
@@ -1134,7 +1140,7 @@ def lazyload(*keys):
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
See also: :func:`eagerload`, :func:`subqueryload`
See also: :func:`eagerload`, :func:`subqueryload`, :func:`immediateload`
"""
return strategies.EagerLazyOption(keys, lazy=True)
@@ -1145,11 +1151,24 @@ def noload(*keys):
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
See also: :func:`lazyload`, :func:`eagerload`, :func:`subqueryload`
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 into an immediate load.
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
See also: :func:`lazyload`, :func:`eagerload`, :func:`subqueryload`
New as of verison 0.6.5.
"""
return strategies.EagerLazyOption(keys, lazy='immediate')
def contains_alias(alias):
"""Return a ``MapperOption`` that will indicate to the query that
the main table has been aliased.
+1 -1
View File
@@ -36,7 +36,7 @@ class DynaLoader(strategies.AbstractRelationshipLoader):
)
def create_row_processor(self, selectcontext, path, mapper, row, adapter):
return (None, None)
return None, None, None
log.class_logger(DynaLoader)
+2 -32
View File
@@ -436,38 +436,8 @@ class MapperProperty(object):
pass
def create_row_processor(self, selectcontext, path, mapper, row, adapter):
"""Return a 2-tuple consiting of two row processing functions and
an instance post-processing function.
Input arguments are the query.SelectionContext and the *first*
applicable row of a result set obtained within
query.Query.instances(), called only the first time a particular
mapper's populate_instance() method is invoked for the overall result.
The settings contained within the SelectionContext as well as the
columns present in the row (which will be the same columns present in
all rows) are used to determine the presence and behavior of the
returned callables. The callables will then be used to process all
rows and instances.
Callables are of the following form::
def new_execute(state, dict_, row, isnew):
# process incoming instance state and given row.
# the instance is
# "new" and was just created upon receipt of this row.
"isnew" indicates if the instance was newly created as a
result of reading this row
def existing_execute(state, dict_, row):
# process incoming instance state and given row. the
# instance is
# "existing" and was created based on a previous row.
return (new_execute, existing_execute)
Either of the three tuples can be ``None`` in which case no function
is called.
"""Return a 3-tuple consisting of three row processing functions.
"""
raise NotImplementedError()
+14 -9
View File
@@ -2135,10 +2135,11 @@ class Mapper(object):
state.load_path = load_path
if not new_populators:
new_populators[:], existing_populators[:] = \
self._populators(context, path, row,
adapter)
self._populators(context, path, row, adapter,
new_populators,
existing_populators
)
if isnew:
populators = new_populators
else:
@@ -2309,20 +2310,24 @@ class Mapper(object):
return instance
return _instance
def _populators(self, context, path, row, adapter):
def _populators(self, context, path, row, adapter,
new_populators, existing_populators):
"""Produce a collection of attribute level row processor callables."""
new_populators, existing_populators = [], []
delayed_populators = []
for prop in self._props.itervalues():
newpop, existingpop = prop.create_row_processor(
newpop, existingpop, delayedpop = prop.create_row_processor(
context, path,
self, row, adapter)
if newpop:
new_populators.append((prop.key, newpop))
if existingpop:
existing_populators.append((prop.key, existingpop))
return new_populators, existing_populators
if delayedpop:
delayed_populators.append((prop.key, delayedpop))
if delayed_populators:
new_populators.extend(delayed_populators)
def _configure_subclass_mapper(self, context, path, adapter):
"""Produce a mapper level row processor callable factory for mappers
inheriting this one."""
+1 -1
View File
@@ -255,7 +255,7 @@ class DescriptorProperty(MapperProperty):
pass
def create_row_processor(self, selectcontext, path, mapper, row, adapter):
return (None, None)
return None, None, None
def merge(self, session, source_state, source_dict,
dest_state, dest_dict, load, _recursive):
+30 -11
View File
@@ -88,7 +88,7 @@ class UninstrumentedColumnLoader(LoaderStrategy):
column_collection.append(c)
def create_row_processor(self, selectcontext, path, mapper, row, adapter):
return None, None
return None, None, None
class ColumnLoader(LoaderStrategy):
"""Strategize the loading of a plain column-based MapperProperty."""
@@ -127,11 +127,11 @@ class ColumnLoader(LoaderStrategy):
if col is not None and col in row:
def new_execute(state, dict_, row):
dict_[key] = row[col]
return new_execute, None
return new_execute, None, None
else:
def new_execute(state, dict_, row):
state.expire_attribute_pre_commit(dict_, key)
return new_execute, None
return new_execute, None, None
log.class_logger(ColumnLoader)
@@ -184,7 +184,7 @@ class CompositeColumnLoader(ColumnLoader):
def new_execute(state, dict_, row):
dict_[key] = composite_class(*[row[c] for c in columns])
return new_execute, None
return new_execute, None, None
log.class_logger(CompositeColumnLoader)
@@ -211,7 +211,7 @@ class DeferredColumnLoader(LoaderStrategy):
# fire off on next access.
state.reset(dict_, key)
return new_execute, None
return new_execute, None, None
def init(self):
if hasattr(self.parent_property, 'composite_class'):
@@ -348,7 +348,7 @@ class NoLoader(AbstractRelationshipLoader):
def create_row_processor(self, selectcontext, path, mapper, row, adapter):
def new_execute(state, dict_, row):
state.initialize(self.key)
return new_execute, None
return new_execute, None, None
log.class_logger(NoLoader)
@@ -509,7 +509,7 @@ class LazyLoader(AbstractRelationshipLoader):
# any existing state.
state.reset(dict_, key)
return new_execute, None
return new_execute, None, None
@classmethod
def _create_lazy_clause(cls, prop, reverse_direction=False):
@@ -683,6 +683,23 @@ class LoadLazyAttribute(object):
else:
return None
class ImmediateLoader(AbstractRelationshipLoader):
def init_class_attribute(self, mapper):
self.parent_property.\
_get_strategy(LazyLoader).\
init_class_attribute(mapper)
def setup_query(self, context, entity,
path, adapter, column_collection=None,
parentmapper=None, **kwargs):
pass
def create_row_processor(self, context, path, mapper, row, adapter):
def execute(state, dict_, row):
state.get_impl(self.key).get(state, dict_)
return None, None, execute
class SubqueryLoader(AbstractRelationshipLoader):
def init(self):
super(SubqueryLoader, self).init()
@@ -859,7 +876,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
path = interfaces._reduce_path(path)
if ('subquery', path) not in context.attributes:
return None, None
return None, None, None
local_cols, remote_cols = self._local_remote_columns(self.parent_property)
@@ -903,7 +920,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
state.get_impl(self.key).\
set_committed_value(state, dict_, scalar)
return execute, None
return execute, None, None
log.class_logger(SubqueryLoader)
@@ -1156,7 +1173,7 @@ class EagerLoader(AbstractRelationshipLoader):
"Multiple rows returned with "
"uselist=False for eagerly-loaded attribute '%s' "
% self)
return new_execute, existing_execute
return new_execute, existing_execute, None
else:
def new_execute(state, dict_, row):
collection = attributes.init_state_collection(
@@ -1181,7 +1198,7 @@ class EagerLoader(AbstractRelationshipLoader):
'append_without_event')
context.attributes[(state, key)] = result_list
_instance(row, result_list)
return new_execute, existing_execute
return new_execute, existing_execute, None
else:
return self.parent_property.\
_get_strategy(LazyLoader).\
@@ -1221,6 +1238,8 @@ def factory(identifier):
return LazyLoader
elif identifier == 'subquery':
return SubqueryLoader
elif identifier == 'immediate':
return ImmediateLoader
else:
return LazyLoader