]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Added a new "lazyload" option "immediateload".
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 15 Oct 2010 15:59:02 +0000 (11:59 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 15 Oct 2010 15:59:02 +0000 (11:59 -0400)
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]

CHANGES
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/dynamic.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/orm/strategies.py

diff --git a/CHANGES b/CHANGES
index 1d78847145291d85d6299d7d9b370f05158cb3a9..2cf9a925462ee29a6db7eb4d68bcf244b854644f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -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
index 8b32d1a273016119998dc889bae66e94233fef4d..d4e436b3b19e80ff945e6d22c39ea52df2acc13b 100644 (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.
index c5ddaca40be7644f5def5f03d7477b9781470763..dd2c6e8960c34500978c5c906ace76653c336ac2 100644 (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)
 
index fa27859ecb22f546c1b32e0db839c5fac27b3220..c3c9c754fb003cd62d78b0fda1b810061015a32c 100644 (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()
index 378570723b3e6c0ff026f706e82d0cff5840b2f9..a14ad647a8242c4cfcd87063b831aeb4d86077c1 100644 (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."""
index 4efd2acc907bd81c8d38b41a4af8a9e5f44a1e8a..0cbbf630d4fb36a4aeb42bbdd34fb91e0476186a 100644 (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):
index 3e6b6a21f63e05ceebfd2d367e98713eed831005..60454eabc27f056c0c5755842536ae164cc023e8 100644 (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