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

Revision 497, 54.6 kB (checked in by ged, 4 years ago)

more cleanup after dropping py2.3/SA0.4

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/05/reference/orm/mapping.html
23#sqlalchemy.orm.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 name for the foreign key column(s). |
67|                      | This argument accepts either a single string or a    |
68|                      | list of strings. The number of strings passed must   |
69|                      | match the number of primary key columns of the target|
70|                      | entity. If this argument is not used, the name of the|
71|                      | column(s) is generated with the pattern              |
72|                      | defined in options.FKCOL_NAMEFORMAT, which is, by    |
73|                      | default: "%(relname)s_%(key)s", where relname is the |
74|                      | name of the ManyToOne relationship, and 'key' is the |
75|                      | name (key) of the primary column in the target       |
76|                      | entity. That's with, in the above Pet/owner example, |
77|                      | the name of the column would be: "owner_id".         |
78+----------------------+------------------------------------------------------+
79| ``required``         | Specify whether or not this field can be set to None |
80|                      | (left without a value). Defaults to ``False``,       |
81|                      | unless the field is a primary key.                   |
82+----------------------+------------------------------------------------------+
83| ``primary_key``      | Specify whether or not the column(s) created by this |
84|                      | relationship should act as a primary_key.            |
85|                      | Defaults to ``False``.                               |
86+----------------------+------------------------------------------------------+
87| ``column_kwargs``    | A dictionary holding any other keyword argument you  |
88|                      | might want to pass to the Column.                    |
89+----------------------+------------------------------------------------------+
90| ``target_column``    | Name (or list of names) of the target column(s).     |
91|                      | If this argument is not specified, the target entity |
92|                      | primary key column(s) are used.                      |
93+----------------------+------------------------------------------------------+
94
95The following optional arguments are also supported to customize the
96ForeignKeyConstraint that is created:
97
98+----------------------+------------------------------------------------------+
99| Option Name          | Description                                          |
100+======================+======================================================+
101| ``use_alter``        | If True, SQLAlchemy will add the constraint in a     |
102|                      | second SQL statement (as opposed to within the       |
103|                      | create table statement). This permits to define      |
104|                      | tables with a circular foreign key dependency        |
105|                      | between them.                                        |
106+----------------------+------------------------------------------------------+
107| ``ondelete``         | Value for the foreign key constraint ondelete clause.|
108|                      | May be one of: ``cascade``, ``restrict``,            |
109|                      | ``set null``, or ``set default``.                    |
110+----------------------+------------------------------------------------------+
111| ``onupdate``         | Value for the foreign key constraint onupdate clause.|
112|                      | May be one of: ``cascade``, ``restrict``,            |
113|                      | ``set null``, or ``set default``.                    |
114+----------------------+------------------------------------------------------+
115| ``constraint_kwargs``| A dictionary holding any other keyword argument you  |
116|                      | might want to pass to the Constraint.                |
117+----------------------+------------------------------------------------------+
118
119In some cases, you may want to declare the foreign key column explicitly,
120instead of letting it be generated automatically.  There are several reasons to
121that: it could be because you want to declare it with precise arguments and
122using column_kwargs makes your code ugly, or because the name of
123your column conflicts with the property name (in which case an error is
124thrown).  In those cases, you can use the ``field`` argument to specify an
125already-declared field to be used for the foreign key column.
126
127For example, for the Pet example above, if you want the database column
128(holding the foreign key) to be called 'owner', one should use the field
129parameter to specify the field manually.
130
131.. sourcecode:: python
132
133    class Pet(Entity):
134        owner_id = Field(Integer, colname='owner')
135        owner = ManyToOne('Person', field=owner_id)
136
137+----------------------+------------------------------------------------------+
138| Option Name          | Description                                          |
139+======================+======================================================+
140| ``field``            | Specify the previously-declared field to be used for |
141|                      | the foreign key column. Use of this parameter is     |
142|                      | mutually exclusive with the colname and column_kwargs|
143|                      | arguments.                                           |
144+----------------------+------------------------------------------------------+
145
146
147Additionally, Elixir supports the belongs_to_ statement as an alternative,
148DSL-based, syntax to define ManyToOne_ relationships.
149
150
151`OneToMany`
152-----------
153
154Describes the parent's side of a parent-child relationship when there can be
155several children.  For example, a `Person` object has many children, each of
156them being a `Person`. This could be expressed like so:
157
158.. sourcecode:: python
159
160    class Person(Entity):
161        parent = ManyToOne('Person')
162        children = OneToMany('Person')
163
164Note that a ``OneToMany`` relationship **cannot exist** without a
165corresponding ``ManyToOne`` relationship in the other way. This is because the
166``OneToMany`` relationship needs the foreign key created by the ``ManyToOne``
167relationship.
168
169In addition to keyword arguments inherited from SQLAlchemy, ``OneToMany``
170relationships accept the following optional (keyword) arguments:
171
172+--------------------+--------------------------------------------------------+
173| Option Name        | Description                                            |
174+====================+========================================================+
175| ``order_by``       | Specify which field(s) should be used to sort the      |
176|                    | results given by accessing the relation field.         |
177|                    | Note that this sort order is only applied when loading |
178|                    | objects from the database. Objects appended to the     |
179|                    | collection afterwards are not re-sorted in-memory on   |
180|                    | the fly.                                               |
181|                    | This argument accepts either a string or a list of     |
182|                    | strings, each corresponding to the name of a field in  |
183|                    | the target entity. These field names can optionally be |
184|                    | prefixed by a minus (for descending order).            |
185+--------------------+--------------------------------------------------------+
186| ``filter``         | Specify a filter criterion (as a clause element) for   |
187|                    | this relationship. This criterion will be and_'ed with |
188|                    | the normal join criterion (primaryjoin) generated by   |
189|                    | Elixir for the relationship. For example:              |
190|                    | boston_addresses = \                                   |
191|                    |  OneToMany('Address', filter=Address.city == 'Boston') |
192+--------------------+--------------------------------------------------------+
193
194Additionally, Elixir supports an alternate, DSL-based, syntax to define
195OneToMany_ relationships, with the has_many_ statement.
196
197
198`OneToOne`
199----------
200
201Describes the parent's side of a parent-child relationship when there is only
202one child.  For example, a `Car` object has one gear stick, which is
203represented as a `GearStick` object. This could be expressed like so:
204
205.. sourcecode:: python
206
207    class Car(Entity):
208        gear_stick = OneToOne('GearStick', inverse='car')
209
210    class GearStick(Entity):
211        car = ManyToOne('Car')
212
213Note that a ``OneToOne`` relationship **cannot exist** without a corresponding
214``ManyToOne`` relationship in the other way. This is because the ``OneToOne``
215relationship needs the foreign_key created by the ``ManyToOne`` relationship.
216
217Additionally, Elixir supports an alternate, DSL-based, syntax to define
218OneToOne_ relationships, with the has_one_ statement.
219
220
221`ManyToMany`
222------------
223
224Describes a relationship in which one kind of entity can be related to several
225objects of the other kind but the objects of that other kind can be related to
226several objects of the first kind.  For example, an `Article` can have several
227tags, but the same `Tag` can be used on several articles.
228
229.. sourcecode:: python
230
231    class Article(Entity):
232        tags = ManyToMany('Tag')
233
234    class Tag(Entity):
235        articles = ManyToMany('Article')
236
237Behind the scene, the ``ManyToMany`` relationship will automatically create an
238intermediate table to host its data.
239
240Note that you don't necessarily need to define the inverse relationship.  In
241our example, even though we want tags to be usable on several articles, we
242might not be interested in which articles correspond to a particular tag.  In
243that case, we could have omitted the `Tag` side of the relationship.
244
245If your ``ManyToMany`` relationship is self-referencial, the entity
246containing it is autoloaded (and you don't intend to specify both the
247primaryjoin and secondaryjoin arguments manually), you must specify at least
248one of either the ``remote_colname`` or ``local_colname`` argument.
249
250In addition to keyword arguments inherited from SQLAlchemy, ``ManyToMany``
251relationships accept the following optional (keyword) arguments:
252
253+--------------------+--------------------------------------------------------+
254| Option Name        | Description                                            |
255+====================+========================================================+
256| ``tablename``      | Specify a custom name for the intermediary table. This |
257|                    | can be used both when the tables needs to be created   |
258|                    | and when the table is autoloaded/reflected from the    |
259|                    | database. If this argument is not used, a name will be |
260|                    | automatically generated by Elixir depending on the name|
261|                    | of the tables of the two entities of the relationship, |
262|                    | the name of the relationship, and, if present, the name|
263|                    | of its inverse. Even though this argument is optional, |
264|                    | it is wise to use it if you are not sure what are the  |
265|                    | exact consequence of using a generated table name.     |
266+--------------------+--------------------------------------------------------+
267| ``schema``         | Specify a custom schema for the intermediate table.    |
268|                    | This can be used both when the tables needs to         |
269|                    | be created and when the table is autoloaded/reflected  |
270|                    | from the database.                                     |
271+--------------------+--------------------------------------------------------+
272| ``remote_colname`` | A string or list of strings specifying the names of    |
273|                    | the column(s) in the intermediary table which          |
274|                    | reference the "remote"/target entity's table.          |
275+--------------------+--------------------------------------------------------+
276| ``local_colname``  | A string or list of strings specifying the names of    |
277|                    | the column(s) in the intermediary table which          |
278|                    | reference the "local"/current entity's table.          |
279+--------------------+--------------------------------------------------------+
280| ``table``          | Use a manually created table. If this argument is      |
281|                    | used, Elixir won't generate a table for this           |
282|                    | relationship, and use the one given instead.           |
283+--------------------+--------------------------------------------------------+
284| ``order_by``       | Specify which field(s) should be used to sort the      |
285|                    | results given by accessing the relation field.         |
286|                    | Note that this sort order is only applied when loading |
287|                    | objects from the database. Objects appended to the     |
288|                    | collection afterwards are not re-sorted in-memory on   |
289|                    | the fly.                                               |
290|                    | This argument accepts either a string or a list of     |
291|                    | strings, each corresponding to the name of a field in  |
292|                    | the target entity. These field names can optionally be |
293|                    | prefixed by a minus (for descending order).            |
294+----------------------+------------------------------------------------------+
295| ``ondelete``       | Value for the foreign key constraint ondelete clause.  |
296|                    | May be one of: ``cascade``, ``restrict``,              |
297|                    | ``set null``, or ``set default``.                      |
298+--------------------+--------------------------------------------------------+
299| ``onupdate``       | Value for the foreign key constraint onupdate clause.  |
300|                    | May be one of: ``cascade``, ``restrict``,              |
301|                    | ``set null``, or ``set default``.                      |
302+--------------------+--------------------------------------------------------+
303| ``table_kwargs``   | A dictionary holding any other keyword argument you    |
304|                    | might want to pass to the underlying Table object.     |
305+--------------------+--------------------------------------------------------+
306
307
308================
309DSL-based syntax
310================
311
312The following DSL statements provide an alternative way to define relationships
313between your entities. The first argument to all those statements is the name
314of the relationship, the second is the 'kind' of object you are relating to
315(it is usually given using the ``of_kind`` keyword).
316
317`belongs_to`
318------------
319
320The ``belongs_to`` statement is the DSL syntax equivalent to the ManyToOne_
321relationship. As such, it supports all the same arguments as ManyToOne_
322relationships.
323
324.. sourcecode:: python
325
326    class Pet(Entity):
327        belongs_to('feeder', of_kind='Person')
328        belongs_to('owner', of_kind='Person', colname="owner_id")
329
330
331`has_many`
332----------
333
334The ``has_many`` statement is the DSL syntax equivalent to the OneToMany_
335relationship. As such, it supports all the same arguments as OneToMany_
336relationships.
337
338.. sourcecode:: python
339
340    class Person(Entity):
341        belongs_to('parent', of_kind='Person')
342        has_many('children', of_kind='Person')
343
344There is also an alternate form of the ``has_many`` relationship that takes
345only two keyword arguments: ``through`` and ``via`` in order to encourage a
346richer form of many-to-many relationship that is an alternative to the
347``has_and_belongs_to_many`` statement.  Here is an example:
348
349.. sourcecode:: python
350
351    class Person(Entity):
352        has_field('name', Unicode)
353        has_many('assignments', of_kind='Assignment')
354        has_many('projects', through='assignments', via='project')
355
356    class Assignment(Entity):
357        has_field('start_date', DateTime)
358        belongs_to('person', of_kind='Person')
359        belongs_to('project', of_kind='Project')
360
361    class Project(Entity):
362        has_field('title', Unicode)
363        has_many('assignments', of_kind='Assignment')
364
365In the above example, a `Person` has many `projects` through the `Assignment`
366relationship object, via a `project` attribute.
367
368
369`has_one`
370---------
371
372The ``has_one`` statement is the DSL syntax equivalent to the OneToOne_
373relationship. As such, it supports all the same arguments as OneToOne_
374relationships.
375
376.. sourcecode:: python
377
378    class Car(Entity):
379        has_one('gear_stick', of_kind='GearStick', inverse='car')
380
381    class GearStick(Entity):
382        belongs_to('car', of_kind='Car')
383
384
385`has_and_belongs_to_many`
386-------------------------
387
388The ``has_and_belongs_to_many`` statement is the DSL syntax equivalent to the
389ManyToMany_ relationship. As such, it supports all the same arguments as
390ManyToMany_ relationships.
391
392.. sourcecode:: python
393
394    class Article(Entity):
395        has_and_belongs_to_many('tags', of_kind='Tag')
396
397    class Tag(Entity):
398        has_and_belongs_to_many('articles', of_kind='Article')
399
400'''
401
402import warnings
403
404from sqlalchemy import ForeignKeyConstraint, Column, Table, and_
405from sqlalchemy.orm import relation, backref, class_mapper
406from sqlalchemy.ext.associationproxy import association_proxy
407
408import options
409from elixir.statements import ClassMutator
410from elixir.properties import Property
411from elixir.entity import EntityMeta, DEBUG
412
413__doc_all__ = []
414
415
416class Relationship(Property):
417    '''
418    Base class for relationships.
419    '''
420
421    def __init__(self, of_kind, inverse=None, *args, **kwargs):
422        super(Relationship, self).__init__()
423
424        self.of_kind = of_kind
425        self.inverse_name = inverse
426
427        self._target = None
428
429        self.property = None # sqlalchemy property
430        self.backref = None  # sqlalchemy backref
431
432        #TODO: unused for now
433        self.args = args
434        self.kwargs = kwargs
435
436    def attach(self, entity, name):
437        super(Relationship, self).attach(entity, name)
438        entity._descriptor.relationships.append(self)
439
440    def create_pk_cols(self):
441        self.create_keys(True)
442
443    def create_non_pk_cols(self):
444        self.create_keys(False)
445
446    def create_keys(self, pk):
447        '''
448        Subclasses (ie. concrete relationships) may override this method to
449        create foreign keys.
450        '''
451
452    def create_properties(self):
453        if self.property or self.backref:
454            return
455
456        kwargs = self.get_prop_kwargs()
457        if 'order_by' in kwargs:
458            kwargs['order_by'] = \
459                self.target._descriptor.translate_order_by(kwargs['order_by'])
460
461        # transform callable arguments
462        for arg in ('primaryjoin', 'secondaryjoin', 'remote_side',
463                    'foreign_keys'):
464            kwarg = kwargs.get(arg, None)
465            if hasattr(kwarg, '__call__'):
466                kwargs[arg] = kwarg()
467
468        # viewonly relationships need to create "standalone" relations (ie
469        # shouldn't be a backref of another relation).
470        if self.inverse and not kwargs.get('viewonly', False):
471            # check if the inverse was already processed (and thus has already
472            # defined a backref we can use)
473            if self.inverse.backref:
474                # let the user override the backref argument
475                if 'backref' not in kwargs:
476                    kwargs['backref'] = self.inverse.backref
477            else:
478                # SQLAlchemy doesn't like when 'secondary' is both defined on
479                # the relation and the backref
480                kwargs.pop('secondary', None)
481
482                # define backref for use by the inverse
483                self.backref = backref(self.name, **kwargs)
484                return
485
486        self.property = relation(self.target, **kwargs)
487        self.add_mapper_property(self.name, self.property)
488
489    @property
490    def target(self):
491        if not self._target:
492            if isinstance(self.of_kind, basestring):
493                collection = self.entity._descriptor.collection
494                self._target = collection.resolve(self.of_kind, self.entity)
495            else:
496                self._target = self.of_kind
497        return self._target
498
499    @property
500    def inverse(self):
501        if not hasattr(self, '_inverse'):
502            if self.inverse_name:
503                desc = self.target._descriptor
504                inverse = desc.find_relationship(self.inverse_name)
505                if inverse is None:
506                    raise Exception(
507                              "Couldn't find a relationship named '%s' in "
508                              "entity '%s' or its parent entities."
509                              % (self.inverse_name, self.target.__name__))
510                assert self.match_type_of(inverse), \
511                    "Relationships '%s' in entity '%s' and '%s' in entity " \
512                    "'%s' cannot be inverse of each other because their " \
513                    "types do not form a valid combination." % \
514                    (self.name, self.entity.__name__,
515                     self.inverse_name, self.target.__name__)
516            else:
517                check_reverse = not self.kwargs.get('viewonly', False)
518                if isinstance(self.target, EntityMeta):
519                    inverse = self.target._descriptor.get_inverse_relation(
520                        self, check_reverse=check_reverse)
521                else:
522                    inverse = None
523            self._inverse = inverse
524            if inverse and not self.kwargs.get('viewonly', False):
525                inverse._inverse = self
526
527        return self._inverse
528
529    def match_type_of(self, other):
530        return False
531
532    def is_inverse(self, other):
533        # viewonly relationships are not symmetrical: a viewonly relationship
534        # should have exactly one inverse (a ManyToOne relationship), but that
535        # inverse shouldn't have the viewonly relationship as its inverse.
536        return not other.kwargs.get('viewonly', False) and \
537               other is not self and \
538               self.match_type_of(other) and \
539               self.entity == other.target and \
540               other.entity == self.target and \
541               (self.inverse_name == other.name or not self.inverse_name) and \
542               (other.inverse_name == self.name or not other.inverse_name)
543
544
545class ManyToOne(Relationship):
546    '''
547
548    '''
549
550    def __init__(self, of_kind,
551                 column_kwargs=None,
552                 colname=None, required=None, primary_key=None,
553                 field=None,
554                 constraint_kwargs=None,
555                 use_alter=None, ondelete=None, onupdate=None,
556                 target_column=None,
557                 *args, **kwargs):
558
559        # 1) handle column-related args
560
561        # check that the column arguments don't conflict
562        assert not (field and (column_kwargs or colname)), \
563               "ManyToOne can accept the 'field' argument or column " \
564               "arguments ('colname' or 'column_kwargs') but not both!"
565
566        if colname and not isinstance(colname, list):
567            colname = [colname]
568        self.colname = colname or []
569
570        column_kwargs = column_kwargs or {}
571        # kwargs go by default to the relation(), so we need to manually
572        # extract those targeting the Column
573        if required is not None:
574            column_kwargs['nullable'] = not required
575        if primary_key is not None:
576            column_kwargs['primary_key'] = primary_key
577        # by default, created columns will have an index.
578        column_kwargs.setdefault('index', True)
579        self.column_kwargs = column_kwargs
580
581        if field and not isinstance(field, list):
582            field = [field]
583        self.field = field or []
584
585        # 2) handle constraint kwargs
586        constraint_kwargs = constraint_kwargs or {}
587        if use_alter is not None:
588            constraint_kwargs['use_alter'] = use_alter
589        if ondelete is not None:
590            constraint_kwargs['ondelete'] = ondelete
591        if onupdate is not None:
592            constraint_kwargs['onupdate'] = onupdate
593        self.constraint_kwargs = constraint_kwargs
594
595        # 3) misc arguments
596        if target_column and not isinstance(target_column, list):
597            target_column = [target_column]
598        self.target_column = target_column
599
600        self.foreign_key = []
601        self.primaryjoin_clauses = []
602
603        super(ManyToOne, self).__init__(of_kind, *args, **kwargs)
604
605    def match_type_of(self, other):
606        return isinstance(other, (OneToMany, OneToOne))
607
608    @property
609    def target_table(self):
610        if isinstance(self.target, EntityMeta):
611            return self.target._descriptor.table
612        else:
613            return class_mapper(self.target).local_table
614
615    def create_keys(self, pk):
616        '''
617        Find all primary keys on the target and create foreign keys on the
618        source accordingly.
619        '''
620
621        if self.foreign_key:
622            return
623
624        if self.column_kwargs.get('primary_key', False) != pk:
625            return
626
627        source_desc = self.entity._descriptor
628        if isinstance(self.target, EntityMeta):
629            # make sure the target has all its pk set up
630            self.target._descriptor.create_pk_cols()
631        #XXX: another option, instead of the FakeTable, would be to create an
632        # EntityDescriptor for the SA class.
633        target_table = self.target_table
634
635        if source_desc.autoload:
636            #TODO: allow target_column to be used as an alternative to
637            # specifying primaryjoin, to be consistent with non-autoloaded
638            # tables
639            if self.colname:
640                if 'primaryjoin' not in self.kwargs:
641                    self.primaryjoin_clauses = \
642                        _get_join_clauses(self.entity.table,
643                                          self.colname, None,
644                                          target_table)[0]
645                    if not self.primaryjoin_clauses:
646                        colnames = ', '.join(self.colname)
647                        raise Exception(
648                            "Couldn't find a foreign key constraint in table "
649                            "'%s' using the following columns: %s."
650                            % (self.entity.table.name, colnames))
651            if self.field:
652                raise NotImplementedError(
653                    "'field' argument not allowed on autoloaded table "
654                    "relationships.")
655        else:
656            fk_refcols = []
657            fk_colnames = []
658
659            if self.target_column is None:
660                target_columns = target_table.primary_key.columns
661            else:
662                target_columns = [target_table.columns[col]
663                                  for col in self.target_column]
664
665            if not target_columns:
666                raise Exception("No primary key found in target table ('%s') "
667                                "for the '%s' relationship of the '%s' entity."
668                                % (target_table.name, self.name,
669                                   self.entity.__name__))
670            if self.colname and \
671               len(self.colname) != len(target_columns):
672                raise Exception(
673                        "The number of column names provided in the colname "
674                        "keyword argument of the '%s' relationship of the "
675                        "'%s' entity is not the same as the number of columns "
676                        "of the primary key of '%s'."
677                        % (self.name, self.entity.__name__,
678                           self.target.__name__))
679
680            for key_num, target_col in enumerate(target_columns):
681                if self.field:
682                    col = self.field[key_num].column
683                else:
684                    if self.colname:
685                        colname = self.colname[key_num]
686                    else:
687                        colname = options.FKCOL_NAMEFORMAT % \
688                                  {'relname': self.name,
689                                   'key': target_col.key}
690
691                    # We can't add the column to the table directly as the
692                    # table might not be created yet.
693                    col = Column(colname, target_col.type,
694                                 **self.column_kwargs)
695                    source_desc.add_column(col)
696
697                    # If the column name was specified, and it is the same as
698                    # this property's name, there is going to be a conflict.
699                    # Don't allow this to happen.
700                    if col.key == self.name:
701                        raise ValueError(
702                                 "ManyToOne named '%s' in '%s' conficts "
703                                 " with the column of the same name. "
704                                 "You should probably define the foreign key "
705                                 "field manually and use the 'field' "
706                                 "argument on the ManyToOne relationship"
707                                 % (self.name, self.entity.__name__))
708
709                # Build the list of local columns which will be part of
710                # the foreign key
711                self.foreign_key.append(col)
712
713                # Store the names of those columns
714                fk_colnames.append(col.key)
715
716                # Build the list of column "paths" the foreign key will
717                # point to
718                fk_refcols.append("%s.%s" % \
719                                  (target_table.fullname, target_col.key))
720
721                # Build up the primary join. This is needed when you have
722                # several ManyToOne relationships between two objects
723                self.primaryjoin_clauses.append(col == target_col)
724
725            if 'name' not in self.constraint_kwargs:
726                # In some databases (at least MySQL) the constraint name needs
727                # to be unique for the whole database, instead of per table.
728                fk_name = options.CONSTRAINT_NAMEFORMAT % \
729                          {'tablename': source_desc.tablename,
730                           'colnames': '_'.join(fk_colnames)}
731                self.constraint_kwargs['name'] = fk_name
732
733            source_desc.add_constraint(
734                ForeignKeyConstraint(fk_colnames, fk_refcols,
735                                     **self.constraint_kwargs))
736
737    def get_prop_kwargs(self):
738        kwargs = {'uselist': False}
739
740        if self.entity.table is self.target_table:
741            # this is needed because otherwise SA has no way to know what is
742            # the direction of the relationship since both columns present in
743            # the primaryjoin belong to the same table. In other words, it is
744            # necessary to know if this particular relation
745            # is the many-to-one side, or the one-to-xxx side. The foreignkey
746            # doesn't help in this case.
747            kwargs['remote_side'] = \
748                [col for col in self.target_table.primary_key.columns]
749
750        if self.primaryjoin_clauses:
751            kwargs['primaryjoin'] = and_(*self.primaryjoin_clauses)
752
753        kwargs.update(self.kwargs)
754
755        return kwargs
756
757
758class OneToOne(Relationship):
759    uselist = False
760
761    def __init__(self, of_kind, filter=None, *args, **kwargs):
762        self.filter = filter
763        if filter is not None:
764            # We set viewonly to True by default for filtered relationships,
765            # unless manually overridden.
766            # This is not strictly necessary, as SQLAlchemy allows non viewonly
767            # relationships with a custom join/filter. The example at:
768            # SADOCS/05/mappers.html#advdatamapping_relation_customjoin
769            # is not viewonly. Those relationships can be used as if the extra
770            # filter wasn't present when inserting. This can lead to a
771            # confusing behavior (if you insert data which doesn't match the
772            # extra criterion it'll get inserted anyway but you won't see it
773            # when you query back the attribute after a round-trip to the
774            # database).
775            if 'viewonly' not in kwargs:
776                kwargs['viewonly'] = True
777        super(OneToOne, self).__init__(of_kind, *args, **kwargs)
778
779    def match_type_of(self, other):
780        return isinstance(other, ManyToOne)
781
782    def create_keys(self, pk):
783        # make sure an inverse relationship exists
784        if self.inverse is None:
785            raise Exception(
786                      "Couldn't find any relationship in '%s' which "
787                      "match as inverse of the '%s' relationship "
788                      "defined in the '%s' entity. If you are using "
789                      "inheritance you "
790                      "might need to specify inverse relationships "
791                      "manually by using the 'inverse' argument."
792                      % (self.target, self.name,
793                         self.entity))
794
795    def get_prop_kwargs(self):
796        kwargs = {'uselist': self.uselist}
797
798        #TODO: for now, we don't break any test if we remove those 2 lines.
799        # So, we should either complete the selfref test to prove that they
800        # are indeed useful, or remove them. It might be they are indeed
801        # useless because the remote_side is already setup in the other way
802        # (ManyToOne).
803        if self.entity.table is self.target.table:
804            #FIXME: IF this code is of any use, it will probably break for
805            # autoloaded tables
806            kwargs['remote_side'] = self.inverse.foreign_key
807
808        # Contrary to ManyToMany relationships, we need to specify the join
809        # clauses even if this relationship is not self-referencial because
810        # there could be several ManyToOne from the target class to us.
811        joinclauses = self.inverse.primaryjoin_clauses
812        if self.filter:
813            # We need to make a copy of the joinclauses, to not add the filter
814            # on the backref
815            joinclauses = joinclauses[:] + [self.filter(self.target.table.c)]
816        if joinclauses:
817            kwargs['primaryjoin'] = and_(*joinclauses)
818
819        kwargs.update(self.kwargs)
820
821        return kwargs
822
823
824class OneToMany(OneToOne):
825    uselist = True
826
827
828class ManyToMany(Relationship):
829    uselist = True
830
831    def __init__(self, of_kind, tablename=None,
832                 local_colname=None, remote_colname=None,
833                 ondelete=None, onupdate=None,
834                 table=None, schema=None,
835                 filter=None,
836                 table_kwargs=None,
837                 *args, **kwargs):
838        self.user_tablename = tablename
839
840        if local_colname and not isinstance(local_colname, list):
841            local_colname = [local_colname]
842        self.local_colname = local_colname or []
843        if remote_colname and not isinstance(remote_colname, list):
844            remote_colname = [remote_colname]
845        self.remote_colname = remote_colname or []
846
847        self.ondelete = ondelete
848        self.onupdate = onupdate
849
850        self.table = table
851        self.schema = schema
852
853        #TODO: this can probably be simplified/moved elsewhere since the
854        #argument disappeared
855        self.column_format = options.M2MCOL_NAMEFORMAT
856        if not hasattr(self.column_format, '__call__'):
857            # we need to store the format in a variable so that the
858            # closure of the lambda is correct
859            format = self.column_format
860            self.column_format = lambda data: format % data
861        if options.MIGRATION_TO_07_AID:
862            self.column_format = \
863                migration_aid_m2m_column_formatter(
864                    lambda data: options.OLD_M2MCOL_NAMEFORMAT % data,
865                    self.column_format)
866
867        self.filter = filter
868        if filter is not None:
869            # We set viewonly to True by default for filtered relationships,
870            # unless manually overridden.
871            if 'viewonly' not in kwargs:
872                kwargs['viewonly'] = True
873
874        self.table_kwargs = table_kwargs or {}
875
876        self.primaryjoin_clauses = []
877        self.secondaryjoin_clauses = []
878
879        super(ManyToMany, self).__init__(of_kind, *args, **kwargs)
880
881    def match_type_of(self, other):
882        return isinstance(other, ManyToMany)
883
884    def create_tables(self):
885        if self.table is not None:
886            if 'primaryjoin' not in self.kwargs or \
887               'secondaryjoin' not in self.kwargs:
888                self._build_join_clauses()
889            assert self.inverse is None or self.inverse.table is None or \
890                   self.inverse.table is self.table
891            return
892
893        if self.inverse:
894            inverse = self.inverse
895            if inverse.table is not None:
896                self.table = inverse.table
897                self.primaryjoin_clauses = inverse.secondaryjoin_clauses
898                self.secondaryjoin_clauses = inverse.primaryjoin_clauses
899                return
900
901            assert not inverse.user_tablename or not self.user_tablename or \
902                   inverse.user_tablename == self.user_tablename
903            assert not inverse.remote_colname or not self.local_colname or \
904                   inverse.remote_colname == self.local_colname
905            assert not inverse.local_colname or not self.remote_colname or \
906                   inverse.local_colname == self.remote_colname
907            assert not inverse.schema or not self.schema or \
908                   inverse.schema == self.schema
909            assert not inverse.table_kwargs or not self.table_kwargs or \
910                   inverse.table_kwargs == self.table_kwargs
911
912            self.user_tablename = inverse.user_tablename or self.user_tablename
913            self.local_colname = inverse.remote_colname or self.local_colname
914            self.remote_colname = inverse.local_colname or self.remote_colname
915            self.schema = inverse.schema or self.schema
916            self.local_colname = inverse.remote_colname or self.local_colname
917
918        # compute table_kwargs
919        complete_kwargs = options.options_defaults['table_options'].copy()
920        complete_kwargs.update(self.table_kwargs)
921
922        #needs: table_options['schema'], autoload, tablename, primary_keys,
923        #entity.__name__, table_fullname
924        e1_desc = self.entity._descriptor
925        e2_desc = self.target._descriptor
926
927        e1_schema = e1_desc.table_options.get('schema', None)
928        e2_schema = e2_desc.table_options.get('schema', None)
929        schema = (self.schema is not None) and self.schema or e1_schema
930
931        assert e1_schema == e2_schema or self.schema, \
932               "Schema %r for entity %s differs from schema %r of entity %s." \
933               " Consider using the schema-parameter. "\
934               % (e1_schema, self.entity.__name__,
935                  e2_schema, self.target.__name__)
936
937        # First, we compute the name of the table. Note that some of the
938        # intermediary variables are reused later for the constraint
939        # names.
940
941        # We use the name of the relation for the first entity
942        # (instead of the name of its primary key), so that we can
943        # have two many-to-many relations between the same objects
944        # without having a table name collision.
945        source_part = "%s_%s" % (e1_desc.tablename, self.name)
946
947        # And we use only the name of the table of the second entity
948        # when there is no inverse, so that a many-to-many relation
949        # can be defined without an inverse.
950        if self.inverse:
951            target_part = "%s_%s" % (e2_desc.tablename, self.inverse.name)
952        else:
953            target_part = e2_desc.tablename
954
955        if self.user_tablename:
956            tablename = self.user_tablename
957        else:
958            # We need to keep the table name consistent (independant of
959            # whether this relation or its inverse is setup first).
960            if self.inverse and source_part < target_part:
961                #XXX: use a different scheme for selfref (to not include the
962                #     table name twice)?
963                tablename = "%s__%s" % (target_part, source_part)
964            else:
965                tablename = "%s__%s" % (source_part, target_part)
966
967            if options.MIGRATION_TO_07_AID:
968                oldname = (self.inverse and
969                           e1_desc.tablename < e2_desc.tablename) and \
970                          "%s__%s" % (target_part, source_part) or \
971                          "%s__%s" % (source_part, target_part)
972                if oldname != tablename:
973                    warnings.warn(
974                        "The generated table name for the '%s' relationship "
975                        "on the '%s' entity changed from '%s' (the name "
976                        "generated by Elixir 0.6.1 and earlier) to '%s'. "
977                        "You should either rename the table in the database "
978                        "to the new name or use the tablename argument on the "
979                        "relationship to force the old name: tablename='%s'!"
980                        % (self.name, self.entity.__name__, oldname,
981                           tablename, oldname))
982
983        if e1_desc.autoload:
984            if not e2_desc.autoload:
985                raise Exception(
986                    "Entity '%s' is autoloaded and its '%s' "
987                    "ManyToMany relationship points to "
988                    "the '%s' entity which is not autoloaded"
989                    % (self.entity.__name__, self.name,
990                       self.target.__name__))
991
992            self.table = Table(tablename, e1_desc.metadata, autoload=True,
993                               **complete_kwargs)
994            if 'primaryjoin' not in self.kwargs or \
995               'secondaryjoin' not in self.kwargs:
996                self._build_join_clauses()
997        else:
998            # We pre-compute the names of the foreign key constraints
999            # pointing to the source (local) entity's table and to the
1000            # target's table
1001
1002            # In some databases (at least MySQL) the constraint names need
1003            # to be unique for the whole database, instead of per table.
1004            source_fk_name = "%s_fk" % source_part
1005            if self.inverse:
1006                target_fk_name = "%s_fk" % target_part
1007            else:
1008                target_fk_name = "%s_inverse_fk" % source_part
1009
1010            columns = []
1011            constraints = []
1012
1013            for num, desc, fk_name, rel, inverse, colnames, join_clauses in (
1014              (0, e1_desc, source_fk_name, self, self.inverse,
1015               self.local_colname, self.primaryjoin_clauses),
1016              (1, e2_desc, target_fk_name, self.inverse, self,
1017               self.remote_colname, self.secondaryjoin_clauses)):
1018
1019                fk_colnames = []
1020                fk_refcols = []
1021                if colnames:
1022                    assert len(colnames) == len(desc.primary_keys)
1023                else:
1024                    # The data generated here will be fed to the M2M column
1025                    # formatter to generate the name of the columns of the
1026                    # intermediate table for *one* side of the relationship,
1027                    # that is, from the intermediate table to the current
1028                    # entity, as stored in the "desc" variable.
1029                    data = {# A) relationships info
1030
1031                            # the name of the rel going *from* the entity
1032                            # we are currently generating a column pointing
1033                            # *to*. This is generally *not* what you want to
1034                            # use. eg in a "Post" and "Tag" example, with
1035                            # relationships named 'tags' and 'posts', when
1036                            # creating the columns from the intermediate
1037                            # table to the "Post" entity, 'relname' will
1038                            # contain 'tags'.
1039                            'relname': rel and rel.name or 'inverse',
1040
1041                            # the name of the inverse relationship. In the
1042                            # above example, 'inversename' will contain
1043                            # 'posts'.
1044                            'inversename': inverse and inverse.name
1045                                                   or 'inverse',
1046                            # is A == B?
1047                            'selfref': e1_desc is e2_desc,
1048                            # provided for backward compatibility, DO NOT USE!
1049                            'num': num,
1050                            # provided for backward compatibility, DO NOT USE!
1051                            'numifself': e1_desc is e2_desc and str(num + 1)
1052                                                            or '',
1053                            # B) target information (from the perspective of
1054                            #    the intermediate table)
1055                            'target': desc.entity,
1056                            'entity': desc.entity.__name__.lower(),
1057                            'tablename': desc.tablename,
1058
1059                            # C) current (intermediate) table name
1060                            'current_table': tablename
1061                           }
1062                    colnames = []
1063                    for pk_col in desc.primary_keys:
1064                        data.update(key=pk_col.key)
1065                        colnames.append(self.column_format(data))
1066
1067                for pk_col, colname in zip(desc.primary_keys, colnames):
1068                    col = Column(colname, pk_col.type, primary_key=True)
1069                    columns.append(col)
1070
1071                    # Build the list of local columns which will be part
1072                    # of the foreign key.
1073                    fk_colnames.append(colname)
1074
1075                    # Build the list of column "paths" the foreign key will
1076                    # point to
1077                    target_path = "%s.%s" % (desc.table_fullname, pk_col.key)
1078                    fk_refcols.append(target_path)
1079
1080                    # Build join clauses (in case we have a self-ref)
1081                    if self.entity is self.target:
1082                        join_clauses.append(col == pk_col)
1083
1084                onupdate = rel and rel.onupdate
1085                ondelete = rel and rel.ondelete
1086
1087                #FIXME: fk_name is misleading
1088                constraints.append(
1089                    ForeignKeyConstraint(fk_colnames, fk_refcols,
1090                                         name=fk_name, onupdate=onupdate,
1091                                         ondelete=ondelete))
1092
1093            args = columns + constraints
1094
1095            self.table = Table(tablename, e1_desc.metadata,
1096                               schema=schema, *args, **complete_kwargs)
1097            if DEBUG:
1098                print self.table.repr2()
1099
1100    def _build_join_clauses(self):
1101        # In the case we have a self-reference, we need to build join clauses
1102        if self.entity is self.target:
1103            if not self.local_colname and not self.remote_colname:
1104                raise Exception(
1105                    "Self-referential ManyToMany "
1106                    "relationships in autoloaded entities need to have at "
1107                    "least one of either 'local_colname' or 'remote_colname' "
1108                    "argument specified. The '%s' relationship in the '%s' "
1109                    "entity doesn't have either."
1110                    % (self.name, self.entity.__name__))
1111
1112            self.primaryjoin_clauses, self.secondaryjoin_clauses = \
1113                _get_join_clauses(self.table,
1114                                  self.local_colname, self.remote_colname,
1115                                  self.entity.table)
1116
1117    def get_prop_kwargs(self):
1118        kwargs = {'secondary': self.table,
1119                  'uselist': self.uselist}
1120
1121        if self.filter:
1122            # we need to make a copy of the joinclauses
1123            secondaryjoin_clauses = self.secondaryjoin_clauses[:] + \
1124                                    [self.filter(self.target.table.c)]
1125        else:
1126            secondaryjoin_clauses = self.secondaryjoin_clauses
1127
1128        if self.target is self.entity or self.filter:
1129            kwargs['primaryjoin'] = and_(*self.primaryjoin_clauses)
1130            kwargs['secondaryjoin'] = and_(*secondaryjoin_clauses)
1131
1132        kwargs.update(self.kwargs)
1133
1134        return kwargs
1135
1136    def is_inverse(self, other):
1137        return super(ManyToMany, self).is_inverse(other) and \
1138               (self.user_tablename == other.user_tablename or
1139                (not self.user_tablename and not other.user_tablename))
1140
1141
1142def migration_aid_m2m_column_formatter(oldformatter, newformatter):
1143    def debug_formatter(data):
1144        old_name = oldformatter(data)
1145        new_name = newformatter(data)
1146        if new_name != old_name:
1147            complete_data = data.copy()
1148            complete_data.update(old_name=old_name,
1149                                 new_name=new_name,
1150                                 targetname=data['target'].__name__)
1151            # Specifying a stacklevel is useless in this case as the name
1152            # generation is triggered by setup_all(), not by the declaration
1153            # of the offending relationship.
1154            warnings.warn("The '%(old_name)s' column in the "
1155                          "'%(current_table)s' table, used as the "
1156                          "intermediate table for the '%(relname)s' "
1157                          "relationship on the '%(targetname)s' entity "
1158                          "was renamed to '%(new_name)s'."
1159                          % complete_data)
1160        return new_name
1161    return debug_formatter
1162
1163
1164def _get_join_clauses(local_table, local_cols1, local_cols2, target_table):
1165    primary_join, secondary_join = [], []
1166    cols1 = local_cols1[:]
1167    cols1.sort()
1168    cols1 = tuple(cols1)
1169
1170    if local_cols2 is not None:
1171        cols2 = local_cols2[:]
1172        cols2.sort()
1173        cols2 = tuple(cols2)
1174    else:
1175        cols2 = None
1176
1177    # Build a map of fk constraints pointing to the correct table.
1178    # The map is indexed on the local col names.
1179    constraint_map = {}
1180    for constraint in local_table.constraints:
1181        if isinstance(constraint, ForeignKeyConstraint):
1182            use_constraint = True
1183            fk_colnames = []
1184
1185            # if all columns point to the correct table, we use the constraint
1186            #TODO: check that it contains as many columns as the pk of the
1187            #target entity, or even that it points to the actual pk columns
1188            for fk in constraint.elements:
1189                if fk.references(target_table):
1190                    # local column key
1191                    fk_colnames.append(fk.parent.key)
1192                else:
1193                    use_constraint = False
1194            if use_constraint:
1195                fk_colnames.sort()
1196                constraint_map[tuple(fk_colnames)] = constraint
1197
1198    # Either the fk column names match explicitely with the columns given for
1199    # one of the joins (primary or secondary), or we assume the current
1200    # columns match because the columns for this join were not given and we
1201    # know the other join is either not used (is None) or has an explicit
1202    # match.
1203
1204#TODO: rewrite this. Even with the comment, I don't even understand it myself.
1205    for cols, constraint in constraint_map.iteritems():
1206        if cols == cols1 or (cols != cols2 and
1207                             not cols1 and (cols2 in constraint_map or
1208                                            cols2 is None)):
1209            join = primary_join
1210        elif cols == cols2 or (cols2 == () and cols1 in constraint_map):
1211            join = secondary_join
1212        else:
1213            continue
1214        for fk in constraint.elements:
1215            join.append(fk.parent == fk.column)
1216    return primary_join, secondary_join
1217
1218
1219def rel_mutator_handler(target):
1220    def handler(entity, name, of_kind=None, through=None, via=None,
1221                *args, **kwargs):
1222        if through and via:
1223            setattr(entity, name,
1224                    association_proxy(through, via, **kwargs))
1225            return
1226        elif through or via:
1227            raise Exception("'through' and 'via' relationship keyword "
1228                            "arguments should be used in combination.")
1229        rel = target(of_kind, *args, **kwargs)
1230        rel.attach(entity, name)
1231    return handler
1232
1233
1234belongs_to = ClassMutator(rel_mutator_handler(ManyToOne))
1235has_one = ClassMutator(rel_mutator_handler(OneToOne))
1236has_many = ClassMutator(rel_mutator_handler(OneToMany))
1237has_and_belongs_to_many = ClassMutator(rel_mutator_handler(ManyToMany))
Note: See TracBrowser for help on using the browser.