Changeset 347 for elixir/trunk/elixir/relationships.py
- Timestamp:
- 06/19/08 15:35:21 (5 years ago)
- Files:
-
- 1 modified
-
elixir/trunk/elixir/relationships.py (modified) (47 diffs)
Legend:
- Unmodified
- Added
- Removed
-
elixir/trunk/elixir/relationships.py
r346 r347 1 1 ''' 2 This module provides support for defining relationships between your Elixir 2 This module provides support for defining relationships between your Elixir 3 3 entities. Elixir currently supports two syntaxes to do so: the default 4 4 `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_. 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_. 8 8 9 9 ====================== … … 11 11 ====================== 12 12 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 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 18 18 for 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 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 22 22 documentation <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 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``, 29 29 ``remote_side``, ``secondary``, ``primaryjoin`` and ``secondaryjoin``. 30 30 31 31 Additionally, if you want a bidirectionnal relationship, you should define the 32 32 inverse relationship on the other entity explicitly (as opposed to how 33 SQLAlchemy's backrefs are defined). In non-ambiguous situations, Elixir will 33 SQLAlchemy's backrefs are defined). In non-ambiguous situations, Elixir will 34 34 match 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`` 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`` 38 38 keyword argument. 39 39 … … 43 43 ----------- 44 44 45 Describes the child's side of a parent-child relationship. For example, 45 Describes the child's side of a parent-child relationship. For example, 46 46 a `Pet` object may belong to its owner, who is a `Person`. This could be 47 47 expressed like so: … … 52 52 owner = ManyToOne('Person') 53 53 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 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 57 57 foreign key referencing the `id` column of the `Person` entity. 58 58 59 In addition to the keyword arguments inherited from SQLAlchemy's relation 59 In addition to the keyword arguments inherited from SQLAlchemy's relation 60 60 function, ``ManyToOne`` relationships accept the following optional arguments 61 61 which will be directed to the created column: … … 102 102 +----------------------+------------------------------------------------------+ 103 103 104 Additionally, Elixir supports the belongs_to_ statement as an alternative, 104 Additionally, Elixir supports the belongs_to_ statement as an alternative, 105 105 DSL-based, syntax to define ManyToOne_ relationships. 106 106 … … 119 119 children = OneToMany('Person') 120 120 121 Note that a ``OneToMany`` relationship **cannot exist** without a 121 Note that a ``OneToMany`` relationship **cannot exist** without a 122 122 corresponding ``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`` 124 124 relationship. 125 125 126 In addition to keyword arguments inherited from SQLAlchemy, ``OneToMany`` 126 In addition to keyword arguments inherited from SQLAlchemy, ``OneToMany`` 127 127 relationships accept the following optional (keyword) arguments: 128 128 … … 141 141 OneToMany_ relationships, with the has_many_ statement. 142 142 143 Also, as for standard SQLAlchemy relations, the ``order_by`` keyword argument 143 Also, as for standard SQLAlchemy relations, the ``order_by`` keyword argument 144 144 145 145 … … 148 148 149 149 Describes 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 150 one child. For example, a `Car` object has one gear stick, which is 151 151 represented as a `GearStick` object. This could be expressed like so: 152 152 … … 159 159 car = ManyToOne('Car') 160 160 161 Note that a ``OneToOne`` relationship **cannot exist** without a corresponding 161 Note that a ``OneToOne`` relationship **cannot exist** without a corresponding 162 162 ``ManyToOne`` relationship in the other way. This is because the ``OneToOne`` 163 163 relationship needs the foreign_key created by the ``ManyToOne`` relationship. … … 183 183 articles = ManyToMany('Article') 184 184 185 Behind the scene, the ``ManyToMany`` relationship will 185 Behind the scene, the ``ManyToMany`` relationship will 186 186 automatically create an intermediate table to host its data. 187 187 188 188 Note 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 189 our example, even though we want tags to be usable on several articles, we 190 190 might not be interested in which articles correspond to a particular tag. In 191 191 that case, we could have omitted the `Tag` side of the relationship. 192 192 193 If the entity containing your ``ManyToMany`` relationship is 193 If the entity containing your ``ManyToMany`` relationship is 194 194 autoloaded, you **must** specify at least one of either the ``remote_side`` or 195 195 ``local_side`` argument. 196 196 197 In addition to keyword arguments inherited from SQLAlchemy, ``ManyToMany`` 197 In addition to keyword arguments inherited from SQLAlchemy, ``ManyToMany`` 198 198 relationships accept the following optional (keyword) arguments: 199 199 … … 238 238 239 239 The 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). 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). 243 243 244 244 `belongs_to` … … 269 269 has_many('children', of_kind='Person') 270 270 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 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 274 274 ``has_and_belongs_to_many`` statement. Here is an example: 275 275 … … 313 313 ------------------------- 314 314 315 The ``has_and_belongs_to_many`` statement is the DSL syntax equivalent to the 315 The ``has_and_belongs_to_many`` statement is the DSL syntax equivalent to the 316 316 ManyToMany_ relationship. As such, it supports all the same arguments as 317 317 ManyToMany_ relationships. … … 347 347 Base class for relationships. 348 348 ''' 349 349 350 350 def __init__(self, of_kind, *args, **kwargs): 351 351 super(Relationship, self).__init__() … … 357 357 self._target = None 358 358 self._inverse = None 359 359 360 360 self.property = None # sqlalchemy property 361 361 self.backref = None # sqlalchemy backref … … 368 368 super(Relationship, self).attach(entity, name) 369 369 entity._descriptor.relationships.append(self) 370 370 371 371 def create_pk_cols(self): 372 372 self.create_keys(True) … … 377 377 def create_keys(self, pk): 378 378 ''' 379 Subclasses (ie. concrete relationships) may override this method to 379 Subclasses (ie. concrete relationships) may override this method to 380 380 create foreign keys. 381 381 ''' 382 382 383 383 def create_tables(self): 384 384 ''' 385 Subclasses (ie. concrete relationships) may override this method to 385 Subclasses (ie. concrete relationships) may override this method to 386 386 create secondary tables. 387 387 ''' 388 388 389 389 def create_properties(self): 390 390 ''' … … 425 425 return self._target 426 426 target = property(target) 427 427 428 428 def inverse(self): 429 429 if not self._inverse: … … 434 434 raise Exception( 435 435 "Couldn't find a relationship named '%s' in " 436 "entity '%s' or its parent entities." 436 "entity '%s' or its parent entities." 437 437 % (self.inverse_name, self.target.__name__)) 438 438 assert self.match_type_of(inverse) … … 443 443 self._inverse = inverse 444 444 inverse._inverse = self 445 445 446 446 return self._inverse 447 447 inverse = property(inverse) 448 448 449 449 def match_type_of(self, other): 450 450 return False … … 461 461 class ManyToOne(Relationship): 462 462 ''' 463 463 464 464 ''' 465 465 466 466 def __init__(self, *args, **kwargs): 467 467 self.colname = kwargs.pop('colname', []) … … 480 480 if 'use_alter' in kwargs: 481 481 self.constraint_kwargs['use_alter'] = kwargs.pop('use_alter') 482 482 483 483 if 'ondelete' in kwargs: 484 484 self.constraint_kwargs['ondelete'] = kwargs.pop('ondelete') 485 485 if 'onupdate' in kwargs: 486 486 self.constraint_kwargs['onupdate'] = kwargs.pop('onupdate') 487 487 488 488 self.foreign_key = list() 489 489 self.primaryjoin_clauses = list() 490 490 491 491 super(ManyToOne, self).__init__(*args, **kwargs) 492 492 493 493 def match_type_of(self, other): 494 494 return isinstance(other, (OneToMany, OneToOne)) … … 496 496 def create_keys(self, pk): 497 497 ''' 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 499 499 source accordingly. 500 500 ''' … … 508 508 source_desc = self.entity._descriptor 509 509 #TODO: make this work if target is a pure SA-mapped class 510 # for that, I need: 510 # for that, I need: 511 511 # - the list of primary key columns of the target table (type and name) 512 512 # - the name of the target table … … 520 520 if self.colname: 521 521 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, 524 524 self.target.table)[0] 525 525 if not self.primaryjoin_clauses: … … 539 539 "'%s' entity is not the same as the number of columns " 540 540 "of the primary key of '%s'." 541 % (self.name, self.entity.__name__, 541 % (self.name, self.entity.__name__, 542 542 self.target.__name__)) 543 543 … … 546 546 raise Exception("No primary key found in target table ('%s') " 547 547 "for the '%s' relationship of the '%s' entity." 548 % (self.target.tablename, self.name, 548 % (self.target.tablename, self.name, 549 549 self.entity.__name__)) 550 550 … … 554 554 else: 555 555 colname = options.FKCOL_NAMEFORMAT % \ 556 {'relname': self.name, 556 {'relname': self.name, 557 557 'key': pk_col.key} 558 558 … … 569 569 fk_colnames.append(col.key) 570 570 571 # Build the list of column "paths" the foreign key will 571 # Build the list of column "paths" the foreign key will 572 572 # point to 573 573 target_path = "%s.%s" % (target_desc.tablename, pk_col.key) … … 577 577 fk_refcols.append(target_path) 578 578 579 # Build up the primary join. This is needed when you have 579 # Build up the primary join. This is needed when you have 580 580 # several belongs_to relationships between two objects 581 581 self.primaryjoin_clauses.append(col == pk_col) 582 582 583 583 if 'name' not in self.constraint_kwargs: 584 584 # In some databases (at least MySQL) the constraint name needs 585 585 # to be unique for the whole database, instead of per table. 586 586 fk_name = options.CONSTRAINT_NAMEFORMAT % \ 587 {'tablename': source_desc.tablename, 587 {'tablename': source_desc.tablename, 588 588 'colnames': '_'.join(fk_colnames)} 589 589 self.constraint_kwargs['name'] = fk_name 590 590 591 591 source_desc.add_constraint( 592 592 ForeignKeyConstraint(fk_colnames, fk_refcols, … … 595 595 def get_prop_kwargs(self): 596 596 kwargs = {'uselist': False} 597 597 598 598 if self.entity.table is self.target.table: 599 599 kwargs['remote_side'] = \ … … 626 626 % (self.target.__name__, self.name, 627 627 self.entity.__name__)) 628 628 629 629 def get_prop_kwargs(self): 630 630 kwargs = {'uselist': self.uselist} 631 631 632 632 #TODO: for now, we don't break any test if we remove those 2 lines. 633 633 # So, we should either complete the selfref test to prove that they … … 639 639 # autoloaded tables 640 640 kwargs['remote_side'] = self.inverse.foreign_key 641 641 642 642 if self.inverse.primaryjoin_clauses: 643 643 kwargs['primaryjoin'] = and_(*self.inverse.primaryjoin_clauses) … … 650 650 class OneToMany(OneToOne): 651 651 uselist = True 652 652 653 653 def get_prop_kwargs(self): 654 654 kwargs = super(OneToMany, self).get_prop_kwargs() … … 688 688 def create_tables(self): 689 689 # 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 691 691 # autoloaded entities 692 692 if self.secondary_table: 693 693 return 694 694 695 695 if self.inverse: 696 696 if self.inverse.secondary_table: … … 707 707 assert e1_schema == e2_schema, \ 708 708 "Schema %r for entity %s differs from schema %r of entity %s" \ 709 % (e1_schema, self.entity.__name__, 709 % (e1_schema, self.entity.__name__, 710 710 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 714 714 # 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. 720 720 source_part = "%s_%s" % (e1_desc.tablename, self.name) 721 721 722 722 # 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 724 724 # can be defined without an inverse. 725 725 if self.inverse: … … 727 727 else: 728 728 target_part = e2_desc.tablename 729 729 730 730 if self.user_tablename: 731 731 tablename = self.user_tablename 732 732 else: 733 # We need to keep the table name consistent (independant of 733 # We need to keep the table name consistent (independant of 734 734 # whether this relation or its inverse is setup first). 735 735 if self.inverse and e1_desc.tablename < e2_desc.tablename: … … 741 741 self._reflect_table(tablename) 742 742 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 745 745 # target's table 746 746 747 # In some databases (at least MySQL) the constraint names need 747 # In some databases (at least MySQL) the constraint names need 748 748 # to be unique for the whole database, instead of per table. 749 749 source_fk_name = "%s_fk" % source_part … … 758 758 joins = (self.primaryjoin_clauses, self.secondaryjoin_clauses) 759 759 for num, desc, fk_name, m2m in ( 760 (0, e1_desc, source_fk_name, self), 760 (0, e1_desc, source_fk_name, self), 761 761 (1, e2_desc, target_fk_name, self.inverse)): 762 762 fk_colnames = list() 763 763 fk_refcols = list() 764 764 765 765 for pk_col in desc.primary_keys: 766 766 colname = self.column_format % \ … … 768 768 'key': pk_col.key, 769 769 '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 773 773 # don't end up with twice the same column name. 774 774 if self.entity is self.target: 775 775 colname += str(num + 1) 776 776 777 777 col = Column(colname, pk_col.type, primary_key=True) 778 778 columns.append(col) 779 779 780 # Build the list of local columns which will be part 780 # Build the list of local columns which will be part 781 781 # of the foreign key. 782 782 fk_colnames.append(colname) 783 783 784 # Build the list of column "paths" the foreign key will 784 # Build the list of column "paths" the foreign key will 785 785 # point to 786 786 target_path = "%s.%s" % (desc.tablename, pk_col.key) … … 793 793 if self.entity is self.target: 794 794 joins[num].append(col == pk_col) 795 795 796 796 onupdate = m2m and m2m.onupdate 797 797 ondelete = m2m and m2m.ondelete 798 798 799 799 constraints.append( 800 800 ForeignKeyConstraint(fk_colnames, fk_refcols, 801 name=fk_name, onupdate=onupdate, 801 name=fk_name, onupdate=onupdate, 802 802 ondelete=ondelete)) 803 803 804 804 args = columns + constraints 805 806 self.secondary_table = Table(tablename, e1_desc.metadata, 805 806 self.secondary_table = Table(tablename, e1_desc.metadata, 807 807 schema=e1_schema, *args) 808 808 … … 815 815 % (self.entity.__name__, self.name, 816 816 self.target.__name__)) 817 818 self.secondary_table = Table(tablename, 817 818 self.secondary_table = Table(tablename, 819 819 self.entity._descriptor.metadata, 820 820 autoload=True) … … 822 822 # In the case we have a self-reference, we need to build join clauses 823 823 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 825 825 # information was defined on the inverse relationship) 826 826 if not self.local_side and not self.remote_side: … … 834 834 835 835 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, 838 838 self.entity.table) 839 839 840 840 def get_prop_kwargs(self): 841 kwargs = {'secondary': self.secondary_table, 841 kwargs = {'secondary': self.secondary_table, 842 842 'uselist': self.uselist} 843 843 … … 856 856 def is_inverse(self, other): 857 857 return super(ManyToMany, self).is_inverse(other) and \ 858 (self.user_tablename == other.user_tablename or 858 (self.user_tablename == other.user_tablename or 859 859 (not self.user_tablename and not other.user_tablename)) 860 860 … … 895 895 # one of the joins (primary or secondary), or we assume the current 896 896 # 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 898 898 # match. 899 899 900 900 #TODO: rewrite this. Even with the comment, I don't even understand it myself. 901 901 for cols, constraint in constraint_map.iteritems(): 902 if cols == cols1 or (cols != cols2 and 902 if cols == cols1 or (cols != cols2 and 903 903 not cols1 and (cols2 in constraint_map or 904 904 cols2 is None)): … … 916 916 def handler(entity, name, *args, **kwargs): 917 917 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'), 920 920 kwargs.pop('via'), 921 921 **kwargs))
