root / elixir / trunk / elixir / relationships.py

Revision 531, 55.2 kB (checked in by ged, 17 months ago)

add some comments, FIXME, etc...

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