]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Further improved the process_relationships function to handle the ordering of
authorJonathan LaCour <jonathan@cleverdevil.org>
Tue, 6 Jun 2006 17:58:41 +0000 (17:58 +0000)
committerJonathan LaCour <jonathan@cleverdevil.org>
Tue, 6 Jun 2006 17:58:41 +0000 (17:58 +0000)
class definitions better. The function was only looking at relationships, not
foreign keys, and was making some improper assumptions. The unit tests all
still pass, and now some of my own code actually works, regardless of the order
that I define the classes in.' 'lib/sqlalchemy/ext/activemapper.py

lib/sqlalchemy/ext/activemapper.py

index 4ff7a150b6e55f7d2e067f91765e6507c535b9c3..24360f3f4027747df511ec9d6f9a34b45d3ecce0 100644 (file)
@@ -1,4 +1,5 @@
-from sqlalchemy             import create_session, relation, mapper, join, DynamicMetaData, class_mapper, util
+from sqlalchemy             import (create_session, relation, mapper, join, 
+                                    DynamicMetaData, class_mapper, util)
 from sqlalchemy             import and_, or_
 from sqlalchemy             import Table, Column, ForeignKey
 from sqlalchemy.ext.sessioncontext import SessionContext
@@ -32,8 +33,8 @@ objectstore = Objectstore(create_session)
 class column(object):
     def __init__(self, coltype, colname=None, foreign_key=None,
                  primary_key=False, *args, **kwargs):
-        if isinstance( foreign_key, basestring ):
-            foreign_key= ForeignKey( foreign_key )
+        if isinstance(foreign_key, basestring): 
+            foreign_key = ForeignKey(foreign_key)
         self.coltype     = coltype
         self.colname     = colname
         self.foreign_key = foreign_key
@@ -56,15 +57,18 @@ class relationship(object):
         self.secondary = secondary
 
 class one_to_many(relationship):
-    def __init__(self, classname, colname=None, backref=None, private=False, lazy=True):
-        relationship.__init__(self, classname, colname, backref, private, lazy, uselist=True)
-
+    def __init__(self, classname, colname=None, backref=None, private=False,
+                 lazy=True):
+        relationship.__init__(self, classname, colname, backref, private, 
+                              lazy, uselist=True)
 
 class one_to_one(relationship):
-    def __init__(self, classname, colname=None, backref=None, private=False, lazy=True):
+    def __init__(self, classname, colname=None, backref=None, private=False,
+                 lazy=True):
         if backref is not None:
             backref = create_backref(backref, uselist=False)
-        relationship.__init__(self, classname, colname, backref, private, lazy, uselist=False)
+        relationship.__init__(self, classname, colname, backref, private, 
+                              lazy, uselist=False)
 
 class many_to_many(relationship):
     def __init__(self, classname, secondary, backref=None, lazy=True):
@@ -79,14 +83,44 @@ class many_to_many(relationship):
 # up if the classes aren't specified in a proper order
 # 
 
-__deferred_classes__  = []
+__deferred_classes__ = set()
+__processed_classes__ = set()
 def process_relationships(klass, was_deferred=False):
+    # first, we loop through all of the relationships defined on the
+    # class, and make sure that the related class already has been
+    # completely processed and defer processing if it has not
     defer = False
     for propname, reldesc in klass.relations.items():
-        if not reldesc.classname in ActiveMapperMeta.classes:
-            if not was_deferred: __deferred_classes__.append(klass)
+        found = False
+        for other_klass in __processed_classes__:
+            if reldesc.classname == other_klass.__name__:
+                found = True
+                break
+        
+        if not found:
+            if not was_deferred: __deferred_classes__.add(klass)
             defer = True
+            break
     
+    # next, we loop through all the columns looking for foreign keys
+    # and make sure that we can find the related tables (they do not 
+    # have to be processed yet, just defined), and we defer if we are 
+    # not able to find any of the related tables
+    for col in klass.columns:
+        if col.foreign_key is not None:
+            found = False
+            for other_klass in ActiveMapperMeta.classes.values():
+                table_name = col.foreign_key._colspec.rsplit('.', 1)[0]
+                if other_klass.table.fullname.lower() == table_name.lower():
+                    found = True
+                        
+            if not found:
+                if not was_deferred: __deferred_classes__.add(klass)
+                defer = True
+                break
+    
+    # if we are able to find all related and referred to tables, then
+    # we can go ahead and assign the relationships to the class
     if not defer:
         relations = {}
         for propname, reldesc in klass.relations.items():
@@ -97,15 +131,24 @@ def process_relationships(klass, was_deferred=False):
                                            private=reldesc.private, 
                                            lazy=reldesc.lazy, 
                                            uselist=reldesc.uselist)
+        
         class_mapper(klass).add_properties(relations)
-        #assign_mapper(objectstore, klass, klass.table, properties=relations,
-        #              inherits=getattr(klass, "_base_mapper", None))
-        if was_deferred: __deferred_classes__.remove(klass)
+        if klass in __deferred_classes__: 
+            __deferred_classes__.remove(klass)
+        __processed_classes__.add(klass)
     
+    # finally, loop through the deferred classes and attempt to process
+    # relationships for them
     if not was_deferred:
-        for deferred_class in __deferred_classes__:
-            process_relationships(deferred_class, was_deferred=True)
-
+        # loop through the list of deferred classes, processing the
+        # relationships, until we can make no more progress
+        last_count = len(__deferred_classes__) + 1
+        while last_count > len(__deferred_classes__):
+            last_count = len(__deferred_classes__)
+            deferred = __deferred_classes__.copy()
+            for deferred_class in deferred:
+                if deferred_class == klass: continue
+                process_relationships(deferred_class, was_deferred=True)
 
 
 class ActiveMapperMeta(type):
@@ -115,7 +158,8 @@ class ActiveMapperMeta(type):
         table_name = clsname.lower()
         columns    = []
         relations  = {}
-        _metadata    = getattr( sys.modules[cls.__module__], "__metadata__", metadata )
+        _metadata  = getattr(sys.modules[cls.__module__], 
+                             "__metadata__", metadata)
         
         if 'mapping' in dict:
             members = inspect.getmembers(dict.get('mapping'))
@@ -147,13 +191,18 @@ class ActiveMapperMeta(type):
                 
                 if isinstance(value, relationship):
                     relations[name] = value
+            
             assert _metadata is not None, "No MetaData specified"
+            
             ActiveMapperMeta.metadatas.add(_metadata)
             cls.table = Table(table_name, _metadata, *columns)
+            cls.columns = columns
+            
             # check for inheritence
-            if hasattr( bases[0], "mapping" ):
+            if hasattr(bases[0], "mapping"):
                 cls._base_mapper= bases[0].mapper
-                assign_mapper(objectstore, cls, cls.table, inherits=cls._base_mapper)
+                assign_mapper(objectstore, cls, cls.table, 
+                              inherits=cls._base_mapper)
             else:
                 assign_mapper(objectstore, cls, cls.table)
             cls.relations = relations
@@ -164,6 +213,7 @@ class ActiveMapperMeta(type):
         super(ActiveMapperMeta, cls).__init__(clsname, bases, dict)
 
 
+
 class ActiveMapper(object):
     __metaclass__ = ActiveMapperMeta
     
@@ -179,7 +229,7 @@ class ActiveMapper(object):
 def create_tables():
     for metadata in ActiveMapperMeta.metadatas:
         metadata.create_all()
+
 def drop_tables():
     for metadata in ActiveMapperMeta.metadatas:
-        metadata.drop_all()
-
+        metadata.drop_all()
\ No newline at end of file