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

Revision 421, 48.9 kB (checked in by ged, 5 years ago)

- moved all relationship options to explicit kwargs, instead of doing

"kwargs.pops()" all over the place.

- changed empty dicts and lists to {} and []. I really tried to get used to the

other notation but it seems like it didn't work out.

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