From d9273e41f75b115b2724083b9017d3ef596822ad Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 14 Nov 2005 00:10:38 +0000 Subject: [PATCH] working on lazyloading even before object is saved --- lib/sqlalchemy/attributes.py | 38 ++++++++++++---- lib/sqlalchemy/mapper.py | 81 ++++++++++++++++++++++++++--------- lib/sqlalchemy/objectstore.py | 4 +- lib/sqlalchemy/types.py | 2 + test/mapper.py | 1 + test/objectstore.py | 22 +++++++++- 6 files changed, 116 insertions(+), 32 deletions(-) diff --git a/lib/sqlalchemy/attributes.py b/lib/sqlalchemy/attributes.py index 78245318c6..fe336745ca 100644 --- a/lib/sqlalchemy/attributes.py +++ b/lib/sqlalchemy/attributes.py @@ -28,6 +28,7 @@ class SmartProperty(object): def attribute_registry(self): return self.manager def property(self, key, uselist, **kwargs): + print "NEW SMART PROP", key, repr(kwargs) def set_prop(obj, value): if uselist: self.attribute_registry().set_list_attribute(obj, key, value, **kwargs) @@ -156,9 +157,14 @@ class CallableProp(object): return m.attribute_history(self.obj)[self.key] else: if not self.obj.__dict__.has_key(self.key) or len(self.obj.__dict__[self.key]) == 0: + if passive: + print "HI YES" + return None + print "doing CALLABLE for key", self.key value = self.callable_() else: value = None + print "WHATEVER THING", self.key p = self.manager.create_list(self.obj, self.key, value, **self.kwargs) self.manager.attribute_history(self.obj)[self.key] = p self.manager = None @@ -218,6 +224,9 @@ class AttributeManager(object): def set_callable(self, obj, key, func, uselist, **kwargs): self.attribute_history(obj)[key] = CallableProp(self, func, obj, key, uselist, **kwargs) + + def create_callable(self, obj, key, func, uselist, **kwargs): + return CallableProp(self, func, obj, key, uselist, **kwargs) def delete_list_attribute(self, obj, key, **kwargs): pass @@ -243,23 +252,34 @@ class AttributeManager(object): def remove(self, obj): pass - def get_history(self, obj, key, **kwargs): + def get_history(self, obj, key, create_prop = None, **kwargs): try: return self.attribute_history(obj)[key].gethistory(**kwargs) except KeyError, e: - p = PropHistory(obj, key, **kwargs) - self.attribute_history(obj)[key] = p - return p + if create_prop is not None: + p = self.create_callable(obj, key, create_prop(obj), uselist=False, **kwargs) + self.attribute_history(obj)[key] = p + return p.gethistory(**kwargs) + else: + p = PropHistory(obj, key, **kwargs) + self.attribute_history(obj)[key] = p + return p - def get_list_history(self, obj, key, passive = False, **kwargs): + def get_list_history(self, obj, key, passive = False, create_prop = None, **kwargs): try: return self.attribute_history(obj)[key].gethistory(passive) except KeyError, e: + print "GETLISTHISTORY", key, repr(passive), repr(create_prop) # TODO: when an callable is re-set on an existing list element - list_ = obj.__dict__.get(key, None) - p = self.create_list(obj, key, list_, **kwargs) - self.attribute_history(obj)[key] = p - return p + if create_prop is not None: + p = self.create_callable(obj, key, create_prop(obj), uselist=True, **kwargs) + self.attribute_history(obj)[key] = p + return p.gethistory(passive) + else: + list_ = obj.__dict__.get(key, None) + p = self.create_list(obj, key, list_, **kwargs) + self.attribute_history(obj)[key] = p + return p def attribute_history(self, obj): try: diff --git a/lib/sqlalchemy/mapper.py b/lib/sqlalchemy/mapper.py index 2b51abe50d..ab98cd6bf4 100644 --- a/lib/sqlalchemy/mapper.py +++ b/lib/sqlalchemy/mapper.py @@ -255,7 +255,7 @@ class Mapper(object): if not self.props.has_key(key): self.props[key] = prop._copy() - if not hasattr(self.class_, '_mapper') or self.is_primary or not _mappers.has_key(self.class_._mapper): + if not hasattr(self.class_, '_mapper') or self.is_primary or not _mappers.has_key(self.class_._mapper) or (inherits is not None and inherits._is_primary_mapper()): self._init_class() engines = property(lambda s: [t.engine for t in s.tables]) @@ -269,6 +269,10 @@ class Mapper(object): def hash_key(self): return self.hashkey + def _is_primary_mapper(self): +# return True + return getattr(self.class_, '_mapper') == self.hashkey + def _init_class(self): """sets up our classes' overridden __init__ method, this mappers hash key as its '_mapper' property, and our columns as its 'c' property. if the class already had a @@ -636,7 +640,7 @@ class ColumnProperty(MapperProperty): def init(self, key, parent): self.key = key # establish a SmartProperty property manager on the object for this key - if not hasattr(parent.class_, key): + if parent._is_primary_mapper(): #print "regiser col on class %s key %s" % (parent.class_.__name__, key) objectstore.uow().register_attribute(parent.class_, key, uselist = False) @@ -712,10 +716,16 @@ class PropertyLoader(MapperProperty): self._compile_synchronizers() - if not hasattr(parent.class_, key): + #if not hasattr(parent.class_, key): #print "regiser list col on class %s key %s" % (parent.class_.__name__, key) - objectstore.uow().register_attribute(parent.class_, key, uselist = self.uselist, deleteremoved = self.private) - + if parent._is_primary_mapper(): + self._set_class_attribute(parent.class_, key) + #objectstore.uow().register_attribute(parent.class_, key, uselist = self.uselist, deleteremoved = self.private) + + def _set_class_attribute(self, class_, key): + print "SET NORMAL CA", key + objectstore.uow().register_attribute(class_, key, uselist = self.uselist, deleteremoved = self.private) + def _get_direction(self): if self.parent.primarytable is self.target: if self.foreignkey.primary_key: @@ -813,7 +823,9 @@ class PropertyLoader(MapperProperty): return if self.uselist: + print repr(obj), "deleted, getting listm, right now its", repr(obj.__dict__.get(self.key, None)) childlist = uow.attributes.get_list_history(obj, self.key, passive = False) + print "and its", repr(childlist) else: childlist = uow.attributes.get_history(obj, self.key) for child in childlist.deleted_items() + childlist.unchanged_items(): @@ -912,12 +924,14 @@ class PropertyLoader(MapperProperty): uowcommit.register_deleted_list(childlist) else: for obj in deplist: + #print "PROCESS:", repr(obj) if self.direction == PropertyLoader.RIGHT: uowcommit.register_object(obj) - childlist = getlist(obj) + childlist = getlist(obj, passive=True) if childlist is None: continue uowcommit.register_saved_list(childlist) for child in childlist.added_items(): + #print "parent", repr(obj), "child", repr(child), "EOF" self._synchronize(obj, child, None, False) if self.direction == PropertyLoader.LEFT: uowcommit.register_object(child) @@ -966,37 +980,62 @@ class LazyLoader(PropertyLoader): PropertyLoader.init(self, key, parent) (self.lazywhere, self.lazybinds) = create_lazy_clause(self.parent.table, self.primaryjoin, self.secondaryjoin, self.foreignkey) - def execute(self, instance, row, identitykey, imap, isnew): - if isnew: - def lazyload(): - params = {} - for key in self.lazybinds.keys(): - params[key] = row[key] + def _set_class_attribute(self, class_, key): + print "SET DYNAMIC CA,", key + objectstore.uow().register_attribute(class_, key, uselist = self.uselist, deleteremoved = self.private, create_prop=lambda i: self.setup_loader(i)) + + def setup_loader(self, instance): + def lazyload(): + params = {} + allparams = True + #print "setting up loader, lazywhere", str(self.lazywhere) + for col, bind in self.lazybinds.iteritems(): + if self.direction == PropertyLoader.RIGHT: + params[bind.key] = self.mapper._getattrbycolumn(instance, col) + #print "getting attr", col.table.name + "." + col.key, "off instance", repr(instance), "and its", params[bind.key] + else: + params[bind.key] = self.parent._getattrbycolumn(instance, col) + if params[bind.key] is None: + allparams = False + break + if allparams: if self.secondary is not None: order_by = [self.secondary.rowid_column] else: order_by = [] result = self.mapper.select(self.lazywhere, order_by=order_by,**params) - if self.uselist: - return result + else: + result = [] + if self.uselist: + return result + else: + if len(result): + return result[0] else: - if len(result): - return result[0] - else: - return None - objectstore.uow().register_callable(instance, self.key, lazyload, uselist=self.uselist, deleteremoved = self.private) + return None + return lazyload +# objectstore.uow().register_callable(instance, self.key, lazyload, uselist=self.uselist, deleteremoved = self.private) + + def execute(self, instance, row, identitykey, imap, isnew): + if isnew: + return + # lazyload = self.setup_loader(instance) + # objectstore.uow().register_callable(instance, self.key, lazyload, uselist=self.uselist, deleteremoved = self.private) + # self.setup_loader(instance) def create_lazy_clause(table, primaryjoin, secondaryjoin, foreignkey): binds = {} def visit_binary(binary): circular = binary.left.table is binary.right.table if isinstance(binary.left, schema.Column) and ((not circular and binary.left.table is table) or foreignkey is binary.right): - binary.left = binds.setdefault(table.name + "_" + binary.left.name, +# binary.left = binds.setdefault(table.name + "_" + binary.left.name, + binary.left = binds.setdefault(binary.left, sql.BindParamClause(table.name + "_" + binary.left.name, None, shortname = binary.left.name)) binary.swap() if isinstance(binary.right, schema.Column) and ((not circular and binary.right.table is table) or foreignkey is binary.left): - binary.right = binds.setdefault(table.name + "_" + binary.right.name, +# binary.right = binds.setdefault(table.name + "_" + binary.right.name, + binary.right = binds.setdefault(binary.right, sql.BindParamClause(table.name + "_" + binary.right.name, None, shortname = binary.right.name)) if secondaryjoin is not None: diff --git a/lib/sqlalchemy/objectstore.py b/lib/sqlalchemy/objectstore.py index 06d1512624..914aaecb2d 100644 --- a/lib/sqlalchemy/objectstore.py +++ b/lib/sqlalchemy/objectstore.py @@ -298,6 +298,7 @@ class UOWTransaction(object): refreshed/updated to reflect a recent save/upcoming delete operation, but not a full save/delete operation on the object itself, unless an additional save/delete registration is entered for the object.""" +# print "RO", str(obj), str(isdelete), str(listonly) mapper = object_mapper(obj) self.mappers.append(mapper) task = self.get_task_by_mapper(mapper) @@ -313,6 +314,7 @@ class UOWTransaction(object): self.dependencies[(mapper, dependency)] = True def register_processor(self, mapper, isdelete, processor, mapperfrom, isdeletefrom): + print "RP", str(mapper), str(isdelete), str(processor), str(mapperfrom) task = self.get_task_by_mapper(mapper) targettask = self.get_task_by_mapper(mapperfrom) task.dependencies.append((processor, targettask, isdeletefrom)) @@ -334,7 +336,7 @@ class UOWTransaction(object): task.mapper.register_dependencies(self) head = self._sort_dependencies() - #print "Task dump:\n" + head.dump() + print "Task dump:\n" + head.dump() if head is not None: head.execute(self) diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index e9bc6d89fa..0e30a98a03 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -78,6 +78,8 @@ class String(NullTypeEngine): class Integer(NullTypeEngine): """integer datatype""" + # TODO: do string bind params need int(value) performed before sending ? + # seems to be not needed with SQLite, Postgres pass class Numeric(NullTypeEngine): diff --git a/test/mapper.py b/test/mapper.py index 4bde6a0748..fc51942643 100644 --- a/test/mapper.py +++ b/test/mapper.py @@ -20,6 +20,7 @@ class MapperSuperTest(AssertMixin): db.echo = testbase.echo def setUp(self): objectstore.clear() + clear_mappers() class MapperTest(MapperSuperTest): diff --git a/test/objectstore.py b/test/objectstore.py index f8aaf497e1..a832b2f3ef 100644 --- a/test/objectstore.py +++ b/test/objectstore.py @@ -641,7 +641,27 @@ class SaveTest(AssertMixin): l = m.select(items.c.item_name.in_(*[e['item_name'] for e in data[1:]]), order_by=[items.c.item_name, keywords.c.name]) self.assert_result(l, *data) - + def testbidirectional(self): + m1 = mapper(User, users, properties={ + 'addresses':relation(Address, addresses, lazy=True, private=True) + }, is_primary=True) + + m2 = mapper(Address, addresses, properties = dict( + user = relation(User, users, lazy = False) + ), is_primary=True) + + u = User() + print repr(u.__dict__.get('addresses', None)) + u.user_name = 'test' + a = Address() + a.email_address = 'testaddress' + a.user = u + objectstore.commit() + print repr(u.__dict__.get('addresses', None)) +# objectstore.clear() +# objectstore.delete(u) + # objectstore.commit() + if __name__ == "__main__": unittest.main() -- 2.47.2