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

Revision 488, 55.9 kB (checked in by ged, 4 years ago)

callable is deprecated in py3k

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