Changeset 129

Show
Ignore:
Timestamp:
06/15/07 10:27:31 (6 years ago)
Author:
ged
Message:

- initial commit of my autodelay functionality + test
- changed how entities find their target: if the class is not found in the

target module, it tries in the list of entities defined for that call frame.
In most case, it will be the module, but it also works for classes defined
inside a method.

Location:
elixir/branches/autodelay
Files:
1 added
3 modified

Legend:

Unmodified
Added
Removed
  • elixir/branches/autodelay/elixir/__init__.py

    r119 r129  
    4040           'using_options', 'using_table_options', 'using_mapper_options', 
    4141           'options_defaults', 'metadata', 'objectstore', 
    42            'create_all', 'drop_all', 'setup_all', 'cleanup_all', 
    43            'delay_setup'] + \ 
     42           'create_all', 'drop_all', 'setup_all', 'cleanup_all'] + \ 
    4443          sqlalchemy.types.__all__ 
    4544 
    4645__pudge_all__ = ['create_all', 'drop_all', 'setup_all', 'cleanup_all', 
    47                  'metadata', 'objectstore', 'delay_setup'] 
     46                 'metadata', 'objectstore'] 
    4847 
    4948# connect 
     
    8079        md.drop_all() 
    8180 
    82 delayed_entities = set() 
    83 delay_setup = False 
    84  
     81_delayed_descriptors = list() 
    8582 
    8683def setup_all(): 
    8784    '''Setup the table and mapper for all entities which have been delayed. 
    8885 
    89     This should be used in conjunction with setting ``delay_setup`` to ``True`` 
    90     before defining your entities. 
     86    This is called automatically when your entity is first accessed, or ... 
     87    [TODO: complete this] 
    9188    ''' 
    92     for entity in delayed_entities: 
    93         entity.setup_table() 
    94     for entity in delayed_entities: 
    95         entity.setup_mapper() 
     89    if not _delayed_descriptors: 
     90        return 
     91 
     92#TODO: define all those operations as methods on the descriptor 
     93#    for method_name in ('setup_table', 'setup_mapper', 'setup_relkeys', ...): 
     94#        for desc in _delayed_descriptors: 
     95#            method = getattr(desc, method_name) 
     96#            method() 
     97 
     98    for desc in _delayed_descriptors: 
     99        #TODO: I need to restore the original table_iterator on the metadata 
     100        entity = desc.entity 
     101        entity._ready = False 
     102        del sqlalchemy.orm.mapper_registry[entity._class_key] 
     103        del desc.metadata.tables[entity._table_key] 
     104 
     105    for desc in _delayed_descriptors: 
     106        desc.setup_table() 
    96107 
    97108    # setup all relationships 
    98     for entity in delayed_entities: 
    99         for rel in entity.relationships.itervalues(): 
    100             rel.setup() 
     109    for desc in _delayed_descriptors: 
     110        for rel in desc.relationships.itervalues(): 
     111            rel.create_keys() 
    101112 
    102     delayed_entities.clear() 
     113    for desc in _delayed_descriptors: 
     114        for rel in desc.relationships.itervalues(): 
     115            rel.create_tables() 
     116 
     117    for desc in _delayed_descriptors: 
     118        desc.setup_mapper() 
     119 
     120    for desc in _delayed_descriptors: 
     121        for rel in desc.relationships.itervalues(): 
     122            rel.create_properties() 
     123 
     124    del _delayed_descriptors[:] 
    103125 
    104126    # issue the "CREATE" SQL statements 
    105     create_all() 
     127#    create_all() 
    106128 
    107129 
     
    113135        md.clear() 
    114136    metadatas.clear() 
    115     EntityDescriptor.uninitialized_rels.clear() 
    116137 
    117138    objectstore.clear() 
  • elixir/branches/autodelay/elixir/entity.py

    r119 r129  
    66from sqlalchemy.ext.assignmapper    import assign_mapper 
    77from sqlalchemy.util                import OrderedDict 
     8import sqlalchemy 
    89from elixir.statements              import Statement 
    910from elixir.fields                  import Field 
     
    5556        self.constraints = list() 
    5657 
    57         #CHECKME: this is a workaround for the "current" descriptor/target 
    58         # property ugliness. The problem is that this workaround is ugly too. 
    59         # I'm not sure if this is a safe practice. It works but...? 
    60 #        setattr(self.module, entity.__name__, entity) 
    61  
    6258        # set default value for options 
    6359        self.order_by = None 
     
    9389            self.tablename = self.tablename(entity) 
    9490     
    95     def setup(self): 
    96         ''' 
    97         Create tables, keys, columns that have been specified so far and  
    98         assign a mapper. Will be called when an instance of the entity is  
    99         created or a mapper is needed to access one or many instances of the  
    100         entity. It will try to initialize the entity's relationships (along  
    101         with any delayed relationship) but some of them might be delayed. 
    102         ''' 
    103         if elixir.delay_setup: 
    104             elixir.delayed_entities.add(self) 
    105             return 
    106  
    107         self.setup_table() 
    108         self.setup_mapper() 
    109  
    110         # This marks all relations of the entity (or, at least those which  
    111         # have been added so far by statements) as being uninitialized 
    112         EntityDescriptor.uninitialized_rels.update( 
    113             self.relationships.values()) 
    114  
    115         # try to setup all uninitialized relationships 
    116         EntityDescriptor.setup_relationships() 
    117      
    11891    def translate_order_by(self, order_by): 
    11992        if isinstance(order_by, basestring): 
     
    161134                properties[field.column.name] = deferred(field.column, 
    162135                                                         group=group) 
    163  
    164136        assign_mapper(session.context, self.entity, self.entity.table, 
    165137                      properties=properties, **kwargs) 
     
    191163#            elif self.inheritance == 'concrete': 
    192164                # do not reuse parent table, but copy all fields 
    193                 # the problem is that, at this points, all "plain" fields 
     165                # the problem is that, at this point, all "plain" fields 
    194166                # are known, but not those generated by relations 
    195167#                for field in self.fields.itervalues(): 
     
    217189        self.entity.table = Table(self.tablename, self.metadata,  
    218190                                  *args, **kwargs) 
    219      
     191 
    220192    def create_auto_primary_key(self): 
    221193        ''' 
     
    285257    all_relationships = property(all_relationships) 
    286258 
    287     def setup_relationships(cls): 
    288         for relationship in list(EntityDescriptor.uninitialized_rels): 
    289             if relationship.setup(): 
    290                 EntityDescriptor.uninitialized_rels.remove(relationship) 
    291     setup_relationships = classmethod(setup_relationships) 
     259 
     260class TriggerProxy(object): 
     261    def __init__(self, class_, attrname, setupfunc): 
     262        self.class_ = class_ 
     263        self.attrname = attrname 
     264        self.setupfunc = setupfunc 
     265 
     266    def __getattr__(self, name): 
     267        self.setupfunc() 
     268        proxied_attr = getattr(self.class_, self.attrname) 
     269        return getattr(proxied_attr, name) 
     270 
     271    def __repr__(self): 
     272        proxied_attr = getattr(self.class_, self.attrname) 
     273        return "PROXY(%s)" % (self.class_.__name__) 
    292274 
    293275class EntityMeta(type): 
     
    297279    entities (ie you don't want to use the provided 'Entity' class). 
    298280    """ 
     281    _ready = False 
     282    _entities = {} 
    299283 
    300284    def __init__(cls, name, bases, dict_): 
     
    303287            return 
    304288 
     289        cid = cls._caller = id(sys._getframe(1)) 
     290        caller_entities = EntityMeta._entities.setdefault(cid, {}) 
     291        caller_entities[name] = cls 
     292 
    305293        # create the entity descriptor 
    306294        desc = cls._descriptor = EntityDescriptor(cls) 
    307         EntityDescriptor.current = desc 
    308          
    309         # process statements 
     295 
     296        # process statements. Needed before the proxy for metadata 
    310297        Statement.process(cls) 
    311          
     298 
    312299        # setup misc options here (like tablename etc.) 
    313300        desc.setup_options() 
    314          
    315         # create table & assign (empty) mapper 
    316         desc.setup() 
    317  
    318     def q(cls): 
    319         return cls.query() 
    320     q = property(q) 
     301 
     302        # create trigger proxies 
     303        # TODO: support entity_name... or maybe not. I'm not sure it makes  
     304        # sense in Elixir. 
     305        cls.setup_proxy() 
     306 
     307    def setup_proxy(cls, entity_name=None): 
     308        #TODO: move as much as possible of those "_private" values to the 
     309        # descriptor, so that we don't mess the initial class. 
     310        cls._class_key = sqlalchemy.orm.ClassKey(cls, entity_name) 
     311 
     312        tablename = cls._descriptor.tablename 
     313        schema = cls._descriptor.table_options.get('schema', None) 
     314        cls._table_key = sqlalchemy.schema._get_table_key(tablename, schema) 
     315 
     316        elixir._delayed_descriptors.append(cls._descriptor) 
     317         
     318        mapper_proxy = TriggerProxy(cls, 'mapper', elixir.setup_all) 
     319        table_proxy = TriggerProxy(cls, 'table', elixir.setup_all) 
     320 
     321        sqlalchemy.orm.mapper_registry[cls._class_key] = mapper_proxy 
     322        md = cls._descriptor.metadata 
     323        md.tables[cls._table_key] = table_proxy 
     324 
     325        # We need to monkeypatch the metadata's table iterator method because  
     326        # otherwise it doesn't work if the setup is triggered by the  
     327        # metadata.create_all(). 
     328        # This is because ManyToMany relationships add tables AFTER the list  
     329        # of tables that are going to be created is "computed"  
     330        # (metadata.tables.values()). 
     331        # see: 
     332        # - table_iterator method in MetaData class in sqlalchemy/schema.py  
     333        # - visit_metadata method in sqlalchemy/ansisql.py 
     334        original_table_iterator = md.table_iterator 
     335        if not hasattr(original_table_iterator,  
     336                       '_elixir_setup_trigger'): 
     337            def table_iterator(reverse=True, tables=None): 
     338                elixir.setup_all() 
     339                return original_table_iterator(reverse, tables) 
     340            table_iterator._elixir_setup_trigger = True 
     341            table_iterator.__doc__ = original_table_iterator.__doc__ 
     342            md.table_iterator = table_iterator 
     343 
     344        cls._ready = True 
     345 
     346    def __getattribute__(cls, name): 
     347        if type.__getattribute__(cls, "_ready"): 
     348            elixir.setup_all() 
     349        return type.__getattribute__(cls, name) 
     350 
     351    def __call__(cls, *args, **kwargs): 
     352        elixir.setup_all() 
     353        return type.__call__(cls, *args, **kwargs) 
     354         
     355 
    321356 
    322357class Entity(object): 
     
    346381 
    347382    def __init__(self, **kwargs): 
    348          for key, value in kwargs.items(): 
    349              setattr(self, key, value) 
    350  
     383        for key, value in kwargs.items(): 
     384            setattr(self, key, value) 
     385 
  • elixir/branches/autodelay/elixir/relationships.py

    r119 r129  
    194194from elixir.statements  import Statement 
    195195from elixir.fields      import Field 
    196 from elixir.entity      import EntityDescriptor 
     196from elixir.entity      import EntityDescriptor, EntityMeta 
    197197 
    198198import sys 
     
    219219        self.args = args 
    220220        self.kwargs = kwargs 
    221          
     221#TODO: rename relationships to properties? or use add_property? 
     222# answer: NO.  
    222223        self.entity._descriptor.relationships[self.name] = self 
    223224     
     
    236237    def create_properties(self): 
    237238        ''' 
    238         Subclasses (ie. concrete relationships) may override this method to add  
    239         properties to the involved entities. 
     239        Subclasses (ie. concrete relationships) may override this method to 
     240        add properties to the involved entities. 
    240241        ''' 
    241242     
     
    244245        Sets up the relationship, creates foreign keys and secondary tables. 
    245246        ''' 
     247        print "Rel::setup", self.name 
     248 
     249        if self.property: 
     250            print " -> already done" 
     251            return False 
    246252 
    247253        if not self.target: 
     254            print " -> not ready" 
    248255            return False 
    249256 
    250         if self.property: 
    251             return True 
     257        #CHIOTTE, ca trigger le truc 
     258#        if not self.target._done: 
     259#            print " -> not ready (!!)" 
     260#            return False 
    252261 
    253262        self.create_keys() 
     
    261270            path = self.of_kind.rsplit('.', 1) 
    262271            classname = path.pop() 
     272            module = None 
    263273 
    264274            if path: 
    265275                # do we have a fully qualified entity name? 
    266276                module = sys.modules[path.pop()] 
    267             else:  
    268                 # if not, try the same module as the source 
    269                 module = self.entity._descriptor.module 
    270  
    271             self._target = getattr(module, classname, None) 
    272             if not self._target: 
    273                 # This is ugly but we need it because the class which is 
    274                 # currently being defined (we have to keep in mind we are in  
    275                 # its metaclass code) is not yet available in the module 
    276                 # namespace, so the getattr above fails. And unfortunately, 
    277                 # this doesn't only happen for the owning entity of this 
    278                 # relation since we might be setting up a deferred relation. 
    279                 e = EntityDescriptor.current.entity 
    280                 if classname == e.__name__ or \ 
    281                         self.of_kind == e.__module__ +'.'+ e.__name__: 
    282                     self._target = e 
    283                 else: 
    284                     return None 
    285          
     277                self._target = getattr(module, classname, None) 
     278            else: 
     279                # If not, try the list of entities of the "caller" of the  
     280                # source class. Most of the time, this will be the module the 
     281                # class is defined in. But it could also be a method (inner 
     282                # classes). 
     283                caller_entities = EntityMeta._entities[self.entity._caller] 
     284                self._target = caller_entities[classname] 
    286285        return self._target 
    287286    target = property(target) 
     
    310309    inverse = property(inverse) 
    311310     
     311    #TODO: move this to each rel type 
    312312    def match_type_of(self, other): 
    313313        t1, t2 = type(self), type(other) 
     
    361361        source accordingly. 
    362362        ''' 
     363        print "BT::create_keys", self.name, self.entity.__name__ 
     364        if self.foreign_key: 
     365            print " -> already done" 
     366            return 
    363367 
    364368        source_desc = self.entity._descriptor 
     369        #TODO: make this work if target is a pure SA-mapped class 
     370        # for that, I need:  
     371        # - the list of primary key columns of the target table 
     372        # - the name of the target table 
    365373        target_desc = self.target._descriptor 
    366374 
     
    369377            if self.colname: 
    370378                self.primaryjoin_clauses = \ 
    371                     _build_join_clauses(self.entity.table,  
    372                                         self.colname, None,  
    373                                         self.target.table)[0] 
     379                    _get_join_clauses(self.entity.table,  
     380                                      self.colname, None,  
     381                                      self.target.table)[0] 
    374382                if not self.primaryjoin_clauses: 
    375383                    raise Exception( 
     
    413421 
    414422                # build the list of columns the foreign key will point to 
    415                 if target_desc.entity.table.schema: 
    416                     fk_refcols.append("%s.%s.%s" % ( 
    417                         target_desc.entity.table.schema, 
    418                         target_desc.entity.table.name, 
    419                         pk_col.name)) 
    420                 else: 
    421                     fk_refcols.append("%s.%s" % (target_desc.entity.table.name, 
    422                                                  pk_col.name)) 
     423                fk_refcols.append("%s.%s" % (target_desc.entity.table.name, 
     424                                             pk_col.name)) 
    423425 
    424426                # build up the primary join. This is needed when you have  
     
    436438     
    437439    def create_properties(self): 
     440        if self.property: 
     441            print " -> already done" 
     442            return 
     443 
     444        print "BT::create_properties", self.name, self.entity.__name__ 
    438445        kwargs = self.kwargs 
    439446         
     
    457464 
    458465    def create_keys(self): 
     466        print "HO::create_keys", self.name, self.entity.__name__ 
     467 
    459468        # make sure an inverse relationship exists 
    460469        if self.inverse is None: 
     
    469478                         self.entity.__name__)) 
    470479        # make sure it is set up because it creates the foreign key we'll need 
    471         self.inverse.setup() 
     480        self.inverse.create_keys() 
    472481     
    473482    def create_properties(self): 
     483        print "HO::create_properties", self.name, self.entity.__name__ 
     484        if self.property: 
     485            print " -> already done" 
     486            return 
     487 
    474488        kwargs = self.kwargs 
    475489         
     
    497511 
    498512    def create_properties(self): 
     513        print "HM::create_properties", self.name, self.entity.__name__ 
     514        if self.property: 
     515            print " -> already done" 
     516            return 
     517 
    499518        if 'order_by' in self.kwargs: 
    500519            self.kwargs['order_by'] = \ 
     
    522541 
    523542    def create_tables(self): 
     543        print "HABTM::create_table", self.name 
     544        if self.secondary_table: 
     545            print " -> already done" 
     546            return 
     547 
    524548        if self.inverse: 
    525549            if self.inverse.secondary_table: 
     
    527551                self.primaryjoin_clauses = self.inverse.secondaryjoin_clauses 
    528552                self.secondaryjoin_clauses = self.inverse.primaryjoin_clauses 
    529  
    530         if not self.secondary_table: 
    531             e1_desc = self.entity._descriptor 
    532             e2_desc = self.target._descriptor 
    533             
    534             # First, we compute the name of the table. Note that some of the  
    535             # intermediary variables are reused later for the constraint  
    536             # names. 
     553                return 
     554 
     555        e1_desc = self.entity._descriptor 
     556        e2_desc = self.target._descriptor 
     557        
     558        # First, we compute the name of the table. Note that some of the  
     559        # intermediary variables are reused later for the constraint  
     560        # names. 
     561         
     562        # We use the name of the relation for the first entity  
     563        # (instead of the name of its primary key), so that we can  
     564        # have two many-to-many relations between the same objects  
     565        # without having a table name collision.  
     566        source_part = "%s_%s" % (e1_desc.tablename, self.name) 
     567 
     568        # And we use only the name of the table of the second entity 
     569        # when there is no inverse, so that a many-to-many relation  
     570        # can be defined without an inverse. 
     571        if self.inverse: 
     572            target_part = "%s_%s" % (e2_desc.tablename, self.inverse.name) 
     573        else: 
     574            target_part = e2_desc.tablename 
     575         
     576        if self.user_tablename: 
     577            tablename = self.user_tablename 
     578        else: 
     579            # We need to keep the table name consistent (independant of  
     580            # whether this relation or its inverse is setup first). 
     581            if self.inverse and e1_desc.tablename < e2_desc.tablename: 
     582                tablename = "%s__%s" % (target_part, source_part) 
     583            else: 
     584                tablename = "%s__%s" % (source_part, target_part) 
     585 
     586        if e1_desc.autoload: 
     587            self._reflect_table(tablename) 
     588        else: 
     589            # We pre-compute the names of the foreign key constraints  
     590            # pointing to the source (local) entity's table and to the  
     591            # target's table 
     592 
     593            # In some databases (at lease MySQL) the constraint names need  
     594            # to be unique for the whole database, instead of per table. 
     595            source_fk_name = "%s_fk" % source_part 
     596            if self.inverse: 
     597                target_fk_name = "%s_fk" % target_part 
     598            else: 
     599                target_fk_name = "%s_inverse_fk" % source_part 
     600 
     601            columns = list() 
     602            constraints = list() 
     603 
     604            joins = (self.primaryjoin_clauses, self.secondaryjoin_clauses) 
     605            for num, desc, fk_name in ((0, e1_desc, source_fk_name),  
     606                                       (1, e2_desc, target_fk_name)): 
     607                fk_colnames = list() 
     608                fk_refcols = list() 
    537609             
    538             # We use the name of the relation for the first entity  
    539             # (instead of the name of its primary key), so that we can  
    540             # have two many-to-many relations between the same objects  
    541             # without having a table name collision.  
    542             source_part = "%s_%s" % (e1_desc.tablename, self.name) 
    543  
    544             # And we use only the name of the table of the second entity 
    545             # when there is no inverse, so that a many-to-many relation  
    546             # can be defined without an inverse. 
    547             if self.inverse: 
    548                 target_part = "%s_%s" % (e2_desc.tablename, self.inverse.name) 
    549             else: 
    550                 target_part = e2_desc.tablename 
     610                for key in desc.primary_keys: 
     611                    pk_col = key.column 
     612                     
     613                    colname = '%s_%s' % (desc.tablename, pk_col.name) 
     614 
     615                    # In case we have a many-to-many self-reference, we  
     616                    # need to tweak the names of the columns so that we  
     617                    # don't end up with twice the same column name. 
     618                    if self.entity is self.target: 
     619                        colname += str(num + 1) 
     620 
     621                    col = Column(colname, pk_col.type) 
     622                    columns.append(col) 
     623 
     624                    # Build the list of local columns which will be part  
     625                    # of the foreign key. 
     626                    fk_colnames.append(colname) 
     627 
     628                    # Build the list of columns the foreign key will point 
     629                    # to. 
     630                    fk_refcols.append(desc.tablename + '.' + pk_col.name) 
     631 
     632                    # Build join clauses (in case we have a self-ref) 
     633                    if self.entity is self.target: 
     634                        joins[num].append(col == pk_col) 
     635                 
     636                constraints.append( 
     637                    ForeignKeyConstraint(fk_colnames, fk_refcols, 
     638                                         name=fk_name)) 
     639 
     640            args = columns + constraints 
    551641             
    552             if self.user_tablename: 
    553                 tablename = self.user_tablename 
    554             else: 
    555                 # We need to keep the table name consistent (independant of  
    556                 # whether this relation or its inverse is setup first). 
    557                 if self.inverse and e1_desc.tablename < e2_desc.tablename: 
    558                     tablename = "%s__%s" % (target_part, source_part) 
    559                 else: 
    560                     tablename = "%s__%s" % (source_part, target_part) 
    561  
    562             if e1_desc.autoload: 
    563                 self._reflect_table(tablename) 
    564             else: 
    565                 # We pre-compute the names of the foreign key constraints  
    566                 # pointing to the source (local) entity's table and to the  
    567                 # target's table 
    568  
    569                 # In some databases (at lease MySQL) the constraint names need  
    570                 # to be unique for the whole database, instead of per table. 
    571                 source_fk_name = "%s_fk" % source_part 
    572                 if self.inverse: 
    573                     target_fk_name = "%s_fk" % target_part 
    574                 else: 
    575                     target_fk_name = "%s_inverse_fk" % source_part 
    576  
    577                 columns = list() 
    578                 constraints = list() 
    579  
    580                 joins = (self.primaryjoin_clauses, self.secondaryjoin_clauses) 
    581                 for num, desc, fk_name in ((0, e1_desc, source_fk_name),  
    582                                            (1, e2_desc, target_fk_name)): 
    583                     fk_colnames = list() 
    584                     fk_refcols = list() 
    585                  
    586                     for key in desc.primary_keys: 
    587                         pk_col = key.column 
    588                          
    589                         colname = '%s_%s' % (desc.tablename, pk_col.name) 
    590  
    591                         # In case we have a many-to-many self-reference, we  
    592                         # need to tweak the names of the columns so that we  
    593                         # don't end up with twice the same column name. 
    594                         if self.entity is self.target: 
    595                             colname += str(num + 1) 
    596  
    597                         col = Column(colname, pk_col.type) 
    598                         columns.append(col) 
    599  
    600                         # Build the list of local columns which will be part  
    601                         # of the foreign key. 
    602                         fk_colnames.append(colname) 
    603  
    604                         # Build the list of columns the foreign key will point 
    605                         # to. 
    606                         fk_refcols.append(desc.tablename + '.' + pk_col.name) 
    607  
    608                         # Build join clauses (in case we have a self-ref) 
    609                         if self.entity is self.target: 
    610                             joins[num].append(col == pk_col) 
    611                      
    612                     constraints.append( 
    613                         ForeignKeyConstraint(fk_colnames, fk_refcols, 
    614                                              name=fk_name)) 
    615  
    616                 args = columns + constraints 
    617                  
    618                 self.secondary_table = Table(tablename, e1_desc.metadata,  
    619                                              *args) 
     642            self.secondary_table = Table(tablename, e1_desc.metadata,  
     643                                         *args) 
     644            print " -> created", tablename, e1_desc.metadata 
    620645 
    621646    def _reflect_table(self, tablename): 
     
    646671 
    647672            self.primaryjoin_clauses, self.secondaryjoin_clauses = \ 
    648                 _build_join_clauses(self.secondary_table,  
    649                                     self.local_side, self.remote_side,  
    650                                     self.entity.table) 
     673                _get_join_clauses(self.secondary_table,  
     674                                  self.local_side, self.remote_side,  
     675                                  self.entity.table) 
    651676 
    652677    def create_properties(self): 
     678        print "HABTM::create_properties", self.name, self.entity.__name__ 
     679        if self.property: 
     680            print " -> already done" 
     681            return 
    653682        kwargs = self.kwargs 
    654683 
     
    671700 
    672701 
    673 def _build_join_clauses(local_table, local_cols1, local_cols2, target_table): 
     702def _get_join_clauses(local_table, local_cols1, local_cols2, target_table): 
    674703    primary_join, secondary_join = [], [] 
    675704    cols1 = local_cols1[:] 
     
    685714    constraint_map = {} 
    686715    for constraint in local_table.constraints: 
     716        print "constraint", constraint 
    687717        if isinstance(constraint, ForeignKeyConstraint): 
    688718            use_constraint = False 
     
    719749has_many = Statement(HasMany) 
    720750has_and_belongs_to_many = Statement(HasAndBelongsToMany) 
     751