1. idank
  2. sqlalchemy

Commits

Mike Bayer  committed ca801a4

- Added an attribute helper method ``set_committed_value`` in
sqlalchemy.orm.attributes. Given an object, attribute name,
and value, will set the value on the object as part of its
"committed" state, i.e. state that is understood to have
been loaded from the database. Helps with the creation of
homegrown collection loaders and such.
- documented public attributes helper functions.

  • Participants
  • Parent commits bfe5b78
  • Branches default

Comments (0)

Files changed (7)

File CHANGES

View file
  • Ignore whitespace
       - Other filterings, like 
         query(A).join(A.bs).filter(B.foo=='bar'), were erroneously
         adapting "B.foo" as though it were an "A".
-      
+    
+     - Added an attribute helper method ``set_committed_value`` in 
+       sqlalchemy.orm.attributes.  Given an object, attribute name,
+       and value, will set the value on the object as part of its
+       "committed" state, i.e. state that is understood to have 
+       been loaded from the database.   Helps with the creation of 
+       homegrown collection loaders and such.
+       
 - sql
     - Fixed missing _label attribute on Function object, others
       when used in a select() with use_labels (such as when used

File doc/build/mappers.rst

View file
  • Ignore whitespace
 Setting Noload 
 ~~~~~~~~~~~~~~~
 
-
 The opposite of the dynamic relation is simply "noload", specified using ``lazy=None``:
 
 .. sourcecode:: python+sql

File doc/build/reference/orm/mapping.rst

View file
  • Ignore whitespace
 
 .. autofunction:: clear_mappers
 
+Attribute Utilities
+-------------------
+.. autofunction:: sqlalchemy.orm.attributes.del_attribute
+
+.. autofunction:: sqlalchemy.orm.attributes.get_attribute
+
+.. autofunction:: sqlalchemy.orm.attributes.get_history
+
+.. autofunction:: sqlalchemy.orm.attributes.init_collection
+
+.. function:: sqlalchemy.orm.attributes.instance_state
+
+    Return the :class:`InstanceState` for a given object.
+
+.. autofunction:: sqlalchemy.orm.attributes.is_instrumented
+
+.. function:: sqlalchemy.orm.attributes.manager_of_class
+
+    Return the :class:`ClassManager` for a given class.
+
+.. autofunction:: sqlalchemy.orm.attributes.set_attribute
+
+.. autofunction:: sqlalchemy.orm.attributes.set_committed_value
+
 Internals
 ---------
 

File lib/sqlalchemy/orm/attributes.py

View file
  • Ignore whitespace
 about stability.  Caveat emptor.
 
 This attribute is consulted by the default SQLAlchemy instrumentation
-resultion code.  If custom finders are installed in the global
+resolution code.  If custom finders are installed in the global
 instrumentation_finders list, they may or may not choose to honor this
 attribute.
 
 
         if self.extensions:
             self.fire_remove_event(state, old, None)
-            del state.dict[self.key]
-        else:
-            del state.dict[self.key]
+        del state.dict[self.key]
 
     def get_history(self, state, passive=PASSIVE_OFF):
         return History.from_attribute(
 
         if self.extensions:
             value = self.fire_replace_event(state, value, old, initiator)
-            state.dict[self.key] = value
-        else:
-            state.dict[self.key] = value
+        state.dict[self.key] = value
 
     def fire_replace_event(self, state, value, previous, initiator):
         for ext in self.extensions:
             self.added_items.remove(value)
         self.deleted_items.add(value)
 
+def _conditional_instance_state(obj):
+    if not isinstance(obj, InstanceState):
+        obj = instance_state(obj)
+    return obj
+        
+def get_history(obj, key, **kwargs):
+    """Return a History record for the given object and attribute key.
+    
+    obj is an instrumented object instance.  An InstanceState
+    is accepted directly for backwards compatibility but 
+    this usage is deprecated.
+    
+    """
+    return get_state_history(_conditional_instance_state(obj), key, **kwargs)
 
-def get_history(state, key, **kwargs):
+def get_state_history(state, key, **kwargs):
     return state.get_history(key, **kwargs)
 
 def has_parent(cls, obj, key, optimistic=False):
 def unregister_attribute(class_, key):
     manager_of_class(class_).uninstrument_attribute(key)
 
-def init_collection(state, key):
+def init_collection(obj, key):
+    """Initialize a collection attribute and return the collection adapter.
+    
+    This function is used to provide direct access to collection internals
+    for a previously unloaded attribute.  e.g.::
+        
+        collection_adapter = init_collection(someobject, 'elements')
+        for elem in values:
+            collection_adapter.append_without_event(elem)
+    
+    For an easier way to do the above, see :func:`~sqlalchemy.orm.attributes.set_committed_value`.
+    
+    obj is an instrumented object instance.  An InstanceState
+    is accepted directly for backwards compatibility but 
+    this usage is deprecated.
+    
+    """
+
+    return init_state_collection(_conditional_instance_state(obj), key)
+    
+def init_state_collection(state, key):
     """Initialize a collection attribute and return the collection adapter."""
+    
     attr = state.get_impl(key)
     user_data = attr.initialize(state)
     return attr.get_collection(state, user_data)
 
+def set_committed_value(instance, key, value):
+    """Set the value of an attribute with no history events.
+    
+    Cancels any previous history present.  The value should be 
+    a scalar value for scalar-holding attributes, or
+    an iterable for any collection-holding attribute.
+
+    This is the same underlying method used when a lazy loader
+    fires off and loads additional data from the database.
+    In particular, this method can be used by application code
+    which has loaded additional attributes or collections through
+    separate queries, which can then be attached to an instance
+    as though it were part of its original loaded state.
+    
+    """
+    state = instance_state(instance)
+    state.get_impl(key).set_committed_value(instance, key, value)
+    
 def set_attribute(instance, key, value):
+    """Set the value of an attribute, firing history events.
+    
+    This function may be used regardless of instrumentation
+    applied directly to the class, i.e. no descriptors are required.
+    Custom attribute management schemes will need to make usage
+    of this method to establish attribute state as understood
+    by SQLAlchemy.
+    
+    """
     state = instance_state(instance)
     state.get_impl(key).set(state, value, None)
 
 def get_attribute(instance, key):
+    """Get the value of an attribute, firing any callables required.
+
+    This function may be used regardless of instrumentation
+    applied directly to the class, i.e. no descriptors are required.
+    Custom attribute management schemes will need to make usage
+    of this method to make usage of attribute state as understood
+    by SQLAlchemy.
+    
+    """
     state = instance_state(instance)
     return state.get_impl(key).get(state)
 
 def del_attribute(instance, key):
+    """Delete the value of an attribute, firing history events.
+
+    This function may be used regardless of instrumentation
+    applied directly to the class, i.e. no descriptors are required.
+    Custom attribute management schemes will need to make usage
+    of this method to establish attribute state as understood
+    by SQLAlchemy.
+    
+    """
     state = instance_state(instance)
     state.get_impl(key).delete(state)
 
 def is_instrumented(instance, key):
+    """Return True if the given attribute on the given instance is instrumented
+    by the attributes package.
+    
+    This function may be used regardless of instrumentation
+    applied directly to the class, i.e. no descriptors are required.
+    
+    """
     return manager_of_class(instance.__class__).is_instrumented(key, search=True)
 
 class InstrumentationRegistry(object):
 # Create a registry singleton and prepare placeholders for lookup functions.
 
 instrumentation_registry = InstrumentationRegistry()
+
 create_manager_for_cls = None
+
 manager_of_class = None
+
 instance_state = None
+
+
 _lookup_strategy = None
 
 def _install_lookup_strategy(implementation):

File lib/sqlalchemy/orm/mapper.py

View file
  • Ignore whitespace
                             params[col._label] = mapper._get_state_attr_by_column(state, col)
                             params[col.key] = params[col._label] + 1
                             for prop in mapper._columntoproperty.itervalues():
-                                history = attributes.get_history(state, prop.key, passive=True)
+                                history = attributes.get_state_history(state, prop.key, passive=True)
                                 if history.added:
                                     hasdata = True
                         elif mapper.polymorphic_on and mapper.polymorphic_on.shares_lineage(col):
                                 continue
 
                             prop = mapper._columntoproperty[col]
-                            history = attributes.get_history(state, prop.key, passive=True)
+                            history = attributes.get_state_history(state, prop.key, passive=True)
                             if history.added:
                                 if isinstance(history.added[0], sql.ClauseElement):
                                     value_params[col] = history.added[0]

File lib/sqlalchemy/orm/strategies.py

View file
  • Ignore whitespace
                         # when self-referential eager loading is used; the same instance may be present
                         # in two distinct sets of result columns
 
-                        collection = attributes.init_collection(state, key)
+                        collection = attributes.init_state_collection(state, key)
                         appender = util.UniqueAppender(collection, 'append_without_event')
 
                         context.attributes[(state, key)] = appender

File lib/sqlalchemy/orm/unitofwork.py

View file
  • Ignore whitespace
             # if the cached lookup was "passive" and now we want non-passive, do a non-passive
             # lookup and re-cache
             if cached_passive and not passive:
-                history = attributes.get_history(state, key, passive=False)
+                history = attributes.get_state_history(state, key, passive=False)
                 self.attributes[hashkey] = (history, passive)
         else:
-            history = attributes.get_history(state, key, passive=passive)
+            history = attributes.get_state_history(state, key, passive=passive)
             self.attributes[hashkey] = (history, passive)
 
         if not history or not state.get_impl(key).uses_objects: