]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
working on lazyloading even before object is saved
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 14 Nov 2005 00:10:38 +0000 (00:10 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 14 Nov 2005 00:10:38 +0000 (00:10 +0000)
lib/sqlalchemy/attributes.py
lib/sqlalchemy/mapper.py
lib/sqlalchemy/objectstore.py
lib/sqlalchemy/types.py
test/mapper.py
test/objectstore.py

index 78245318c6dbe77513c635e420464f07e9f3fa5c..fe336745caecef5c476c0b13e429f1711ca02ce3 100644 (file)
@@ -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:
index 2b51abe50d0e28855364945025fa229dda569bc0..ab98cd6bf4f3e566015d8ce964b9bd20dc4d0414 100644 (file)
@@ -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:
index 06d15126245850e391a13dfd6ec6c8b990e3a145..914aaecb2d525e4ad9f291b9b74b082313854f96 100644 (file)
@@ -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)
             
index e9bc6d89fad4fde9b0f535b60f8fdee41f417c71..0e30a98a03a12570157f74c7fc50b27fc3052f58 100644 (file)
@@ -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):
index 4bde6a07485781d2fe3f4a6d4de84638e67182c2..fc51942643e3060f2c8f24066cb6db4fcd33f55e 100644 (file)
@@ -20,6 +20,7 @@ class MapperSuperTest(AssertMixin):
         db.echo = testbase.echo
     def setUp(self):
         objectstore.clear()
+        clear_mappers()
 
     
 class MapperTest(MapperSuperTest):
index f8aaf497e12a3c932f68b77ea9629ef617e52683..a832b2f3ef0618ed6ef39080ab4f7231d2089f20 100644 (file)
@@ -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()