]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Added an attribute helper method ``set_committed_value`` in
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 15 Feb 2009 20:43:14 +0000 (20:43 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 15 Feb 2009 20:43:14 +0000 (20:43 +0000)
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.

CHANGES
doc/build/mappers.rst
doc/build/reference/orm/mapping.rst
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/unitofwork.py

diff --git a/CHANGES b/CHANGES
index 1603ce65946e8552d447141a4cf0a3365995e951..ae0a2e2ff4930ae9a599b7714c72c888629ab944 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -46,7 +46,14 @@ CHANGES
       - 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
index cb770415e9b47599637c65c8fe68cc064ee071b0..89db6a0a740e79b6aaf914aa9e031c81f98ec7a7 100644 (file)
@@ -1673,7 +1673,6 @@ Note that eager/lazy loading options cannot be used in conjunction dynamic relat
 Setting Noload 
 ~~~~~~~~~~~~~~~
 
-
 The opposite of the dynamic relation is simply "noload", specified using ``lazy=None``:
 
 .. sourcecode:: python+sql
index 9605fcb10cbf5f200bd26c7a2cc0d2e22a096caa..3f3e51ef35159bb32b34bcc9a02c149a93c94387 100644 (file)
@@ -60,6 +60,30 @@ Utilities
 
 .. 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
 ---------
 
index 557a29098f944f5d4f9282424ed03c3e3453ce4d..d95acf0c7ba68afa7276a8cacfc36948af5704ca 100644 (file)
@@ -71,7 +71,7 @@ between releases.  ClassManager is not public and no guarantees are made
 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.
 
@@ -431,9 +431,7 @@ class ScalarAttributeImpl(AttributeImpl):
 
         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(
@@ -452,9 +450,7 @@ class ScalarAttributeImpl(AttributeImpl):
 
         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:
@@ -1537,8 +1533,22 @@ class PendingCollection(object):
             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):
@@ -1605,25 +1615,98 @@ def register_descriptor(class_, key, proxy_property=None, comparator=None, paren
 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):
@@ -1708,9 +1791,14 @@ 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):
index 1cd8e055b013e2fcdd911871403c862182601188..81d5404d77b54062f80561ccc9fc912dd3a497d7 100644 (file)
@@ -1290,7 +1290,7 @@ class Mapper(object):
                             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):
@@ -1302,7 +1302,7 @@ class Mapper(object):
                                 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]
index b72722e77d74335de8e128dc2f9be315c5832eeb..f46ffc44d8a9dd4c36ac1b4088be70a083d4a48b 100644 (file)
@@ -765,7 +765,7 @@ class EagerLoader(AbstractRelationLoader):
                         # 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
index 61c58b2499a45f369bceae28c3d1ac5662b5a4b0..8ad88982024fbbeae79b20f3abe3ba46397e4e37 100644 (file)
@@ -107,10 +107,10 @@ class UOWTransaction(object):
             # 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: