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

Revision 458, 52.7 kB (checked in by ged, 5 years ago)

- Changed the pattern used by default to generate column names for (all)

ManyToMany relationships so that the meaning of bidirectional
self-referential relationships does not depend on the order of declaration
of each side (closes #69). See upgrade notes for details.

- Made M2MCOL_NAMEFORMAT accept a callable, so that more complex naming

generation can be used if so desired.

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