]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- split ScalarInstrumentedAttribute into a "scalar" and an "object" version.
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 31 Oct 2007 19:11:22 +0000 (19:11 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 31 Oct 2007 19:11:22 +0000 (19:11 +0000)
The "object" version loads the existing value on set/del, fires events,
 and handles trackparent operations; the "scalar" version does not.
- column loaders now use the "scalar" version of InstrumentedAttribute, so that
event handlers etc. don't fire off for regular column attribute operations.
- some adjustments to AttributeHistory to work properly for non-loaded attributes
- deferred column attributes no longer trigger a load operation when the
attribute is assigned to.  in those cases, the newly assigned
value will be present in the flushes' UPDATE statement unconditionally.

CHANGES
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/strategies.py
test/orm/attributes.py
test/orm/collection.py
test/orm/unitofwork.py

diff --git a/CHANGES b/CHANGES
index 00d16e97c10135ccfc4b055e0e5c700b079966b2..e7501fd6741002ec857f0a404ec93723c0a4e046 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -20,6 +20,10 @@ CHANGES
 
 - sqlite will reflect "DECIMAL" as a numeric column.
 
+- Made access dao detection more reliable [ticket:828]
+
+- Removed unused util.hash().
+
 - fixed INSERT statements w.r.t. primary key columns that have SQL-expression
   based default generators on them; SQL expression executes inline as normal
   but will not trigger a "postfetch" condition for the column, for those DB's
@@ -29,41 +33,42 @@ CHANGES
   'preexecute_pk_sequences'.  An attribute proxy is in place for out-of-tree
   dialects using the old name.
 
-- de-cruftified backref configuration code, backrefs which step on existing
-  properties now raise an error [ticket:832]
-
-- improved behavior of add_property() etc., fixed [ticket:831] involving
-  synonym/deferred
+- orm:
+    - deferred column attributes no longer trigger a load operation when the
+      attribute is assigned to.  in those cases, the newly assigned
+      value will be present in the flushes' UPDATE statement unconditionally.
+      
+    - de-cruftified backref configuration code, backrefs which step on existing
+      properties now raise an error [ticket:832]
 
-- fixed clear_mappers() behavior to better clean up after itself
+    - improved behavior of add_property() etc., fixed [ticket:831] involving
+      synonym/deferred
 
-- fix to "row switch" behavior, i.e. when an INSERT/DELETE is combined into a
-  single UPDATE; many-to-many relations on the parent object update properly. 
-  [ticket:841]
+    - fixed clear_mappers() behavior to better clean up after itself
 
-- it's an error to session.save() an object which is already persistent
-  [ticket:840]
+    - fix to "row switch" behavior, i.e. when an INSERT/DELETE is combined into a
+      single UPDATE; many-to-many relations on the parent object update properly. 
+      [ticket:841]
 
-- behavior of query.options() is now fully based on paths, i.e. an option
-  such as eagerload_all('x.y.z.y.x') will apply eagerloading to only
-  those paths, i.e. and not 'x.y.x'; eagerload('children.children') applies
-  only to exactly two-levels deep, etc. [ticket:777]
+    - it's an error to session.save() an object which is already persistent
+      [ticket:840]
 
-- Made access dao detection more reliable [ticket:828]
-
-- Removed unused util.hash().
+    - behavior of query.options() is now fully based on paths, i.e. an option
+      such as eagerload_all('x.y.z.y.x') will apply eagerloading to only
+      those paths, i.e. and not 'x.y.x'; eagerload('children.children') applies
+      only to exactly two-levels deep, etc. [ticket:777]
 
-- Fixed __hash__ for association proxy- these collections are unhashable,
-  just like their mutable Python counterparts.
+    - Fixed __hash__ for association proxy- these collections are unhashable,
+      just like their mutable Python counterparts.
 
-- Fixed a truncation error when re-assigning a subset of a collection
-  (obj.relation = obj.relation[1:]) [ticket:834]
+    - Fixed a truncation error when re-assigning a subset of a collection
+      (obj.relation = obj.relation[1:]) [ticket:834]
 
-- Added proxying of save_or_update, __contains__ and __iter__ methods for
-  scoped sessions.
+    - Added proxying of save_or_update, __contains__ and __iter__ methods for
+      scoped sessions.
 
-- session.update() raises an error when updating an instance that is already
-  in the session with a different identity.
+    - session.update() raises an error when updating an instance that is already
+      in the session with a different identity.
 
 0.4.0
 -----
index 6d9c092a6b42d653933e760d95d877b065862651..1855a24b023b3c5c5648bbc0ffcc191dd45a39ef 100644 (file)
@@ -308,15 +308,11 @@ class AttributeImpl(object):
         for ext in self.extensions:
             ext.set(obj, value, previous, initiator or self)
 
-
-        
 class ScalarAttributeImpl(AttributeImpl):
-    """represents a scalar-holding InstrumentedAttribute."""
-    
-    def __init__(self, class_, manager, key, callable_, trackparent=False, extension=None, copy_function=None, compare_function=None, mutable_scalars=False, **kwargs):
+    """represents a scalar value-holding InstrumentedAttribute."""
+    def __init__(self, class_, manager, key, callable_, copy_function=None, compare_function=None, mutable_scalars=False, **kwargs):
         super(ScalarAttributeImpl, self).__init__(class_, manager, key,
-          callable_, trackparent=trackparent, extension=extension,
-          compare_function=compare_function, mutable_scalars=mutable_scalars, **kwargs)
+          callable_, compare_function=compare_function, mutable_scalars=mutable_scalars, **kwargs)
 
         if copy_function is None:
             copy_function = self.__copy
