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

Revision 426, 49.9 kB (checked in by ged, 6 years ago)

- Fixed filter argument on OneToMany relationship leaking the filter to the

unfiltered relationship. Doh!!!

- added preliminary code for filter on ManyToMany (but see ticket #68 comment)
- renamed M2M.secondary_table to M2M.table

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',
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 relationships,
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        # Contrary to ManyToMany relationships, we need to specify the join
796        # clauses even if this relationship is not self-referencial because
797        # there could be several ManyToOne from the target class to us.
798        joinclauses = self.inverse.primaryjoin_clauses
799        if self.filter:
800            # We need to make a copy of the joinclauses, to not add the filter
801            # on the backref
802            joinclauses = joinclauses[:] + [self.filter(self.target.table.c)]
803        if joinclauses:
804            kwargs['primaryjoin'] = and_(*joinclauses)
805
806        kwargs.update(self.kwargs)
807
808        return kwargs
809
810
811class OneToMany(OneToOne):
812    uselist = True
813
814
815class ManyToMany(Relationship):
816    uselist = True
817
818    def __init__(self, of_kind, tablename=None,
819                 local_colname=None, remote_colname=None,
820                 ondelete=None, onupdate=None,
821                 table=None, schema=None,
822                 column_format=None,
823                 filter=None,
824                 *args, **kwargs):
825        self.user_tablename = tablename
826
827        if local_colname and not isinstance(local_colname, list):
828            local_colname = [local_colname]
829        self.local_colname = local_colname or []
830        if remote_colname and not isinstance(remote_colname, list):
831            remote_colname = [remote_colname]
832        self.remote_colname = remote_colname or []
833
834        self.ondelete = ondelete
835        self.onupdate = onupdate
836
837        self.table = table
838        self.schema = schema
839
840        if column_format:
841            warnings.warn("The 'column_format' argument on ManyToMany "
842                "relationships is deprecated. Please use the 'local_colname' "
843                "and/or 'remote_colname' arguments if you want custom "
844                "column names for this table only, or modify "
845                "options.M2MCOL_NAMEFORMAT if you want a custom format for "
846                "all ManyToMany tables", DeprecationWarning, stacklevel=3)
847        self.column_format = column_format or options.M2MCOL_NAMEFORMAT
848
849        self.filter = filter
850        if filter is not None:
851            # We set viewonly to True by default for filtered relationships,
852            # unless manually overridden.
853            if 'viewonly' not in kwargs:
854                kwargs['viewonly'] = True
855
856        self.primaryjoin_clauses = []
857        self.secondaryjoin_clauses = []
858
859        super(ManyToMany, self).__init__(of_kind, *args, **kwargs)
860
861    def get_table(self):
862        warnings.warn("The secondary_table attribute on ManyToMany objects is"
863                      "deprecated", DeprecationWarning, stacklevel=2)
864        return self.table
865    secondary_table = property(get_table)
866
867    def match_type_of(self, other):
868        return isinstance(other, ManyToMany)
869
870    def create_tables(self):
871        if self.table:
872            if 'primaryjoin' not in self.kwargs or \
873               'secondaryjoin' not in self.kwargs:
874                self._build_join_clauses()
875            return
876
877        if self.inverse:
878            if self.inverse.table:
879                self.table = self.inverse.table
880                self.primaryjoin_clauses = self.inverse.secondaryjoin_clauses
881                self.secondaryjoin_clauses = self.inverse.primaryjoin_clauses
882                return
883
884        e1_desc = self.entity._descriptor
885        e2_desc = self.target._descriptor
886
887        e1_schema = e1_desc.table_options.get('schema', None)
888        e2_schema = e2_desc.table_options.get('schema', None)
889        schema = (self.schema is not None) and self.schema or e1_schema
890
891        assert e1_schema == e2_schema or self.schema, \
892               "Schema %r for entity %s differs from schema %r of entity %s." \
893               " Consider using the schema-parameter. "\
894               % (e1_schema, self.entity.__name__,
895                  e2_schema, self.target.__name__)
896
897        # First, we compute the name of the table. Note that some of the
898        # intermediary variables are reused later for the constraint
899        # names.
900
901        # We use the name of the relation for the first entity
902        # (instead of the name of its primary key), so that we can
903        # have two many-to-many relations between the same objects
904        # without having a table name collision.
905        source_part = "%s_%s" % (e1_desc.tablename, self.name)
906
907        # And we use only the name of the table of the second entity
908        # when there is no inverse, so that a many-to-many relation
909        # can be defined without an inverse.
910        if self.inverse:
911            target_part = "%s_%s" % (e2_desc.tablename, self.inverse.name)
912        else:
913            target_part = e2_desc.tablename
914
915        if self.user_tablename:
916            tablename = self.user_tablename
917        else:
918            # We need to keep the table name consistent (independant of
919            # whether this relation or its inverse is setup first).
920            if self.inverse and source_part < target_part:
921                #TODO: if self-relf, only include source_part
922                tablename = "%s__%s" % (target_part, source_part)
923                if options.CHECK_TABLENAME_CHANGES and \
924                   e1_desc.tablename >= e2_desc.tablename:
925                    oldname = "%s__%s" % (source_part, target_part)
926
927                    raise Exception(
928                        "The generated table name for the '%s' relationship "
929                        "on the '%s' entity changed from '%s' (the name "
930                        "generated by Elixir 0.6.1 and earlier) to '%s'. "
931                        "You should either rename the table in the database "
932                        "to the new name or use the tablename argument on the "
933                        "relationship to force the old name: tablename='%s'!"
934                        % (self.name, self.entity.__name__, oldname,
935                           tablename, oldname))
936            else:
937                tablename = "%s__%s" % (source_part, target_part)
938
939        if e1_desc.autoload:
940            if not e2_desc.autoload:
941                raise Exception(
942                    "Entity '%s' is autoloaded and its '%s' "
943                    "ManyToMany relationship points to "
944                    "the '%s' entity which is not autoloaded"
945                    % (self.entity.__name__, self.name,
946                       self.target.__name__))
947
948            self.table = Table(tablename, e1_desc.metadata, autoload=True)
949            if 'primaryjoin' not in self.kwargs or \
950               'secondaryjoin' not in self.kwargs:
951                self._build_join_clauses()
952        else:
953            # We pre-compute the names of the foreign key constraints
954            # pointing to the source (local) entity's table and to the
955            # target's table
956
957            # In some databases (at least MySQL) the constraint names need
958            # to be unique for the whole database, instead of per table.
959            source_fk_name = "%s_fk" % source_part
960            if self.inverse:
961                target_fk_name = "%s_fk" % target_part
962            else:
963                target_fk_name = "%s_inverse_fk" % source_part
964
965            columns = []
966            constraints = []
967
968            joins = (self.primaryjoin_clauses, self.secondaryjoin_clauses)
969            for num, desc, fk_name, rel, colnames in (
970              (0, e1_desc, source_fk_name, self, self.local_colname),
971              (1, e2_desc, target_fk_name, self.inverse, self.remote_colname)):
972
973                fk_colnames = []
974                fk_refcols = []
975
976                if colnames:
977                    assert len(colnames) == len(desc.primary_keys)
978                else:
979                    for pk_col in desc.primary_keys:
980                        colname = self.column_format % \
981                                  {'tablename': desc.tablename,
982                                   'key': pk_col.key,
983                                   'entity': desc.entity.__name__.lower()}
984                        # In case we have a many-to-many self-reference, we
985                        # need to tweak the names of the columns so that we
986                        # don't end up with twice the same column name.
987                        if self.entity is self.target:
988                            colname += str(num + 1)
989                        colnames.append(colname)
990
991                for pk_col, colname in zip(desc.primary_keys, colnames):
992                    col = Column(colname, pk_col.type, primary_key=True)
993                    columns.append(col)
994
995                    # Build the list of local columns which will be part
996                    # of the foreign key.
997                    fk_colnames.append(colname)
998
999                    # Build the list of column "paths" the foreign key will
1000                    # point to
1001                    target_path = "%s.%s" % (desc.table_fullname, pk_col.key)
1002                    fk_refcols.append(target_path)
1003
1004                    # Build join clauses (in case we have a self-ref)
1005                    if self.entity is self.target:
1006                        joins[num].append(col == pk_col)
1007
1008                onupdate = rel and rel.onupdate
1009                ondelete = rel and rel.ondelete
1010
1011                constraints.append(
1012                    ForeignKeyConstraint(fk_colnames, fk_refcols,
1013                                         name=fk_name, onupdate=onupdate,
1014                                         ondelete=ondelete))
1015
1016            args = columns + constraints
1017
1018            self.table = Table(tablename, e1_desc.metadata,
1019                               schema=schema, *args)
1020            if DEBUG:
1021                print self.table.repr2()
1022
1023    def _build_join_clauses(self):
1024        # In the case we have a self-reference, we need to build join clauses
1025        if self.entity is self.target:
1026            #TODO: we should check if that information was defined on the
1027            #inverse relationship. We have two options: either we force the
1028            #definition on both sides, or we accept defining on one side only.
1029            #In the later case, we still need to make the check as to not
1030            #peusdo-randomly fail depending on initialization order.
1031            if not self.local_colname and not self.remote_colname:
1032                raise Exception(
1033                    "Self-referential ManyToMany "
1034                    "relationships in autoloaded entities need to have at "
1035                    "least one of either 'local_colname' or 'remote_colname' "
1036                    "argument specified. The '%s' relationship in the '%s' "
1037                    "entity doesn't have either."
1038                    % (self.name, self.entity.__name__))
1039
1040            self.primaryjoin_clauses, self.secondaryjoin_clauses = \
1041                _get_join_clauses(self.table,
1042                                  self.local_colname, self.remote_colname,
1043                                  self.entity.table)
1044
1045    def get_prop_kwargs(self):
1046        kwargs = {'secondary': self.table,
1047                  'uselist': self.uselist}
1048
1049        if self.filter:
1050            # we need to make a copy of the joinclauses
1051            secondaryjoin_clauses = self.secondaryjoin_clauses[:] + \
1052                                    [self.filter(self.target.table.c)]
1053        else:
1054            secondaryjoin_clauses = self.secondaryjoin_clauses
1055
1056        if self.target is self.entity or self.filter:
1057            kwargs['primaryjoin'] = and_(*self.primaryjoin_clauses)
1058            kwargs['secondaryjoin'] = and_(*secondaryjoin_clauses)
1059
1060        kwargs.update(self.kwargs)
1061
1062        return kwargs
1063
1064    def is_inverse(self, other):
1065        return super(ManyToMany, self).is_inverse(other) and \
1066               (self.user_tablename == other.user_tablename or
1067                (not self.user_tablename and not other.user_tablename))
1068
1069
1070def _get_join_clauses(local_table, local_cols1, local_cols2, target_table):
1071    primary_join, secondary_join = [], []
1072    cols1 = local_cols1[:]
1073    cols1.sort()
1074    cols1 = tuple(cols1)
1075
1076    if local_cols2 is not None:
1077        cols2 = local_cols2[:]
1078        cols2.sort()
1079        cols2 = tuple(cols2)
1080    else:
1081        cols2 = None
1082
1083    # Build a map of fk constraints pointing to the correct table.
1084    # The map is indexed on the local col names.
1085    constraint_map = {}
1086    for constraint in local_table.constraints:
1087        if isinstance(constraint, ForeignKeyConstraint):
1088            use_constraint = True
1089            fk_colnames = []
1090
1091            # if all columns point to the correct table, we use the constraint
1092            #TODO: check that it contains as many columns as the pk of the
1093            #target entity, or even that it points to the actual pk columns
1094            for fk in constraint.elements:
1095                if fk.references(target_table):
1096                    # local column key
1097                    fk_colnames.append(fk.parent.key)
1098                else:
1099                    use_constraint = False
1100            if use_constraint:
1101                fk_colnames.sort()
1102                constraint_map[tuple(fk_colnames)] = constraint
1103
1104    # Either the fk column names match explicitely with the columns given for
1105    # one of the joins (primary or secondary), or we assume the current
1106    # columns match because the columns for this join were not given and we
1107    # know the other join is either not used (is None) or has an explicit
1108    # match.
1109
1110#TODO: rewrite this. Even with the comment, I don't even understand it myself.
1111    for cols, constraint in constraint_map.iteritems():
1112        if cols == cols1 or (cols != cols2 and
1113                             not cols1 and (cols2 in constraint_map or
1114                                            cols2 is None)):
1115            join = primary_join
1116        elif cols == cols2 or (cols2 == () and cols1 in constraint_map):
1117            join = secondary_join
1118        else:
1119            continue
1120        for fk in constraint.elements:
1121            join.append(fk.parent == fk.column)
1122    return primary_join, secondary_join
1123
1124
1125def rel_mutator_handler(target):
1126    def handler(entity, name, of_kind=None, through=None, via=None,
1127                *args, **kwargs):
1128        if through and via:
1129            setattr(entity, name,
1130                    association_proxy(through, via, **kwargs))
1131            return
1132        elif through or via:
1133            raise Exception("'through' and 'via' relationship keyword "
1134                            "arguments should be used in combination.")
1135        rel = target(of_kind, *args, **kwargs)
1136        rel.attach(entity, name)
1137    return handler
1138
1139
1140belongs_to = ClassMutator(rel_mutator_handler(ManyToOne))
1141has_one = ClassMutator(rel_mutator_handler(OneToOne))
1142has_many = ClassMutator(rel_mutator_handler(OneToMany))
1143has_and_belongs_to_many = ClassMutator(rel_mutator_handler(ManyToMany))
Note: See TracBrowser for help on using the browser.