root / elixir / trunk / elixir / relationships.py @ 347

Revision 347, 37.8 kB (checked in by ged, 6 years ago)

removed trailing spaces

Line 
1'''
2This module provides support for defining relationships between your Elixir
3entities.  Elixir currently supports two syntaxes to do so: the default
4`Attribute-based syntax`_ which supports the following types of relationships:
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_.
8
9======================
10Attribute-based syntax
11======================
12
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
18for a list of their specific keyword arguments. At this point, we'll just note
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
22documentation <http://www.sqlalchemy.org/docs/04/sqlalchemy_orm.html
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``,
29``remote_side``, ``secondary``, ``primaryjoin`` and ``secondaryjoin``.
30
31Additionally, if you want a bidirectionnal relationship, you should define the
32inverse relationship on the other entity explicitly (as opposed to how
33SQLAlchemy's backrefs are defined). In non-ambiguous situations, Elixir will
34match relationships together automatically. If there are several relationships
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``
38keyword argument.
39
40Here is a detailed explanation of each relation type:
41
42`ManyToOne`
43-----------
44
45Describes the child's side of a parent-child relationship.  For example,
46a `Pet` object may belong to its owner, who is a `Person`.  This could be
47expressed like so:
48
49.. sourcecode:: python
50
51    class Pet(Entity):
52        owner = ManyToOne('Person')
53
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
57foreign key referencing the `id` column of the `Person` entity.
58
59In addition to the keyword arguments inherited from SQLAlchemy's relation
60function, ``ManyToOne`` relationships accept the following optional arguments
61which will be directed to the created column:
62
63+----------------------+------------------------------------------------------+
64| Option Name          | Description                                          |
65+======================+======================================================+
66| ``colname``          | Specify a custom column name.                        |
67+----------------------+------------------------------------------------------+
68| ``required``         | Specify whether or not this field can be set to None |
69|                      | (left without a value). Defaults to ``False``,       |
70|                      | unless the field is a primary key.                   |
71+----------------------+------------------------------------------------------+
72| ``primary_key``      | Specify whether or not the column(s) created by this |
73|                      | relationship should act as a primary_key.            |
74|                      | Defaults to ``False``.                               |
75+----------------------+------------------------------------------------------+
76| ``column_kwargs``    | A dictionary holding any other keyword argument you  |
77|                      | might want to pass to the Column.                    |
78+----------------------+------------------------------------------------------+
79
80The following optional arguments are also supported to customize the
81ForeignKeyConstraint that is created:
82
83+----------------------+------------------------------------------------------+
84| Option Name          | Description                                          |
85+======================+======================================================+
86| ``use_alter``        | If True, SQLAlchemy will add the constraint in a     |
87|                      | second SQL statement (as opposed to within the       |
88|                      | create table statement). This permits to define      |
89|                      | tables with a circular foreign key dependency        |
90|                      | between them.                                        |
91+----------------------+------------------------------------------------------+
92| ``ondelete``         | Value for the foreign key constraint ondelete clause.|
93|                      | May be one of: ``cascade``, ``restrict``,            |
94|                      | ``set null``, or ``set default``.                    |
95+----------------------+------------------------------------------------------+
96| ``onupdate``         | Value for the foreign key constraint onupdate clause.|
97|                      | May be one of: ``cascade``, ``restrict``,            |
98|                      | ``set null``, or ``set default``.                    |
99+----------------------+------------------------------------------------------+
100| ``constraint_kwargs``| A dictionary holding any other keyword argument you  |
101|                      | might want to pass to the Constraint.                |
102+----------------------+------------------------------------------------------+
103
104Additionally, Elixir supports the belongs_to_ statement as an alternative,
105DSL-based, syntax to define ManyToOne_ relationships.
106
107
108`OneToMany`
109-----------
110
111Describes the parent's side of a parent-child relationship when there can be
112several children.  For example, a `Person` object has many children, each of
113them being a `Person`. This could be expressed like so:
114
115.. sourcecode:: python
116
117    class Person(Entity):
118        parent = ManyToOne('Person')
119        children = OneToMany('Person')
120
121Note that a ``OneToMany`` relationship **cannot exist** without a
122corresponding ``ManyToOne`` relationship in the other way. This is because the
123``OneToMany`` relationship needs the foreign key created by the ``ManyToOne``
124relationship.
125
126In addition to keyword arguments inherited from SQLAlchemy, ``OneToMany``
127relationships accept the following optional (keyword) arguments:
128
129+--------------------+--------------------------------------------------------+
130| Option Name        | Description                                            |
131+====================+========================================================+
132| ``order_by``       | Specify which field(s) should be used to sort the      |
133|                    | results given by accessing the relation field. You can |
134|                    | either use a string or a list of strings, each         |
135|                    | corresponding to the name of a field in the target     |
136|                    | entity. These field names can optionally be prefixed   |
137|                    | by a minus (for descending order).                     |
138+--------------------+--------------------------------------------------------+
139
140Additionally, Elixir supports an alternate, DSL-based, syntax to define
141OneToMany_ relationships, with the has_many_ statement.
142
143Also, as for standard SQLAlchemy relations, the ``order_by`` keyword argument
144
145
146`OneToOne`
147----------
148
149Describes the parent's side of a parent-child relationship when there is only
150one child.  For example, a `Car` object has one gear stick, which is
151represented as a `GearStick` object. This could be expressed like so:
152
153.. sourcecode:: python
154
155    class Car(Entity):
156        gear_stick = OneToOne('GearStick', inverse='car')
157
158    class GearStick(Entity):
159        car = ManyToOne('Car')
160
161Note that a ``OneToOne`` relationship **cannot exist** without a corresponding
162``ManyToOne`` relationship in the other way. This is because the ``OneToOne``
163relationship needs the foreign_key created by the ``ManyToOne`` relationship.
164
165Additionally, Elixir supports an alternate, DSL-based, syntax to define
166OneToOne_ relationships, with the has_one_ statement.
167
168
169`ManyToMany`
170------------
171
172Describes a relationship in which one kind of entity can be related to several
173objects of the other kind but the objects of that other kind can be related to
174several objects of the first kind.  For example, an `Article` can have several
175tags, but the same `Tag` can be used on several articles.
176
177.. sourcecode:: python
178
179    class Article(Entity):
180        tags = ManyToMany('Tag')
181
182    class Tag(Entity):
183        articles = ManyToMany('Article')
184
185Behind the scene, the ``ManyToMany`` relationship will
186automatically create an intermediate table to host its data.
187
188Note that you don't necessarily need to define the inverse relationship.  In
189our example, even though we want tags to be usable on several articles, we
190might not be interested in which articles correspond to a particular tag.  In
191that case, we could have omitted the `Tag` side of the relationship.
192
193If the entity containing your ``ManyToMany`` relationship is
194autoloaded, you **must** specify at least one of either the ``remote_side`` or
195``local_side`` argument.
196
197In addition to keyword arguments inherited from SQLAlchemy, ``ManyToMany``
198relationships accept the following optional (keyword) arguments:
199
200+--------------------+--------------------------------------------------------+
201| Option Name        | Description                                            |
202+====================+========================================================+
203| ``tablename``      | Specify a custom name for the intermediary table. This |
204|                    | can be used both when the tables needs to be created   |
205|                    | and when the table is autoloaded/reflected from the    |
206|                    | database.                                              |
207+--------------------+--------------------------------------------------------+
208| ``remote_side``    | A column name or list of column names specifying       |
209|                    | which column(s) in the intermediary table are used     |
210|                    | for the "remote" part of a self-referential            |
211|                    | relationship. This argument has an effect only when    |
212|                    | your entities are autoloaded.                          |
213+--------------------+--------------------------------------------------------+
214| ``local_side``     | A column name or list of column names specifying       |
215|                    | which column(s) in the intermediary table are used     |
216|                    | for the "local" part of a self-referential             |
217|                    | relationship. This argument has an effect only when    |
218|                    | your entities are autoloaded.                          |
219+--------------------+--------------------------------------------------------+
220| ``order_by``       | Specify which field(s) should be used to sort the      |
221|                    | results given by accessing the relation field. You can |
222|                    | either use a string or a list of strings, each         |
223|                    | corresponding to the name of a field in the target     |
224|                    | entity. These field names can optionally be prefixed   |
225|                    | by a minus (for descending order).                     |
226+--------------------+--------------------------------------------------------+
227| ``column_format``  | Specify an alternate format string for naming the      |
228|                    | columns in the mapping table.  The default value is    |
229|                    | defined in ``elixir.options.M2MCOL_NAMEFORMAT``.  You  |
230|                    | will be passed ``tablename``, ``key``, and ``entity``  |
231|                    | as arguments to the format string.                     |
232+--------------------+--------------------------------------------------------+
233
234
235================
236DSL-based syntax
237================
238
239The following DSL statements provide an alternative way to define relationships
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).
243
244`belongs_to`
245------------
246
247The ``belongs_to`` statement is the DSL syntax equivalent to the ManyToOne_
248relationship. As such, it supports all the same arguments as ManyToOne_
249relationships.
250
251.. sourcecode:: python
252
253    class Pet(Entity):
254        belongs_to('feeder', of_kind='Person')
255        belongs_to('owner', of_kind='Person', colname="owner_id")
256
257
258`has_many`
259----------
260
261The ``has_many`` statement is the DSL syntax equivalent to the OneToMany_
262relationship. As such, it supports all the same arguments as OneToMany_
263relationships.
264
265.. sourcecode:: python
266
267    class Person(Entity):
268        belongs_to('parent', of_kind='Person')
269        has_many('children', of_kind='Person')
270
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
274``has_and_belongs_to_many`` statement.  Here is an example:
275
276.. sourcecode:: python
277
278    class Person(Entity):
279        has_field('name', Unicode)
280        has_many('assignments', of_kind='Assignment')
281        has_many('projects', through='assignments', via='project')
282
283    class Assignment(Entity):
284        has_field('start_date', DateTime)
285        belongs_to('person', of_kind='Person')
286        belongs_to('project', of_kind='Project')
287
288    class Project(Entity):
289        has_field('title', Unicode)
290        has_many('assignments', of_kind='Assignment')
291
292In the above example, a `Person` has many `projects` through the `Assignment`
293relationship object, via a `project` attribute.
294
295
296`has_one`
297---------
298
299The ``has_one`` statement is the DSL syntax equivalent to the OneToOne_
300relationship. As such, it supports all the same arguments as OneToOne_
301relationships.
302
303.. sourcecode:: python
304
305    class Car(Entity):
306        has_one('gear_stick', of_kind='GearStick', inverse='car')
307
308    class GearStick(Entity):
309        belongs_to('car', of_kind='Car')
310
311
312`has_and_belongs_to_many`
313-------------------------
314
315The ``has_and_belongs_to_many`` statement is the DSL syntax equivalent to the
316ManyToMany_ relationship. As such, it supports all the same arguments as
317ManyToMany_ relationships.
318
319.. sourcecode:: python
320
321    class Article(Entity):
322        has_and_belongs_to_many('tags', of_kind='Tag')
323
324    class Tag(Entity):
325        has_and_belongs_to_many('articles', of_kind='Article')
326
327'''
328
329import sys
330
331from sqlalchemy         import ForeignKeyConstraint, Column, \
332                               Table, and_
333from sqlalchemy.orm     import relation, backref
334from sqlalchemy.ext.associationproxy import association_proxy
335
336import options
337from elixir.statements  import ClassMutator
338from elixir.fields      import Field
339from elixir.properties  import Property
340from elixir.entity      import EntityDescriptor, EntityMeta
341
342
343__doc_all__ = []
344
345class Relationship(Property):
346    '''
347    Base class for relationships.
348    '''
349
350    def __init__(self, of_kind, *args, **kwargs):
351        super(Relationship, self).__init__()
352
353        self.inverse_name = kwargs.pop('inverse', None)
354
355        self.of_kind = of_kind
356
357        self._target = None
358        self._inverse = None
359
360        self.property = None # sqlalchemy property
361        self.backref = None  # sqlalchemy backref
362
363        #TODO: unused for now
364        self.args = args
365        self.kwargs = kwargs
366
367    def attach(self, entity, name):
368        super(Relationship, self).attach(entity, name)
369        entity._descriptor.relationships.append(self)
370
371    def create_pk_cols(self):
372        self.create_keys(True)
373
374    def create_non_pk_cols(self):
375        self.create_keys(False)
376
377    def create_keys(self, pk):
378        '''
379        Subclasses (ie. concrete relationships) may override this method to
380        create foreign keys.
381        '''
382
383    def create_tables(self):
384        '''
385        Subclasses (ie. concrete relationships) may override this method to
386        create secondary tables.
387        '''
388
389    def create_properties(self):
390        '''
391        Subclasses (ie. concrete relationships) may override this method to
392        add properties to the involved entities.
393        '''
394        if self.property or self.backref:
395            return
396
397        kwargs = {}
398        if self.inverse:
399            # check if the inverse was already processed (and thus has already
400            # defined a backref we can use)
401            if self.inverse.backref:
402                kwargs['backref'] = self.inverse.backref
403            else:
404                kwargs = self.get_prop_kwargs()
405
406                # SQLAlchemy doesn't like when 'secondary' is both defined on
407                # the relation and the backref
408                kwargs.pop('secondary', None)
409
410                # define backref for use by the inverse
411                self.backref = backref(self.name, **kwargs)
412                return
413
414        kwargs.update(self.get_prop_kwargs())
415        self.property = relation(self.target, **kwargs)
416        self.entity._descriptor.add_property(self.name, self.property)
417
418    def target(self):
419        if not self._target:
420            if isinstance(self.of_kind, EntityMeta):
421                self._target = self.of_kind
422            else:
423                collection = self.entity._descriptor.collection
424                self._target = collection.resolve(self.of_kind, self.entity)
425        return self._target
426    target = property(target)
427
428    def inverse(self):
429        if not self._inverse:
430            if self.inverse_name:
431                desc = self.target._descriptor
432                inverse = desc.find_relationship(self.inverse_name)
433                if inverse is None:
434                    raise Exception(
435                              "Couldn't find a relationship named '%s' in "
436                              "entity '%s' or its parent entities."
437                              % (self.inverse_name, self.target.__name__))
438                assert self.match_type_of(inverse)
439            else:
440                inverse = self.target._descriptor.get_inverse_relation(self)
441
442            if inverse:
443                self._inverse = inverse
444                inverse._inverse = self
445
446        return self._inverse
447    inverse = property(inverse)
448
449    def match_type_of(self, other):
450        return False
451
452    def is_inverse(self, other):
453        return other is not self and \
454               self.match_type_of(other) and \
455               self.entity == other.target and \
456               other.entity == self.target and \
457               (self.inverse_name == other.name or not self.inverse_name) and \
458               (other.inverse_name == self.name or not other.inverse_name)
459
460
461class ManyToOne(Relationship):
462    '''
463
464    '''
465
466    def __init__(self, *args, **kwargs):
467        self.colname = kwargs.pop('colname', [])
468        if self.colname and not isinstance(self.colname, list):
469            self.colname = [self.colname]
470
471        self.column_kwargs = kwargs.pop('column_kwargs', {})
472        if 'required' in kwargs:
473            self.column_kwargs['nullable'] = not kwargs.pop('required')
474        if 'primary_key' in kwargs:
475            self.column_kwargs['primary_key'] = kwargs.pop('primary_key')
476        # by default, columns created will have an index.
477        self.column_kwargs.setdefault('index', True)
478
479        self.constraint_kwargs = kwargs.pop('constraint_kwargs', {})
480        if 'use_alter' in kwargs:
481            self.constraint_kwargs['use_alter'] = kwargs.pop('use_alter')
482
483        if 'ondelete' in kwargs:
484            self.constraint_kwargs['ondelete'] = kwargs.pop('ondelete')
485        if 'onupdate' in kwargs:
486            self.constraint_kwargs['onupdate'] = kwargs.pop('onupdate')
487
488        self.foreign_key = list()
489        self.primaryjoin_clauses = list()
490
491        super(ManyToOne, self).__init__(*args, **kwargs)
492
493    def match_type_of(self, other):
494        return isinstance(other, (OneToMany, OneToOne))
495
496    def create_keys(self, pk):
497        '''
498        Find all primary keys on the target and create foreign keys on the
499        source accordingly.
500        '''
501
502        if self.foreign_key:
503            return
504
505        if self.column_kwargs.get('primary_key', False) != pk:
506            return
507
508        source_desc = self.entity._descriptor
509        #TODO: make this work if target is a pure SA-mapped class
510        # for that, I need:
511        # - the list of primary key columns of the target table (type and name)
512        # - the name of the target table
513        target_desc = self.target._descriptor
514        #make sure the target has all its pk setup up
515        target_desc.create_pk_cols()
516
517        if source_desc.autoload:
518            #TODO: test if this works when colname is a list
519
520            if self.colname:
521                self.primaryjoin_clauses = \
522                    _get_join_clauses(self.entity.table,
523                                      self.colname, None,
524                                      self.target.table)[0]
525                if not self.primaryjoin_clauses:
526                    raise Exception(
527                        "Couldn't find a foreign key constraint in table "
528                        "'%s' using the following columns: %s."
529                        % (self.entity.table.name, ', '.join(self.colname)))
530        else:
531            fk_refcols = list()
532            fk_colnames = list()
533
534            if self.colname and \
535               len(self.colname) != len(target_desc.primary_keys):
536                raise Exception(
537                        "The number of column names provided in the colname "
538                        "keyword argument of the '%s' relationship of the "
539                        "'%s' entity is not the same as the number of columns "
540                        "of the primary key of '%s'."
541                        % (self.name, self.entity.__name__,
542                           self.target.__name__))
543
544            pks = target_desc.primary_keys
545            if not pks:
546                raise Exception("No primary key found in target table ('%s') "
547                                "for the '%s' relationship of the '%s' entity."
548                                % (self.target.tablename, self.name,
549                                   self.entity.__name__))
550
551            for key_num, pk_col in enumerate(pks):
552                if self.colname:
553                    colname = self.colname[key_num]
554                else:
555                    colname = options.FKCOL_NAMEFORMAT % \
556                              {'relname': self.name,
557                               'key': pk_col.key}
558
559                # We can't add the column to the table directly as the table
560                # might not be created yet.
561                col = Column(colname, pk_col.type, **self.column_kwargs)
562                source_desc.add_column(col)
563
564                # Build the list of local columns which will be part of
565                # the foreign key
566                self.foreign_key.append(col)
567
568                # Store the names of those columns
569                fk_colnames.append(col.key)
570
571                # Build the list of column "paths" the foreign key will
572                # point to
573                target_path = "%s.%s" % (target_desc.tablename, pk_col.key)
574                schema = target_desc.table_options.get('schema', None)
575                if schema is not None:
576                    target_path = "%s.%s" % (schema, target_path)
577                fk_refcols.append(target_path)
578
579                # Build up the primary join. This is needed when you have
580                # several belongs_to relationships between two objects
581                self.primaryjoin_clauses.append(col == pk_col)
582
583            if 'name' not in self.constraint_kwargs:
584                # In some databases (at least MySQL) the constraint name needs
585                # to be unique for the whole database, instead of per table.
586                fk_name = options.CONSTRAINT_NAMEFORMAT % \
587                          {'tablename': source_desc.tablename,
588                           'colnames': '_'.join(fk_colnames)}
589                self.constraint_kwargs['name'] = fk_name
590
591            source_desc.add_constraint(
592                ForeignKeyConstraint(fk_colnames, fk_refcols,
593                                     **self.constraint_kwargs))
594
595    def get_prop_kwargs(self):
596        kwargs = {'uselist': False}
597
598        if self.entity.table is self.target.table:
599            kwargs['remote_side'] = \
600                [col for col in self.target.table.primary_key.columns]
601
602        if self.primaryjoin_clauses:
603            kwargs['primaryjoin'] = and_(*self.primaryjoin_clauses)
604
605        kwargs.update(self.kwargs)
606
607        return kwargs
608
609
610class OneToOne(Relationship):
611    uselist = False
612
613    def match_type_of(self, other):
614        return isinstance(other, ManyToOne)
615
616    def create_keys(self, pk):
617        # make sure an inverse relationship exists
618        if self.inverse is None:
619            raise Exception(
620                      "Couldn't find any relationship in '%s' which "
621                      "match as inverse of the '%s' relationship "
622                      "defined in the '%s' entity. If you are using "
623                      "inheritance you "
624                      "might need to specify inverse relationships "
625                      "manually by using the inverse keyword."
626                      % (self.target.__name__, self.name,
627                         self.entity.__name__))
628
629    def get_prop_kwargs(self):
630        kwargs = {'uselist': self.uselist}
631
632        #TODO: for now, we don't break any test if we remove those 2 lines.
633        # So, we should either complete the selfref test to prove that they
634        # are indeed useful, or remove them. It might be they are indeed
635        # useless because of the primaryjoin, and that the remote_side is
636        # already setup in the other way (belongs_to).
637        if self.entity.table is self.target.table:
638            #FIXME: IF this code is of any use, it will probably break for
639            # autoloaded tables
640            kwargs['remote_side'] = self.inverse.foreign_key
641
642        if self.inverse.primaryjoin_clauses:
643            kwargs['primaryjoin'] = and_(*self.inverse.primaryjoin_clauses)
644
645        kwargs.update(self.kwargs)
646
647        return kwargs
648
649
650class OneToMany(OneToOne):
651    uselist = True
652
653    def get_prop_kwargs(self):
654        kwargs = super(OneToMany, self).get_prop_kwargs()
655
656        if 'order_by' in kwargs:
657            kwargs['order_by'] = \
658                self.target._descriptor.translate_order_by(
659                    kwargs['order_by'])
660
661        return kwargs
662
663
664class ManyToMany(Relationship):
665    uselist = True
666
667    def __init__(self, *args, **kwargs):
668        self.user_tablename = kwargs.pop('tablename', None)
669        self.local_side = kwargs.pop('local_side', [])
670        if self.local_side and not isinstance(self.local_side, list):
671            self.local_side = [self.local_side]
672        self.remote_side = kwargs.pop('remote_side', [])
673        if self.remote_side and not isinstance(self.remote_side, list):
674            self.remote_side = [self.remote_side]
675        self.ondelete = kwargs.pop('ondelete', None)
676        self.onupdate = kwargs.pop('onupdate', None)
677        self.column_format = kwargs.pop('column_format', options.M2MCOL_NAMEFORMAT)
678
679        self.secondary_table = kwargs.pop('table', None)
680        self.primaryjoin_clauses = list()
681        self.secondaryjoin_clauses = list()
682
683        super(ManyToMany, self).__init__(*args, **kwargs)
684
685    def match_type_of(self, other):
686        return isinstance(other, ManyToMany)
687
688    def create_tables(self):
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
691        # autoloaded entities
692        if self.secondary_table:
693            return
694
695        if self.inverse:
696            if self.inverse.secondary_table:
697                self.secondary_table = self.inverse.secondary_table
698                self.primaryjoin_clauses = self.inverse.secondaryjoin_clauses
699                self.secondaryjoin_clauses = self.inverse.primaryjoin_clauses
700                return
701
702        e1_desc = self.entity._descriptor
703        e2_desc = self.target._descriptor
704
705        e1_schema = e1_desc.table_options.get('schema', None)
706        e2_schema = e2_desc.table_options.get('schema', None)
707        assert e1_schema == e2_schema, \
708               "Schema %r for entity %s differs from schema %r of entity %s" \
709               % (e1_schema, self.entity.__name__,
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
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.
720        source_part = "%s_%s" % (e1_desc.tablename, self.name)
721
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
724        # can be defined without an inverse.
725        if self.inverse:
726            target_part = "%s_%s" % (e2_desc.tablename, self.inverse.name)
727        else:
728            target_part = e2_desc.tablename
729
730        if self.user_tablename:
731            tablename = self.user_tablename
732        else:
733            # We need to keep the table name consistent (independant of
734            # whether this relation or its inverse is setup first).
735            if self.inverse and e1_desc.tablename < e2_desc.tablename:
736                tablename = "%s__%s" % (target_part, source_part)
737            else:
738                tablename = "%s__%s" % (source_part, target_part)
739
740        if e1_desc.autoload:
741            self._reflect_table(tablename)
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
745            # target's table
746
747            # In some databases (at least MySQL) the constraint names need
748            # to be unique for the whole database, instead of per table.
749            source_fk_name = "%s_fk" % source_part
750            if self.inverse:
751                target_fk_name = "%s_fk" % target_part
752            else:
753                target_fk_name = "%s_inverse_fk" % source_part
754
755            columns = list()
756            constraints = list()
757
758            joins = (self.primaryjoin_clauses, self.secondaryjoin_clauses)
759            for num, desc, fk_name, m2m in (
760                    (0, e1_desc, source_fk_name, self),
761                    (1, e2_desc, target_fk_name, self.inverse)):
762                fk_colnames = list()
763                fk_refcols = list()
764
765                for pk_col in desc.primary_keys:
766                    colname = self.column_format % \
767                              {'tablename': desc.tablename,
768                               'key': pk_col.key,
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
773                    # don't end up with twice the same column name.
774                    if self.entity is self.target:
775                        colname += str(num + 1)
776
777                    col = Column(colname, pk_col.type, primary_key=True)
778                    columns.append(col)
779
780                    # Build the list of local columns which will be part
781                    # of the foreign key.
782                    fk_colnames.append(colname)
783
784                    # Build the list of column "paths" the foreign key will
785                    # point to
786                    target_path = "%s.%s" % (desc.tablename, pk_col.key)
787                    schema = desc.table_options.get('schema', None)
788                    if schema is not None:
789                        target_path = "%s.%s" % (schema, target_path)
790                    fk_refcols.append(target_path)
791
792                    # Build join clauses (in case we have a self-ref)
793                    if self.entity is self.target:
794                        joins[num].append(col == pk_col)
795
796                onupdate = m2m and m2m.onupdate
797                ondelete = m2m and m2m.ondelete
798
799                constraints.append(
800                    ForeignKeyConstraint(fk_colnames, fk_refcols,
801                                         name=fk_name, onupdate=onupdate,
802                                         ondelete=ondelete))
803
804            args = columns + constraints
805
806            self.secondary_table = Table(tablename, e1_desc.metadata,
807                                         schema=e1_schema, *args)
808
809    def _reflect_table(self, tablename):
810        if not self.target._descriptor.autoload:
811            raise Exception(
812                "Entity '%s' is autoloaded and its '%s' "
813                "ManyToMany relationship points to "
814                "the '%s' entity which is not autoloaded"
815                % (self.entity.__name__, self.name,
816                   self.target.__name__))
817
818        self.secondary_table = Table(tablename,
819                                     self.entity._descriptor.metadata,
820                                     autoload=True)
821
822        # In the case we have a self-reference, we need to build join clauses
823        if self.entity is self.target:
824            #CHECKME: maybe we should try even harder by checking if that
825            # information was defined on the inverse relationship)
826            if not self.local_side and not self.remote_side:
827                raise Exception(
828                    "Self-referential ManyToMany "
829                    "relationships in autoloaded entities need to have at "
830                    "least one of either 'local_side' or 'remote_side' "
831                    "argument specified. The '%s' relationship in the '%s' "
832                    "entity doesn't have either."
833                    % (self.name, self.entity.__name__))
834
835            self.primaryjoin_clauses, self.secondaryjoin_clauses = \
836                _get_join_clauses(self.secondary_table,
837                                  self.local_side, self.remote_side,
838                                  self.entity.table)
839
840    def get_prop_kwargs(self):
841        kwargs = {'secondary': self.secondary_table,
842                  'uselist': self.uselist}
843
844        if self.target is self.entity:
845            kwargs['primaryjoin'] = and_(*self.primaryjoin_clauses)
846            kwargs['secondaryjoin'] = and_(*self.secondaryjoin_clauses)
847
848        kwargs.update(self.kwargs)
849
850        if 'order_by' in kwargs:
851            kwargs['order_by'] = \
852                self.target._descriptor.translate_order_by(kwargs['order_by'])
853
854        return kwargs
855
856    def is_inverse(self, other):
857        return super(ManyToMany, self).is_inverse(other) and \
858               (self.user_tablename == other.user_tablename or
859                (not self.user_tablename and not other.user_tablename))
860
861
862def _get_join_clauses(local_table, local_cols1, local_cols2, target_table):
863    primary_join, secondary_join = [], []
864    cols1 = local_cols1[:]
865    cols1.sort()
866    cols1 = tuple(cols1)
867
868    if local_cols2 is not None:
869        cols2 = local_cols2[:]
870        cols2.sort()
871        cols2 = tuple(cols2)
872    else:
873        cols2 = None
874
875    # Build a map of fk constraints pointing to the correct table.
876    # The map is indexed on the local col names.
877    constraint_map = {}
878    for constraint in local_table.constraints:
879        if isinstance(constraint, ForeignKeyConstraint):
880            use_constraint = True
881            fk_colnames = []
882
883            # if all columns point to the correct table, we use the constraint
884            for fk in constraint.elements:
885                if fk.references(target_table):
886                    # local column key
887                    fk_colnames.append(fk.parent.key)
888                else:
889                    use_constraint = False
890            if use_constraint:
891                fk_colnames.sort()
892                constraint_map[tuple(fk_colnames)] = constraint
893
894    # Either the fk column names match explicitely with the columns given for
895    # one of the joins (primary or secondary), or we assume the current
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
898    # match.
899
900#TODO: rewrite this. Even with the comment, I don't even understand it myself.
901    for cols, constraint in constraint_map.iteritems():
902        if cols == cols1 or (cols != cols2 and
903                             not cols1 and (cols2 in constraint_map or
904                                            cols2 is None)):
905            join = primary_join
906        elif cols == cols2 or (cols2 == () and cols1 in constraint_map):
907            join = secondary_join
908        else:
909            continue
910        for fk in constraint.elements:
911            join.append(fk.parent == fk.column)
912    return primary_join, secondary_join
913
914
915def rel_mutator_handler(target):
916    def handler(entity, name, *args, **kwargs):
917        if 'through' in kwargs and 'via' in kwargs:
918            setattr(entity, name,
919                    association_proxy(kwargs.pop('through'),
920                                      kwargs.pop('via'),
921                                      **kwargs))
922            return
923        elif 'through' in kwargs or 'via' in kwargs:
924            raise Exception("'through' and 'via' relationship keyword "
925                            "arguments should be used in combination.")
926        rel = target(kwargs.pop('of_kind'), *args, **kwargs)
927        rel.attach(entity, name)
928    return handler
929
930
931belongs_to = ClassMutator(rel_mutator_handler(ManyToOne))
932has_one = ClassMutator(rel_mutator_handler(OneToOne))
933has_many = ClassMutator(rel_mutator_handler(OneToMany))
934has_and_belongs_to_many = ClassMutator(rel_mutator_handler(ManyToMany))
Note: See TracBrowser for help on using the browser.