diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2007-10-31 19:11:22 +0000 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2007-10-31 19:11:22 +0000 |
commit | 3f4d34b42c9ad2b27b62a64d5d80e47ff3bff5d6 (patch) | |
tree | 29c685b64d2230fee1aff67e310c91c2e0335f7c | |
parent | 60c3a1e8a22c999f93b402b7761fd185018eb2a9 (diff) | |
download | sqlalchemy-3f4d34b42c9ad2b27b62a64d5d80e47ff3bff5d6.tar.gz |
- split ScalarInstrumentedAttribute into a "scalar" and an "object" version.
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.
-rw-r--r-- | CHANGES | 57 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 74 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 8 | ||||
-rw-r--r-- | test/orm/attributes.py | 76 | ||||
-rw-r--r-- | test/orm/collection.py | 20 | ||||
-rw-r--r-- | test/orm/unitofwork.py | 30 |
6 files changed, 167 insertions, 98 deletions
@@ -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 ----- diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 6d9c092a6..1855a24b0 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -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): diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 1c1813311..f64330289 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -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): diff --git a/test/orm/attributes.py b/test/orm/attributes.py index 0fd325bb2..930bfa57e 100644 --- a/test/orm/attributes.py +++ b/test/orm/attributes.py @@ -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 diff --git a/test/orm/collection.py b/test/orm/collection.py index 504a4d0cb..4fe9a5e65 100644 --- a/test/orm/collection.py +++ b/test/orm/collection.py @@ -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 diff --git a/test/orm/unitofwork.py b/test/orm/unitofwork.py index 7669d2554..3336a0783 100644 --- a/test/orm/unitofwork.py +++ b/test/orm/unitofwork.py @@ -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') |