Show
Ignore:
Timestamp:
06/19/08 15:35:21 (6 years ago)
Author:
ged
Message:

removed trailing spaces

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • elixir/trunk/elixir/relationships.py

    r346 r347  
    11''' 
    2 This module provides support for defining relationships between your Elixir  
     2This module provides support for defining relationships between your Elixir 
    33entities.  Elixir currently supports two syntaxes to do so: the default 
    44`Attribute-based syntax`_ which supports the following types of relationships: 
    5 ManyToOne_, OneToMany_, OneToOne_ and ManyToMany_, as well as a  
    6 `DSL-based syntax`_ which provides the following statements: belongs_to_,  
    7 has_many_, has_one_ and has_and_belongs_to_many_.  
     5ManyToOne_, OneToMany_, OneToOne_ and ManyToMany_, as well as a 
     6`DSL-based syntax`_ which provides the following statements: belongs_to_, 
     7has_many_, has_one_ and has_and_belongs_to_many_. 
    88 
    99====================== 
     
    1111====================== 
    1212 
    13 The first argument to all these "normal" relationship classes is the name of  
    14 the class (entity) you are relating to.  
    15  
    16 Following that first mandatory argument, any number of additional keyword  
    17 arguments can be specified for advanced behavior. See each relationship type  
     13The first argument to all these "normal" relationship classes is the name of 
     14the class (entity) you are relating to. 
     15 
     16Following that first mandatory argument, any number of additional keyword 
     17arguments can be specified for advanced behavior. See each relationship type 
    1818for a list of their specific keyword arguments. At this point, we'll just note 
    19 that all the arguments that are not specifically processed by Elixir, as  
    20 mentioned in the documentation below are passed on to the SQLAlchemy  
    21 ``relation`` function. So, please refer to the `SQLAlchemy relation function's  
     19that all the arguments that are not specifically processed by Elixir, as 
     20mentioned in the documentation below are passed on to the SQLAlchemy 
     21``relation`` function. So, please refer to the `SQLAlchemy relation function's 
    2222documentation <http://www.sqlalchemy.org/docs/04/sqlalchemy_orm.html 
    23 #docstrings_sqlalchemy.orm_modfunc_relation>`_ for further detail about which  
    24 keyword arguments are supported.  
    25  
    26 You should keep in mind that the following  
    27 keyword arguments are automatically generated by Elixir and should not be used  
    28 unless you want to override the value provided by Elixir: ``uselist``,  
     23#docstrings_sqlalchemy.orm_modfunc_relation>`_ for further detail about which 
     24keyword arguments are supported. 
     25 
     26You should keep in mind that the following 
     27keyword arguments are automatically generated by Elixir and should not be used 
     28unless you want to override the value provided by Elixir: ``uselist``, 
    2929``remote_side``, ``secondary``, ``primaryjoin`` and ``secondaryjoin``. 
    3030 
    3131Additionally, if you want a bidirectionnal relationship, you should define the 
    3232inverse relationship on the other entity explicitly (as opposed to how 
    33 SQLAlchemy's backrefs are defined). In non-ambiguous situations, Elixir will  
     33SQLAlchemy's backrefs are defined). In non-ambiguous situations, Elixir will 
    3434match relationships together automatically. If there are several relationships 
    35 of the same type between two entities, Elixir is not able to determine which  
    36 relationship is the inverse of which, so you have to disambiguate the  
    37 situation by giving the name of the inverse relationship in the ``inverse``  
     35of the same type between two entities, Elixir is not able to determine which 
     36relationship is the inverse of which, so you have to disambiguate the 
     37situation by giving the name of the inverse relationship in the ``inverse`` 
    3838keyword argument. 
    3939 
     
    4343----------- 
    4444 
    45 Describes the child's side of a parent-child relationship.  For example,  
     45Describes the child's side of a parent-child relationship.  For example, 
    4646a `Pet` object may belong to its owner, who is a `Person`.  This could be 
    4747expressed like so: 
     
    5252        owner = ManyToOne('Person') 
    5353 
    54 Behind the scene, assuming the primary key of the `Person` entity is  
    55 an integer column named `id`, the ``ManyToOne`` relationship will  
    56 automatically add an integer column named `owner_id` to the entity, with a  
     54Behind the scene, assuming the primary key of the `Person` entity is 
     55an integer column named `id`, the ``ManyToOne`` relationship will 
     56automatically add an integer column named `owner_id` to the entity, with a 
    5757foreign key referencing the `id` column of the `Person` entity. 
    5858 
    59 In addition to the keyword arguments inherited from SQLAlchemy's relation  
     59In addition to the keyword arguments inherited from SQLAlchemy's relation 
    6060function, ``ManyToOne`` relationships accept the following optional arguments 
    6161which will be directed to the created column: 
     
    102102+----------------------+------------------------------------------------------+ 
    103103 
    104 Additionally, Elixir supports the belongs_to_ statement as an alternative,  
     104Additionally, Elixir supports the belongs_to_ statement as an alternative, 
    105105DSL-based, syntax to define ManyToOne_ relationships. 
    106106 
     
    119119        children = OneToMany('Person') 
    120120 
    121 Note that a ``OneToMany`` relationship **cannot exist** without a  
     121Note that a ``OneToMany`` relationship **cannot exist** without a 
    122122corresponding ``ManyToOne`` relationship in the other way. This is because the 
    123 ``OneToMany`` relationship needs the foreign key created by the ``ManyToOne``  
     123``OneToMany`` relationship needs the foreign key created by the ``ManyToOne`` 
    124124relationship. 
    125125 
    126 In addition to keyword arguments inherited from SQLAlchemy, ``OneToMany``  
     126In addition to keyword arguments inherited from SQLAlchemy, ``OneToMany`` 
    127127relationships accept the following optional (keyword) arguments: 
    128128 
     
    141141OneToMany_ relationships, with the has_many_ statement. 
    142142 
    143 Also, as for standard SQLAlchemy relations, the ``order_by`` keyword argument  
     143Also, as for standard SQLAlchemy relations, the ``order_by`` keyword argument 
    144144 
    145145 
     
    148148 
    149149Describes the parent's side of a parent-child relationship when there is only 
    150 one child.  For example, a `Car` object has one gear stick, which is  
     150one child.  For example, a `Car` object has one gear stick, which is 
    151151represented as a `GearStick` object. This could be expressed like so: 
    152152 
     
    159159        car = ManyToOne('Car') 
    160160 
    161 Note that a ``OneToOne`` relationship **cannot exist** without a corresponding  
     161Note that a ``OneToOne`` relationship **cannot exist** without a corresponding 
    162162``ManyToOne`` relationship in the other way. This is because the ``OneToOne`` 
    163163relationship needs the foreign_key created by the ``ManyToOne`` relationship. 
     
    183183        articles = ManyToMany('Article') 
    184184 
    185 Behind the scene, the ``ManyToMany`` relationship will  
     185Behind the scene, the ``ManyToMany`` relationship will 
    186186automatically create an intermediate table to host its data. 
    187187 
    188188Note that you don't necessarily need to define the inverse relationship.  In 
    189 our example, even though we want tags to be usable on several articles, we  
     189our example, even though we want tags to be usable on several articles, we 
    190190might not be interested in which articles correspond to a particular tag.  In 
    191191that case, we could have omitted the `Tag` side of the relationship. 
    192192 
    193 If the entity containing your ``ManyToMany`` relationship is  
     193If the entity containing your ``ManyToMany`` relationship is 
    194194autoloaded, you **must** specify at least one of either the ``remote_side`` or 
    195195``local_side`` argument. 
    196196 
    197 In addition to keyword arguments inherited from SQLAlchemy, ``ManyToMany``  
     197In addition to keyword arguments inherited from SQLAlchemy, ``ManyToMany`` 
    198198relationships accept the following optional (keyword) arguments: 
    199199 
     
    238238 
    239239The following DSL statements provide an alternative way to define relationships 
    240 between your entities. The first argument to all those statements is the name  
    241 of the relationship, the second is the 'kind' of object you are relating to  
    242 (it is usually given using the ``of_kind`` keyword).  
     240between your entities. The first argument to all those statements is the name 
     241of the relationship, the second is the 'kind' of object you are relating to 
     242(it is usually given using the ``of_kind`` keyword). 
    243243 
    244244`belongs_to` 
     
    269269        has_many('children', of_kind='Person') 
    270270 
    271 There is also an alternate form of the ``has_many`` relationship that takes  
    272 only two keyword arguments: ``through`` and ``via`` in order to encourage a  
    273 richer form of many-to-many relationship that is an alternative to the  
     271There is also an alternate form of the ``has_many`` relationship that takes 
     272only two keyword arguments: ``through`` and ``via`` in order to encourage a 
     273richer form of many-to-many relationship that is an alternative to the 
    274274``has_and_belongs_to_many`` statement.  Here is an example: 
    275275 
     
    313313------------------------- 
    314314 
    315 The ``has_and_belongs_to_many`` statement is the DSL syntax equivalent to the  
     315The ``has_and_belongs_to_many`` statement is the DSL syntax equivalent to the 
    316316ManyToMany_ relationship. As such, it supports all the same arguments as 
    317317ManyToMany_ relationships. 
     
    347347    Base class for relationships. 
    348348    ''' 
    349      
     349 
    350350    def __init__(self, of_kind, *args, **kwargs): 
    351351        super(Relationship, self).__init__() 
     
    357357        self._target = None 
    358358        self._inverse = None 
    359          
     359 
    360360        self.property = None # sqlalchemy property 
    361361        self.backref = None  # sqlalchemy backref 
     
    368368        super(Relationship, self).attach(entity, name) 
    369369        entity._descriptor.relationships.append(self) 
    370      
     370 
    371371    def create_pk_cols(self): 
    372372        self.create_keys(True) 
     
    377377    def create_keys(self, pk): 
    378378        ''' 
    379         Subclasses (ie. concrete relationships) may override this method to  
     379        Subclasses (ie. concrete relationships) may override this method to 
    380380        create foreign keys. 
    381381        ''' 
    382      
     382 
    383383    def create_tables(self): 
    384384        ''' 
    385         Subclasses (ie. concrete relationships) may override this method to  
     385        Subclasses (ie. concrete relationships) may override this method to 
    386386        create secondary tables. 
    387387        ''' 
    388      
     388 
    389389    def create_properties(self): 
    390390        ''' 
     
    425425        return self._target 
    426426    target = property(target) 
    427      
     427 
    428428    def inverse(self): 
    429429        if not self._inverse: 
     
    434434                    raise Exception( 
    435435                              "Couldn't find a relationship named '%s' in " 
    436                               "entity '%s' or its parent entities."  
     436                              "entity '%s' or its parent entities." 
    437437                              % (self.inverse_name, self.target.__name__)) 
    438438                assert self.match_type_of(inverse) 
     
    443443                self._inverse = inverse 
    444444                inverse._inverse = self 
    445          
     445 
    446446        return self._inverse 
    447447    inverse = property(inverse) 
    448      
     448 
    449449    def match_type_of(self, other): 
    450450        return False 
     
    461461class ManyToOne(Relationship): 
    462462    ''' 
    463      
     463 
    464464    ''' 
    465      
     465 
    466466    def __init__(self, *args, **kwargs): 
    467467        self.colname = kwargs.pop('colname', []) 
     
    480480        if 'use_alter' in kwargs: 
    481481            self.constraint_kwargs['use_alter'] = kwargs.pop('use_alter') 
    482          
     482 
    483483        if 'ondelete' in kwargs: 
    484484            self.constraint_kwargs['ondelete'] = kwargs.pop('ondelete') 
    485485        if 'onupdate' in kwargs: 
    486486            self.constraint_kwargs['onupdate'] = kwargs.pop('onupdate') 
    487          
     487 
    488488        self.foreign_key = list() 
    489489        self.primaryjoin_clauses = list() 
    490490 
    491491        super(ManyToOne, self).__init__(*args, **kwargs) 
    492      
     492 
    493493    def match_type_of(self, other): 
    494494        return isinstance(other, (OneToMany, OneToOne)) 
     
    496496    def create_keys(self, pk): 
    497497        ''' 
    498         Find all primary keys on the target and create foreign keys on the  
     498        Find all primary keys on the target and create foreign keys on the 
    499499        source accordingly. 
    500500        ''' 
     
    508508        source_desc = self.entity._descriptor 
    509509        #TODO: make this work if target is a pure SA-mapped class 
    510         # for that, I need:  
     510        # for that, I need: 
    511511        # - the list of primary key columns of the target table (type and name) 
    512512        # - the name of the target table 
     
    520520            if self.colname: 
    521521                self.primaryjoin_clauses = \ 
    522                     _get_join_clauses(self.entity.table,  
    523                                       self.colname, None,  
     522                    _get_join_clauses(self.entity.table, 
     523                                      self.colname, None, 
    524524                                      self.target.table)[0] 
    525525                if not self.primaryjoin_clauses: 
     
    539539                        "'%s' entity is not the same as the number of columns " 
    540540                        "of the primary key of '%s'." 
    541                         % (self.name, self.entity.__name__,  
     541                        % (self.name, self.entity.__name__, 
    542542                           self.target.__name__)) 
    543543 
     
    546546                raise Exception("No primary key found in target table ('%s') " 
    547547                                "for the '%s' relationship of the '%s' entity." 
    548                                 % (self.target.tablename, self.name,  
     548                                % (self.target.tablename, self.name, 
    549549                                   self.entity.__name__)) 
    550550 
     
    554554                else: 
    555555                    colname = options.FKCOL_NAMEFORMAT % \ 
    556                               {'relname': self.name,  
     556                              {'relname': self.name, 
    557557                               'key': pk_col.key} 
    558558 
     
    569569                fk_colnames.append(col.key) 
    570570 
    571                 # Build the list of column "paths" the foreign key will  
     571                # Build the list of column "paths" the foreign key will 
    572572                # point to 
    573573                target_path = "%s.%s" % (target_desc.tablename, pk_col.key) 
     
    577577                fk_refcols.append(target_path) 
    578578 
    579                 # Build up the primary join. This is needed when you have  
     579                # Build up the primary join. This is needed when you have 
    580580                # several belongs_to relationships between two objects 
    581581                self.primaryjoin_clauses.append(col == pk_col) 
    582              
     582 
    583583            if 'name' not in self.constraint_kwargs: 
    584584                # In some databases (at least MySQL) the constraint name needs 
    585585                # to be unique for the whole database, instead of per table. 
    586586                fk_name = options.CONSTRAINT_NAMEFORMAT % \ 
    587                           {'tablename': source_desc.tablename,  
     587                          {'tablename': source_desc.tablename, 
    588588                           'colnames': '_'.join(fk_colnames)} 
    589589                self.constraint_kwargs['name'] = fk_name 
    590                  
     590 
    591591            source_desc.add_constraint( 
    592592                ForeignKeyConstraint(fk_colnames, fk_refcols, 
     
    595595    def get_prop_kwargs(self): 
    596596        kwargs = {'uselist': False} 
    597          
     597 
    598598        if self.entity.table is self.target.table: 
    599599            kwargs['remote_side'] = \ 
     
    626626                      % (self.target.__name__, self.name, 
    627627                         self.entity.__name__)) 
    628      
     628 
    629629    def get_prop_kwargs(self): 
    630630        kwargs = {'uselist': self.uselist} 
    631          
     631 
    632632        #TODO: for now, we don't break any test if we remove those 2 lines. 
    633633        # So, we should either complete the selfref test to prove that they 
     
    639639            # autoloaded tables 
    640640            kwargs['remote_side'] = self.inverse.foreign_key 
    641          
     641 
    642642        if self.inverse.primaryjoin_clauses: 
    643643            kwargs['primaryjoin'] = and_(*self.inverse.primaryjoin_clauses) 
     
    650650class OneToMany(OneToOne): 
    651651    uselist = True 
    652      
     652 
    653653    def get_prop_kwargs(self): 
    654654        kwargs = super(OneToMany, self).get_prop_kwargs() 
     
    688688    def create_tables(self): 
    689689        # Warning: if the table was specified manually, the join clauses won't 
    690         # be computed. We might want to autodetect joins based on fk, as for  
     690        # be computed. We might want to autodetect joins based on fk, as for 
    691691        # autoloaded entities 
    692692        if self.secondary_table: 
    693693            return 
    694          
     694 
    695695        if self.inverse: 
    696696            if self.inverse.secondary_table: 
     
    707707        assert e1_schema == e2_schema, \ 
    708708               "Schema %r for entity %s differs from schema %r of entity %s" \ 
    709                % (e1_schema, self.entity.__name__,  
     709               % (e1_schema, self.entity.__name__, 
    710710                  e2_schema, self.target.__name__) 
    711         
    712         # First, we compute the name of the table. Note that some of the  
    713         # intermediary variables are reused later for the constraint  
     711 
     712        # First, we compute the name of the table. Note that some of the 
     713        # intermediary variables are reused later for the constraint 
    714714        # names. 
    715          
    716         # We use the name of the relation for the first entity  
    717         # (instead of the name of its primary key), so that we can  
    718         # have two many-to-many relations between the same objects  
    719         # without having a table name collision.  
     715 
     716        # We use the name of the relation for the first entity 
     717        # (instead of the name of its primary key), so that we can 
     718        # have two many-to-many relations between the same objects 
     719        # without having a table name collision. 
    720720        source_part = "%s_%s" % (e1_desc.tablename, self.name) 
    721721 
    722722        # And we use only the name of the table of the second entity 
    723         # when there is no inverse, so that a many-to-many relation  
     723        # when there is no inverse, so that a many-to-many relation 
    724724        # can be defined without an inverse. 
    725725        if self.inverse: 
     
    727727        else: 
    728728            target_part = e2_desc.tablename 
    729          
     729 
    730730        if self.user_tablename: 
    731731            tablename = self.user_tablename 
    732732        else: 
    733             # We need to keep the table name consistent (independant of  
     733            # We need to keep the table name consistent (independant of 
    734734            # whether this relation or its inverse is setup first). 
    735735            if self.inverse and e1_desc.tablename < e2_desc.tablename: 
     
    741741            self._reflect_table(tablename) 
    742742        else: 
    743             # We pre-compute the names of the foreign key constraints  
    744             # pointing to the source (local) entity's table and to the  
     743            # We pre-compute the names of the foreign key constraints 
     744            # pointing to the source (local) entity's table and to the 
    745745            # target's table 
    746746 
    747             # In some databases (at least MySQL) the constraint names need  
     747            # In some databases (at least MySQL) the constraint names need 
    748748            # to be unique for the whole database, instead of per table. 
    749749            source_fk_name = "%s_fk" % source_part 
     
    758758            joins = (self.primaryjoin_clauses, self.secondaryjoin_clauses) 
    759759            for num, desc, fk_name, m2m in ( 
    760                     (0, e1_desc, source_fk_name, self),  
     760                    (0, e1_desc, source_fk_name, self), 
    761761                    (1, e2_desc, target_fk_name, self.inverse)): 
    762762                fk_colnames = list() 
    763763                fk_refcols = list() 
    764              
     764 
    765765                for pk_col in desc.primary_keys: 
    766766                    colname = self.column_format % \ 
     
    768768                               'key': pk_col.key, 
    769769                               'entity': desc.entity.__name__.lower()} 
    770                      
    771                     # In case we have a many-to-many self-reference, we  
    772                     # need to tweak the names of the columns so that we  
     770 
     771                    # In case we have a many-to-many self-reference, we 
     772                    # need to tweak the names of the columns so that we 
    773773                    # don't end up with twice the same column name. 
    774774                    if self.entity is self.target: 
    775775                        colname += str(num + 1) 
    776                      
     776 
    777777                    col = Column(colname, pk_col.type, primary_key=True) 
    778778                    columns.append(col) 
    779779 
    780                     # Build the list of local columns which will be part  
     780                    # Build the list of local columns which will be part 
    781781                    # of the foreign key. 
    782782                    fk_colnames.append(colname) 
    783783 
    784                     # Build the list of column "paths" the foreign key will  
     784                    # Build the list of column "paths" the foreign key will 
    785785                    # point to 
    786786                    target_path = "%s.%s" % (desc.tablename, pk_col.key) 
     
    793793                    if self.entity is self.target: 
    794794                        joins[num].append(col == pk_col) 
    795                  
     795 
    796796                onupdate = m2m and m2m.onupdate 
    797797                ondelete = m2m and m2m.ondelete 
    798                  
     798 
    799799                constraints.append( 
    800800                    ForeignKeyConstraint(fk_colnames, fk_refcols, 
    801                                          name=fk_name, onupdate=onupdate,  
     801                                         name=fk_name, onupdate=onupdate, 
    802802                                         ondelete=ondelete)) 
    803803 
    804804            args = columns + constraints 
    805              
    806             self.secondary_table = Table(tablename, e1_desc.metadata,  
     805 
     806            self.secondary_table = Table(tablename, e1_desc.metadata, 
    807807                                         schema=e1_schema, *args) 
    808808 
     
    815815                % (self.entity.__name__, self.name, 
    816816                   self.target.__name__)) 
    817                  
    818         self.secondary_table = Table(tablename,  
     817 
     818        self.secondary_table = Table(tablename, 
    819819                                     self.entity._descriptor.metadata, 
    820820                                     autoload=True) 
     
    822822        # In the case we have a self-reference, we need to build join clauses 
    823823        if self.entity is self.target: 
    824             #CHECKME: maybe we should try even harder by checking if that  
     824            #CHECKME: maybe we should try even harder by checking if that 
    825825            # information was defined on the inverse relationship) 
    826826            if not self.local_side and not self.remote_side: 
     
    834834 
    835835            self.primaryjoin_clauses, self.secondaryjoin_clauses = \ 
    836                 _get_join_clauses(self.secondary_table,  
    837                                   self.local_side, self.remote_side,  
     836                _get_join_clauses(self.secondary_table, 
     837                                  self.local_side, self.remote_side, 
    838838                                  self.entity.table) 
    839839 
    840840    def get_prop_kwargs(self): 
    841         kwargs = {'secondary': self.secondary_table,  
     841        kwargs = {'secondary': self.secondary_table, 
    842842                  'uselist': self.uselist} 
    843843 
     
    856856    def is_inverse(self, other): 
    857857        return super(ManyToMany, self).is_inverse(other) and \ 
    858                (self.user_tablename == other.user_tablename or  
     858               (self.user_tablename == other.user_tablename or 
    859859                (not self.user_tablename and not other.user_tablename)) 
    860860 
     
    895895    # one of the joins (primary or secondary), or we assume the current 
    896896    # columns match because the columns for this join were not given and we 
    897     # know the other join is either not used (is None) or has an explicit  
     897    # know the other join is either not used (is None) or has an explicit 
    898898    # match. 
    899          
     899 
    900900#TODO: rewrite this. Even with the comment, I don't even understand it myself. 
    901901    for cols, constraint in constraint_map.iteritems(): 
    902         if cols == cols1 or (cols != cols2 and  
     902        if cols == cols1 or (cols != cols2 and 
    903903                             not cols1 and (cols2 in constraint_map or 
    904904                                            cols2 is None)): 
     
    916916    def handler(entity, name, *args, **kwargs): 
    917917        if 'through' in kwargs and 'via' in kwargs: 
    918             setattr(entity, name,  
    919                     association_proxy(kwargs.pop('through'),  
     918            setattr(entity, name, 
     919                    association_proxy(kwargs.pop('through'), 
    920920                                      kwargs.pop('via'), 
    921921                                      **kwargs))