Commits

Anonymous committed 59812b8

- 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]

  • Participants
  • Parent commits 2c04165

Comments (0)

Files changed (7)

 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

File lib/sqlalchemy/orm/__init__.py

     'eagerload',
     'eagerload_all',
     'extension',
+    'immediateload',
     'join',
     'joinedload',
     'joinedload_all',
       ``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
         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)
 
     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)
 
     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.

File lib/sqlalchemy/orm/dynamic.py

         )
 
     def create_row_processor(self, selectcontext, path, mapper, row, adapter):
-        return (None, None)
+        return None, None, None
 
 log.class_logger(DynaLoader)
 

File lib/sqlalchemy/orm/interfaces.py

         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()

File lib/sqlalchemy/orm/mapper.py

                     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:
             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."""

File lib/sqlalchemy/orm/properties.py

         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):

File lib/sqlalchemy/orm/strategies.py

             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."""
             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)
 
             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)
     
                 # 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'):
     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)
         
                 # 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):
             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()
         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)
 
                 state.get_impl(self.key).\
                         set_committed_value(state, dict_, scalar)
             
-        return execute, None
+        return execute, None, None
 
 log.class_logger(SubqueryLoader)
 
                             "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(
                                                 '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).\
         return LazyLoader
     elif identifier == 'subquery':
         return SubqueryLoader
+    elif identifier == 'immediate':
+        return ImmediateLoader
     else:
         return LazyLoader