@@ -328,9 +324,8 @@ class ScalarAttributeImpl(AttributeImpl):
         return item
 
     def delete(self, state):
-        old = self.get(state)
         del state.dict[self.key]
-        self.fire_remove_event(state, old, self)
+        state.modified=True
 
     def check_mutable_modified(self, state):
         if self.mutable_scalars:
@@ -358,12 +353,47 @@ class ScalarAttributeImpl(AttributeImpl):
         if state.trigger:
             state.call_trigger()
 
-        old = self.get(state)
         state.dict[self.key] = value
-        self.fire_replace_event(state, value, old, initiator)
+        state.modified=True
 
     type = property(lambda self: self.property.columns[0].type)
 
+
+class ScalarObjectAttributeImpl(ScalarAttributeImpl):
+    """represents a scalar class-instance holding InstrumentedAttribute.
+    
+    Adds events to delete/set operations.
+    """
+    
+    def __init__(self, class_, manager, key, callable_, trackparent=False, extension=None, copy_function=None, compare_function=None, mutable_scalars=False, **kwargs):
+        super(ScalarObjectAttributeImpl, self).__init__(class_, manager, key,
+          callable_, trackparent=trackparent, extension=extension,
+          compare_function=compare_function, mutable_scalars=mutable_scalars, **kwargs)
+
+    def delete(self, state):
+        old = self.get(state)
+        del state.dict[self.key]
+        self.fire_remove_event(state, old, self)
+
+    def set(self, state, value, initiator):
+        """Set a value on the given object.
+
+        `initiator` is the ``InstrumentedAttribute`` that initiated the
+        ``set()` operation and is used to control the depth of a circular
+        setter operation.
+        """
+
+        if initiator is self:
+            return
+
+        # if an instance-wide "trigger" was set, call that
+        if state.trigger:
+            state.call_trigger()
+
+        old = self.get(state)
+        state.dict[self.key] = value
+        self.fire_replace_event(state, value, old, initiator)
+
         
 class CollectionAttributeImpl(AttributeImpl):
     """A collection-holding attribute that instruments changes in membership.
@@ -766,6 +796,8 @@ class AttributeHistory(object):
     particular instance.
     """
 
+    NO_VALUE = object()
+    
     def __init__(self, attr, state, current, passive=False):
         self.attr = attr
 
