- 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
'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
-----
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
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:
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.
particular instance.
"""
+ NO_VALUE = object()
+
def __init__(self, attr, state, current, passive=False):
self.attr = attr
# 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 = []
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 = []
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``.
callable_,
typecallable,
**kwargs)
+ elif useobject:
+ return ScalarObjectAttributeImpl(class_, self, key, callable_,
+ **kwargs)
else:
return ScalarAttributeImpl(class_, self, key, callable_,
**kwargs)
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.
"""
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):
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:
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):
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):
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__)
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())
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__)
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()
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)
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
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()
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()
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')
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)
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()
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)
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)
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"
@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"
@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
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)
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)
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
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)
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
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)
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
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)
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)
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
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')