@@ -773,13 +805,16 @@ class AttributeHistory(object):
         # the 'current' value, this "original" was also populated just
         # now as well (therefore we have to get it second)
         if state.committed_state:
-            original = state.committed_state.get(attr.key, None)
+            original = state.committed_state.get(attr.key, NO_VALUE)
         else:
-            original = None
+            original = NO_VALUE
 
         if hasattr(attr, 'get_collection'):
             self._current = current
-            s = util.Set(original or [])
+            if original is NO_VALUE:
+                s = util.Set([])
+            else:
+                s = util.Set(original)
             self._added_items = []
             self._unchanged_items = []
             self._deleted_items = []
@@ -801,7 +836,7 @@ class AttributeHistory(object):
                 self._deleted_items = []
             else:
                 self._added_items = [current]
-                if original is not None:
+                if original is not NO_VALUE and original is not None:
                     self._deleted_items = [original]
                 else:
                     self._deleted_items = []
@@ -979,7 +1014,7 @@ class AttributeManager(object):
 
         getattr(obj.__class__, key).impl.set_callable(obj._state, callable_, clear=clear)
 
-    def _create_prop(self, class_, key, uselist, callable_, typecallable, **kwargs):
+    def _create_prop(self, class_, key, uselist, callable_, typecallable, useobject, **kwargs):
         """Create a scalar property object, defaulting to
         ``InstrumentedAttribute``, which will communicate change
         events back to this ``AttributeManager``.
@@ -993,6 +1028,9 @@ class AttributeManager(object):
                                                    callable_,
                                                    typecallable,
                                                    **kwargs)
+        elif useobject:
+            return ScalarObjectAttributeImpl(class_, self, key, callable_,
+                                               **kwargs)
         else:
             return ScalarAttributeImpl(class_, self, key, callable_,
                                                **kwargs)
@@ -1070,7 +1108,7 @@ class AttributeManager(object):
         self._inherited_attribute_cache.pop(class_,None)
         self._noninherited_attribute_cache.pop(class_,None)
         
-    def register_attribute(self, class_, key, uselist, callable_=None, **kwargs):
+    def register_attribute(self, class_, key, uselist, useobject, callable_=None, **kwargs):
         """Register an attribute at the class level to be instrumented
         for all instances of the class.
         """
@@ -1084,7 +1122,7 @@ class AttributeManager(object):
         if isinstance(typecallable, InstrumentedAttribute):
             typecallable = None
         comparator = kwargs.pop('comparator', None)
-        setattr(class_, key, InstrumentedAttribute(self._create_prop(class_, key, uselist, callable_,
+        setattr(class_, key, InstrumentedAttribute(self._create_prop(class_, key, uselist, callable_, useobject=useobject,
                                            typecallable=typecallable, **kwargs), comparator=comparator))
 
     def set_raw_value(self, instance, key, value):
index 1c1813311667af1fa3ac2fcb07d344562548dc2a..f643302897ca9b7e94119a97c7787b302459b8c9 100644 (file)
@@ -51,12 +51,12 @@ class ColumnLoader(LoaderStrategy):
                     return False
             else:
                 return True
-        sessionlib.attribute_manager.register_attribute(self.parent.class_, self.key, uselist=False, copy_function=copy, compare_function=compare, mutable_scalars=True, comparator=self.parent_property.comparator)
+        sessionlib.attribute_manager.register_attribute(self.parent.class_, self.key, uselist=False, useobject=False, copy_function=copy, compare_function=compare, mutable_scalars=True, comparator=self.parent_property.comparator)
 
     def _init_scalar_attribute(self):
         self.logger.info("register managed attribute %s on class %s" % (self.key, self.parent.class_.__name__))
         coltype = self.columns[0].type
-        sessionlib.attribute_manager.register_attribute(self.parent.class_, self.key, uselist=False, copy_function=coltype.copy_value, compare_function=coltype.compare_values, mutable_scalars=self.columns[0].type.is_mutable(), comparator=self.parent_property.comparator)
+        sessionlib.attribute_manager.register_attribute(self.parent.class_, self.key, uselist=False, useobject=False, copy_function=coltype.copy_value, compare_function=coltype.compare_values, mutable_scalars=self.columns[0].type.is_mutable(), comparator=self.parent_property.comparator)
         
     def create_row_processor(self, selectcontext, mapper, row):
         if self.is_composite:
@@ -160,7 +160,7 @@ class DeferredColumnLoader(LoaderStrategy):
     def init_class_attribute(self):
         self.is_class_level = True
         self.logger.info("register managed attribute %s on class %s" % (self.key, self.parent.class_.__name__))
-        sessionlib.attribute_manager.register_attribute(self.parent.class_, self.key, uselist=False, callable_=self.setup_loader, copy_function=self.columns[0].type.copy_value, compare_function=self.columns[0].type.compare_values, mutable_scalars=self.columns[0].type.is_mutable(), comparator=self.parent_property.comparator)
+        sessionlib.attribute_manager.register_attribute(self.parent.class_, self.key, uselist=False, useobject=False, callable_=self.setup_loader, copy_function=self.columns[0].type.copy_value, compare_function=self.columns[0].type.compare_values, mutable_scalars=self.columns[0].type.is_mutable(), comparator=self.parent_property.comparator)
 
     def setup_query(self, context, **kwargs):
         if self.group is not None and context.attributes.get(('undefer', self.group), False):
@@ -252,7 +252,7 @@ class AbstractRelationLoader(LoaderStrategy):
         
     def _register_attribute(self, class_, callable_=None, **kwargs):
         self.logger.info("register managed %s attribute %s on class %s" % ((self.uselist and "list-holding" or "scalar"), self.key, self.parent.class_.__name__))
-        sessionlib.attribute_manager.register_attribute(class_, self.key, uselist = self.uselist, extension=self.attributeext, cascade=self.cascade,  trackparent=True, typecallable=self.parent_property.collection_class, callable_=callable_, comparator=self.parent_property.comparator, **kwargs)
+        sessionlib.attribute_manager.register_attribute(class_, self.key, uselist=self.uselist, useobject=True, extension=self.attributeext, cascade=self.cascade,  trackparent=True, typecallable=self.parent_property.collection_class, callable_=callable_, comparator=self.parent_property.comparator, **kwargs)
 
 class DynaLoader(AbstractRelationLoader):
     def init_class_attribute(self):
index 0fd325bb2f22d7fdc632e166d4fe787a9f0abe9a..930bfa57e20a28692bc6f60b895dcbce64df2862 100644 (file)
@@ -16,9 +16,9 @@ class AttributesTest(PersistTest):
         class User(object):pass
         manager = attributes.AttributeManager()
         manager.register_class(User)
-        manager.register_attribute(User, 'user_id', uselist = False)
-        manager.register_attribute(User, 'user_name', uselist = False)
-        manager.register_attribute(User, 'email_address', uselist = False)
+        manager.register_attribute(User, 'user_id', uselist = False, useobject=False)
+        manager.register_attribute(User, 'user_name', uselist = False, useobject=False)
+        manager.register_attribute(User, 'email_address', uselist = False, useobject=False)
         
         u = User()
         print repr(u.__dict__)
@@ -47,16 +47,16 @@ class AttributesTest(PersistTest):
         manager = attributes.AttributeManager()
         manager.register_class(MyTest)
         manager.register_class(MyTest2)
-        manager.register_attribute(MyTest, 'user_id', uselist = False)
-        manager.register_attribute(MyTest, 'user_name', uselist = False)
-        manager.register_attribute(MyTest, 'email_address', uselist = False)
-        manager.register_attribute(MyTest2, 'a', uselist = False)
-        manager.register_attribute(MyTest2, 'b', uselist = False)
+        manager.register_attribute(MyTest, 'user_id', uselist = False, useobject=False)
+        manager.register_attribute(MyTest, 'user_name', uselist = False, useobject=False)
+        manager.register_attribute(MyTest, 'email_address', uselist = False, useobject=False)
+        manager.register_attribute(MyTest2, 'a', uselist = False, useobject=False)
+        manager.register_attribute(MyTest2, 'b', uselist = False, useobject=False)
         # shouldnt be pickling callables at the class level
         def somecallable(*args):
             return None
         attr_name = 'mt2'
-        manager.register_attribute(MyTest, attr_name, uselist = True, trackparent=True, callable_=somecallable)
+        manager.register_attribute(MyTest, attr_name, uselist = True, trackparent=True, callable_=somecallable, useobject=True)
 
         o = MyTest()
         o.mt2.append(MyTest2())
@@ -109,11 +109,11 @@ class AttributesTest(PersistTest):
         manager = attributes.AttributeManager()
         manager.register_class(User)
         manager.register_class(Address)
-        manager.register_attribute(User, 'user_id', uselist = False)
-        manager.register_attribute(User, 'user_name', uselist = False)
-        manager.register_attribute(User, 'addresses', uselist = True)
-        manager.register_attribute(Address, 'address_id', uselist = False)
-        manager.register_attribute(Address, 'email_address', uselist = False)
+        manager.register_attribute(User, 'user_id', uselist = False, useobject=False)
+        manager.register_attribute(User, 'user_name', uselist = False, useobject=False)
+        manager.register_attribute(User, 'addresses', uselist = True, useobject=True)
+        manager.register_attribute(Address, 'address_id', uselist = False, useobject=False)
+        manager.register_attribute(Address, 'email_address', uselist = False, useobject=False)
         
         u = User()
         print repr(u.__dict__)
@@ -152,8 +152,8 @@ class AttributesTest(PersistTest):
         manager = attributes.AttributeManager()
         manager.register_class(Student)
         manager.register_class(Course)
-        manager.register_attribute(Student, 'courses', uselist=True, extension=attributes.GenericBackrefExtension('students'))
-        manager.register_attribute(Course, 'students', uselist=True, extension=attributes.GenericBackrefExtension('courses'))
+        manager.register_attribute(Student, 'courses', uselist=True, extension=attributes.GenericBackrefExtension('students'), useobject=True)
+        manager.register_attribute(Course, 'students', uselist=True, extension=attributes.GenericBackrefExtension('courses'), useobject=True)
         
         s = Student()
         c = Course()
@@ -179,8 +179,8 @@ class AttributesTest(PersistTest):
 
         manager.register_class(Post)
         manager.register_class(Blog)
-        manager.register_attribute(Post, 'blog', uselist=False, extension=attributes.GenericBackrefExtension('posts'), trackparent=True)
-        manager.register_attribute(Blog, 'posts', uselist=True, extension=attributes.GenericBackrefExtension('blog'), trackparent=True)
+        manager.register_attribute(Post, 'blog', uselist=False, extension=attributes.GenericBackrefExtension('posts'), trackparent=True, useobject=True)
+        manager.register_attribute(Blog, 'posts', uselist=True, extension=attributes.GenericBackrefExtension('blog'), trackparent=True, useobject=True)
         b = Blog()
         (p1, p2, p3) = (Post(), Post(), Post())
         b.posts.append(p1)
@@ -204,8 +204,8 @@ class AttributesTest(PersistTest):
         class Jack(object):pass
         manager.register_class(Port)
         manager.register_class(Jack)
-        manager.register_attribute(Port, 'jack', uselist=False, extension=attributes.GenericBackrefExtension('port'))
-        manager.register_attribute(Jack, 'port', uselist=False, extension=attributes.GenericBackrefExtension('jack'))
+        manager.register_attribute(Port, 'jack', uselist=False, extension=attributes.GenericBackrefExtension('port'), useobject=True)
+        manager.register_attribute(Jack, 'port', uselist=False, extension=attributes.GenericBackrefExtension('jack'), useobject=True)
         p = Port()
         j = Jack()
         p.jack = j
@@ -225,8 +225,8 @@ class AttributesTest(PersistTest):
         manager.register_class(Blog)
         
         # set up instrumented attributes with backrefs    
-        manager.register_attribute(Post, 'blog', uselist=False, extension=attributes.GenericBackrefExtension('posts'), trackparent=True)
-        manager.register_attribute(Blog, 'posts', uselist=True, extension=attributes.GenericBackrefExtension('blog'), trackparent=True)
+        manager.register_attribute(Post, 'blog', uselist=False, extension=attributes.GenericBackrefExtension('posts'), trackparent=True, useobject=True)
+        manager.register_attribute(Blog, 'posts', uselist=True, extension=attributes.GenericBackrefExtension('blog'), trackparent=True, useobject=True)
 
         # create objects as if they'd been freshly loaded from the database (without history)
         b = Blog()
@@ -268,9 +268,9 @@ class AttributesTest(PersistTest):
         def func3():
             print "func3"
             return "this is the shared attr"
-        manager.register_attribute(Foo, 'element', uselist=False, callable_=lambda o:func1)
-        manager.register_attribute(Foo, 'element2', uselist=False, callable_=lambda o:func3)
-        manager.register_attribute(Bar, 'element', uselist=False, callable_=lambda o:func2)
+        manager.register_attribute(Foo, 'element', uselist=False, callable_=lambda o:func1, useobject=True)
+        manager.register_attribute(Foo, 'element2', uselist=False, callable_=lambda o:func3, useobject=True)
+        manager.register_attribute(Bar, 'element', uselist=False, callable_=lambda o:func2, useobject=True)
         
         x = Foo()
         y = Bar()
@@ -287,7 +287,7 @@ class AttributesTest(PersistTest):
         manager = attributes.AttributeManager()
         manager.register_class(Foo)
         manager.register_class(Bar)
-        manager.register_attribute(Foo, 'element', uselist=False)
+        manager.register_attribute(Foo, 'element', uselist=False, useobject=True)
         x = Bar()
         x.element = 'this is the element'
         hist = manager.get_history(x, 'element')
@@ -315,9 +315,9 @@ class AttributesTest(PersistTest):
         def func2():
             return [Bar(1), Bar(2), Bar(3)]
 
-        manager.register_attribute(Foo, 'col1', uselist=False, callable_=lambda o:func1)
-        manager.register_attribute(Foo, 'col2', uselist=True, callable_=lambda o:func2)
-        manager.register_attribute(Bar, 'id', uselist=False)
+        manager.register_attribute(Foo, 'col1', uselist=False, callable_=lambda o:func1, useobject=True)
+        manager.register_attribute(Foo, 'col2', uselist=True, callable_=lambda o:func2, useobject=True)
+        manager.register_attribute(Bar, 'id', uselist=False, useobject=True)
 
         x = Foo()
         manager.commit(x)
@@ -335,8 +335,8 @@ class AttributesTest(PersistTest):
         manager.register_class(Foo)
         manager.register_class(Bar)
         
-        manager.register_attribute(Foo, 'element', uselist=False, trackparent=True)
-        manager.register_attribute(Bar, 'element', uselist=False, trackparent=True)
+        manager.register_attribute(Foo, 'element', uselist=False, trackparent=True, useobject=True)
+        manager.register_attribute(Bar, 'element', uselist=False, trackparent=True, useobject=True)
         
         f1 = Foo()
         f2 = Foo()
@@ -359,7 +359,7 @@ class AttributesTest(PersistTest):
         class Foo(object):pass
         manager = attributes.AttributeManager()
         manager.register_class(Foo)
-        manager.register_attribute(Foo, 'element', uselist=False, copy_function=lambda x:[y for y in x], mutable_scalars=True)
+        manager.register_attribute(Foo, 'element', uselist=False, copy_function=lambda x:[y for y in x], mutable_scalars=True, useobject=False)
         x = Foo()
         x.element = ['one', 'two', 'three']    
         manager.commit(x)
@@ -369,7 +369,7 @@ class AttributesTest(PersistTest):
         manager.unregister_class(Foo)
         manager = attributes.AttributeManager()
         manager.register_class(Foo)
-        manager.register_attribute(Foo, 'element', uselist=False)
+        manager.register_attribute(Foo, 'element', uselist=False, useobject=False)
         x = Foo()
         x.element = ['one', 'two', 'three']    
         manager.commit(x)
@@ -395,11 +395,11 @@ class AttributesTest(PersistTest):
         manager = attributes.AttributeManager()
         class Foo(object):pass
         manager.register_class(Foo)
-        manager.register_attribute(Foo, "collection", uselist=True, typecallable=set)
+        manager.register_attribute(Foo, "collection", uselist=True, typecallable=set, useobject=True)
         assert isinstance(Foo().collection, set)
         
         try:
-            manager.register_attribute(Foo, "collection", uselist=True, typecallable=dict)
+            manager.register_attribute(Foo, "collection", uselist=True, typecallable=dict, useobject=True)
             assert False
         except exceptions.ArgumentError, e:
             assert str(e) == "Type InstrumentedDict must elect an appender method to be a collection class"
@@ -411,12 +411,12 @@ class AttributesTest(PersistTest):
             @collection.remover
             def remove(self, item):
                 del self[item.foo]
-        manager.register_attribute(Foo, "collection", uselist=True, typecallable=MyDict)
+        manager.register_attribute(Foo, "collection", uselist=True, typecallable=MyDict, useobject=True)
         assert isinstance(Foo().collection, MyDict)
         
         class MyColl(object):pass
         try:
-            manager.register_attribute(Foo, "collection", uselist=True, typecallable=MyColl)
+            manager.register_attribute(Foo, "collection", uselist=True, typecallable=MyColl, useobject=True)
             assert False
         except exceptions.ArgumentError, e:
             assert str(e) == "Type MyColl must elect an appender method to be a collection class"
@@ -431,7 +431,7 @@ class AttributesTest(PersistTest):
             @collection.remover
             def remove(self, item):
                 pass
-        manager.register_attribute(Foo, "collection", uselist=True, typecallable=MyColl)
+        manager.register_attribute(Foo, "collection", uselist=True, typecallable=MyColl, useobject=True)
         try:
             Foo().collection
             assert True
index 504a4d0cb770582fc6b5bcb1c6e4feea49e0a92f..4fe9a5e65318e79059b1e54a9cc1ab71afb73b21 100644 (file)
@@ -58,7 +58,7 @@ class CollectionsTest(PersistTest):
         canary = Canary()
         manager.register_class(Foo)
         manager.register_attribute(Foo, 'attr', True, extension=canary,
-                                   typecallable=typecallable)
+                                   typecallable=typecallable, useobject=True)
 
         obj = Foo()
         adapter = collections.collection_adapter(obj.attr)
@@ -96,7 +96,7 @@ class CollectionsTest(PersistTest):
         canary = Canary()
         manager.register_class(Foo)
         manager.register_attribute(Foo, 'attr', True, extension=canary,
-                                   typecallable=typecallable)
+                                   typecallable=typecallable, useobject=True)
 
         obj = Foo()
         adapter = collections.collection_adapter(obj.attr)
@@ -238,7 +238,7 @@ class CollectionsTest(PersistTest):
         canary = Canary()
         manager.register_class(Foo)
         manager.register_attribute(Foo, 'attr', True, extension=canary,
-                                   typecallable=typecallable)
+                                   typecallable=typecallable, useobject=True)
 
         obj = Foo()
         direct = obj.attr
@@ -362,7 +362,7 @@ class CollectionsTest(PersistTest):
         canary = Canary()
         manager.register_class(Foo)
         manager.register_attribute(Foo, 'attr', True, extension=canary,
-                                   typecallable=typecallable)
+                                   typecallable=typecallable, useobject=True)
 
         obj = Foo()
         adapter = collections.collection_adapter(obj.attr)
@@ -495,7 +495,7 @@ class CollectionsTest(PersistTest):
         canary = Canary()
         manager.register_class(Foo)
         manager.register_attribute(Foo, 'attr', True, extension=canary,
-                                   typecallable=typecallable)
+                                   typecallable=typecallable, useobject=True)
 
         obj = Foo()
         direct = obj.attr
@@ -600,7 +600,7 @@ class CollectionsTest(PersistTest):
         canary = Canary()
         manager.register_class(Foo)
         manager.register_attribute(Foo, 'attr', True, extension=canary,
-                                   typecallable=typecallable)
+                                   typecallable=typecallable, useobject=True)
 
         obj = Foo()
         adapter = collections.collection_adapter(obj.attr)
@@ -718,7 +718,7 @@ class CollectionsTest(PersistTest):
         canary = Canary()
         manager.register_class(Foo)
         manager.register_attribute(Foo, 'attr', True, extension=canary,
-                                   typecallable=typecallable)
+                                   typecallable=typecallable, useobject=True)
 
         obj = Foo()
         direct = obj.attr
@@ -893,7 +893,7 @@ class CollectionsTest(PersistTest):
         canary = Canary()
         manager.register_class(Foo)
         manager.register_attribute(Foo, 'attr', True, extension=canary,
-                                   typecallable=typecallable)
+                                   typecallable=typecallable, useobject=True)
 
         obj = Foo()
         adapter = collections.collection_adapter(obj.attr)
@@ -1027,7 +1027,7 @@ class CollectionsTest(PersistTest):
         canary = Canary()
         manager.register_class(Foo)
         manager.register_attribute(Foo, 'attr', True, extension=canary,
-                                   typecallable=Custom)
+                                   typecallable=Custom, useobject=True)
 
         obj = Foo()
         adapter = collections.collection_adapter(obj.attr)
@@ -1096,7 +1096,7 @@ class CollectionsTest(PersistTest):
         canary = Canary()
         creator = entity_maker
         manager.register_class(Foo)
-        manager.register_attribute(Foo, 'attr', True, extension=canary)
+        manager.register_attribute(Foo, 'attr', True, extension=canary, useobject=True)
 
         obj = Foo()
         col1 = obj.attr
index 7669d25541254c8b5ade606ae107b8ce23601f23..3336a0783aa28bdd121cca047bdf25179a7b9085 100644 (file)
@@ -1081,14 +1081,40 @@ class SaveTest(ORMTest):
         self.assert_(l.user_id == au.user_id and l.address_id == au.address_id)
     
     def test_deferred(self):
-        """test that a deferred load within a commit() doesnt screw up the connection"""
+        """test deferred column operations"""
+        
         mapper(User, users, properties={
             'user_name':deferred(users.c.user_name)
         })
+        
+        # dont set deferred attribute, commit session
         u = User()
         u.user_id=42
         Session.commit()
-  
+
+        #  assert that changes get picked up
+        u.user_name = 'some name'
+        Session.commit()
+        assert list(Session.execute(users.select(), mapper=User)) == [(42, 'some name')]
+        Session.clear()
+        
+        # assert that a set operation doesn't trigger a load operation
+        u = Session.query(User).filter(User.user_name=='some name').one()
+        def go():
+            u.user_name = 'some other name'
+        self.assert_sql_count(testbase.db, go, 0)
+        Session.flush()
+        assert list(Session.execute(users.select(), mapper=User)) == [(42, 'some other name')]
+        
+        Session.clear()
+        
+        # test assigning None to an unloaded deferred also works
+        u = Session.query(User).filter(User.user_name=='some other name').one()
+        u.user_name = None
+        Session.flush()
+        assert list(Session.execute(users.select(), mapper=User)) == [(42, None)]
+        
+        
     # why no support on oracle ?  because oracle doesn't save
     # "blank" strings; it saves a single space character. 
     @testing.unsupported('oracle')