~ubuntu-branches/debian/jessie/sqlalchemy/jessie

« back to all changes in this revision

Viewing changes to lib/sqlalchemy/orm/properties.py

  • Committer: Package Import Robot
  • Author(s): Piotr Ożarowski, Jakub Wilk, Piotr Ożarowski
  • Date: 2013-07-06 20:53:52 UTC
  • mfrom: (1.4.23) (16.1.17 experimental)
  • Revision ID: package-import@ubuntu.com-20130706205352-ryppl1eto3illd79
Tags: 0.8.2-1
[ Jakub Wilk ]
* Use canonical URIs for Vcs-* fields.

[ Piotr Ożarowski ]
* New upstream release
* Upload to unstable
* Build depend on python3-all instead of -dev, extensions are not built for
  Python 3.X 

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# orm/properties.py
2
 
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
 
2
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
3
3
#
4
4
# This module is part of SQLAlchemy and is released under
5
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
11
11
 
12
12
"""
13
13
 
14
 
from sqlalchemy import sql, util, log, exc as sa_exc
15
 
from sqlalchemy.sql.util import ClauseAdapter, criterion_as_pairs, \
16
 
    join_condition, _shallow_annotate
17
 
from sqlalchemy.sql import operators, expression
18
 
from sqlalchemy.orm import attributes, dependency, mapper, \
19
 
    object_mapper, strategies, configure_mappers
20
 
from sqlalchemy.orm.util import CascadeOptions, _class_to_mapper, \
21
 
    _orm_annotate, _orm_deannotate
22
 
 
23
 
from sqlalchemy.orm.interfaces import MANYTOMANY, MANYTOONE, \
24
 
    MapperProperty, ONETOMANY, PropComparator, StrategizedProperty
 
14
from .. import sql, util, log, exc as sa_exc, inspect
 
15
from ..sql import operators, expression
 
16
from . import (
 
17
    attributes, mapper,
 
18
    strategies, configure_mappers, relationships,
 
19
    dependency
 
20
    )
 
21
from .util import CascadeOptions, \
 
22
        _orm_annotate, _orm_deannotate, _orm_full_deannotate
 
23
 
 
24
from .interfaces import MANYTOMANY, MANYTOONE, ONETOMANY,\
 
25
        PropComparator, StrategizedProperty
 
26
 
25
27
mapperlib = util.importlater("sqlalchemy.orm", "mapperlib")
26
28
NoneType = type(None)
27
29
 
28
 
__all__ = ('ColumnProperty', 'CompositeProperty', 'SynonymProperty',
29
 
           'ComparableProperty', 'RelationshipProperty', 'RelationProperty')
30
 
 
31
30
from descriptor_props import CompositeProperty, SynonymProperty, \
32
 
            ComparableProperty,ConcreteInheritedProperty
 
31
            ComparableProperty, ConcreteInheritedProperty
 
32
 
 
33
__all__ = ['ColumnProperty', 'CompositeProperty', 'SynonymProperty',
 
34
           'ComparableProperty', 'RelationshipProperty', 'RelationProperty']
 
35
 
33
36
 
34
37
class ColumnProperty(StrategizedProperty):
35
38
    """Describes an object attribute that corresponds to a table column.
41
44
    def __init__(self, *columns, **kwargs):
42
45
        """Construct a ColumnProperty.
43
46
 
44
 
        Note the public constructor is the :func:`.orm.column_property` function.
 
47
        Note the public constructor is the :func:`.orm.column_property`
 
48
        function.
45
49
 
46
50
        :param \*columns: The list of `columns` describes a single
47
51
          object property. If there are multiple tables joined
60
64
 
61
65
        :param extension:
62
66
 
 
67
        :param info: Optional data dictionary which will be populated into the
 
68
         :attr:`.info` attribute of this object.
 
69
 
63
70
        """
64
71
        self._orig_columns = [expression._labeled(c) for c in columns]
65
 
        self.columns = [expression._labeled(_orm_deannotate(c))
 
72
        self.columns = [expression._labeled(_orm_full_deannotate(c))
66
73
                            for c in columns]
67
74
        self.group = kwargs.pop('group', None)
68
75
        self.deferred = kwargs.pop('deferred', False)
74
81
        self.active_history = kwargs.pop('active_history', False)
75
82
        self.expire_on_flush = kwargs.pop('expire_on_flush', True)
76
83
 
 
84
        if 'info' in kwargs:
 
85
            self.info = kwargs.pop('info')
 
86
 
77
87
        if 'doc' in kwargs:
78
88
            self.doc = kwargs.pop('doc')
79
89
        else:
99
109
        else:
100
110
            self.strategy_class = strategies.ColumnLoader
101
111
 
 
112
    @property
 
113
    def expression(self):
 
114
        """Return the primary column or expression for this ColumnProperty.
 
115
 
 
116
        """
 
117
        return self.columns[0]
 
118
 
102
119
    def instrument_class(self, mapper):
103
120
        if not self.instrument:
104
121
            return
147
164
                impl = dest_state.get_impl(self.key)
148
165
                impl.set(dest_state, dest_dict, value, None)
149
166
        elif dest_state.has_identity and self.key not in dest_dict:
150
 
            dest_state.expire_attributes(dest_dict, [self.key])
 
167
            dest_state._expire_attributes(dest_dict, [self.key])
151
168
 
152
169
    class Comparator(PropComparator):
 
170
        """Produce boolean, comparison, and other operators for
 
171
        :class:`.ColumnProperty` attributes.
 
172
 
 
173
        See the documentation for :class:`.PropComparator` for a brief
 
174
        overview.
 
175
 
 
176
        See also:
 
177
 
 
178
        :class:`.PropComparator`
 
179
 
 
180
        :class:`.ColumnOperators`
 
181
 
 
182
        :ref:`types_operators`
 
183
 
 
184
        :attr:`.TypeEngine.comparator_factory`
 
185
 
 
186
        """
153
187
        @util.memoized_instancemethod
154
188
        def __clause_element__(self):
155
189
            if self.adapter:
156
190
                return self.adapter(self.prop.columns[0])
157
191
            else:
158
192
                return self.prop.columns[0]._annotate({
159
 
                                                "parententity": self.mapper,
160
 
                                                "parentmapper":self.mapper})
 
193
                    "parententity": self._parentmapper,
 
194
                    "parentmapper": self._parentmapper})
 
195
 
 
196
        @util.memoized_property
 
197
        def info(self):
 
198
            ce = self.__clause_element__()
 
199
            try:
 
200
                return ce.info
 
201
            except AttributeError:
 
202
                return self.prop.info
 
203
 
 
204
        def __getattr__(self, key):
 
205
            """proxy attribute access down to the mapped column.
 
206
 
 
207
            this allows user-defined comparison methods to be accessed.
 
208
            """
 
209
            return getattr(self.__clause_element__(), key)
161
210
 
162
211
        def operate(self, op, *other, **kwargs):
163
212
            return op(self.__clause_element__(), *other, **kwargs)
174
223
 
175
224
log.class_logger(ColumnProperty)
176
225
 
 
226
 
177
227
class RelationshipProperty(StrategizedProperty):
178
228
    """Describes an object property that holds a single item or list
179
229
    of items that correspond to a related database table.
180
230
 
181
231
    Public constructor is the :func:`.orm.relationship` function.
182
232
 
183
 
    Of note here is the :class:`.RelationshipProperty.Comparator`
184
 
    class, which implements comparison operations for scalar-
185
 
    and collection-referencing mapped attributes.
 
233
    See also:
 
234
 
 
235
    :ref:`relationship_config_toplevel`
186
236
 
187
237
    """
188
238
 
189
239
    strategy_wildcard_key = 'relationship:*'
190
240
 
 
241
    _dependency_processor = None
 
242
 
191
243
    def __init__(self, argument,
192
244
        secondary=None, primaryjoin=None,
193
245
        secondaryjoin=None,
209
261
        cascade_backrefs=True,
210
262
        load_on_pending=False,
211
263
        strategy_class=None, _local_remote_pairs=None,
212
 
        query_class=None):
 
264
        query_class=None,
 
265
            info=None):
213
266
 
214
267
        self.uselist = uselist
215
268
        self.argument = argument
241
294
        self.comparator = self.comparator_factory(self, None)
242
295
        util.set_creation_order(self)
243
296
 
 
297
        if info is not None:
 
298
            self.info = info
 
299
 
244
300
        if strategy_class:
245
301
            self.strategy_class = strategy_class
246
 
        elif self.lazy== 'dynamic':
 
302
        elif self.lazy == 'dynamic':
247
303
            from sqlalchemy.orm import dynamic
248
304
            self.strategy_class = dynamic.DynaLoader
249
305
        else:
251
307
 
252
308
        self._reverse_property = set()
253
309
 
254
 
        if cascade is not False:
255
 
            self.cascade = CascadeOptions(cascade)
256
 
        else:
257
 
            self.cascade = CascadeOptions("save-update, merge")
258
 
 
259
 
        if self.passive_deletes == 'all' and \
260
 
                    ("delete" in self.cascade or
261
 
                    "delete-orphan" in self.cascade):
262
 
            raise sa_exc.ArgumentError(
263
 
                            "Can't set passive_deletes='all' in conjunction "
264
 
                            "with 'delete' or 'delete-orphan' cascade")
 
310
        self.cascade = cascade if cascade is not False \
 
311
                            else "save-update, merge"
265
312
 
266
313
        self.order_by = order_by
267
314
 
276
323
        else:
277
324
            self.backref = backref
278
325
 
279
 
 
280
326
    def instrument_class(self, mapper):
281
327
        attributes.register_descriptor(
282
328
            mapper.class_,
287
333
            )
288
334
 
289
335
    class Comparator(PropComparator):
290
 
        """Produce comparison operations for :func:`~.orm.relationship`-based
291
 
         attributes."""
292
 
 
293
 
        def __init__(self, prop, mapper, of_type=None, adapter=None):
 
336
        """Produce boolean, comparison, and other operators for
 
337
        :class:`.RelationshipProperty` attributes.
 
338
 
 
339
        See the documentation for :class:`.PropComparator` for a brief overview
 
340
        of ORM level operator definition.
 
341
 
 
342
        See also:
 
343
 
 
344
        :class:`.PropComparator`
 
345
 
 
346
        :class:`.ColumnProperty.Comparator`
 
347
 
 
348
        :class:`.ColumnOperators`
 
349
 
 
350
        :ref:`types_operators`
 
351
 
 
352
        :attr:`.TypeEngine.comparator_factory`
 
353
 
 
354
        """
 
355
 
 
356
        _of_type = None
 
357
 
 
358
        def __init__(self, prop, parentmapper, of_type=None, adapter=None):
294
359
            """Construction of :class:`.RelationshipProperty.Comparator`
295
360
            is internal to the ORM's attribute mechanics.
296
361
 
297
362
            """
298
363
            self.prop = prop
299
 
            self.mapper = mapper
 
364
            self._parentmapper = parentmapper
300
365
            self.adapter = adapter
301
366
            if of_type:
302
 
                self._of_type = _class_to_mapper(of_type)
 
367
                self._of_type = of_type
303
368
 
304
369
        def adapted(self, adapter):
305
370
            """Return a copy of this PropComparator which will use the
308
373
 
309
374
            """
310
375
 
311
 
            return self.__class__(self.property, self.mapper,
 
376
            return self.__class__(self.property, self._parentmapper,
312
377
                                  getattr(self, '_of_type', None),
313
378
                                  adapter)
314
379
 
315
 
        @property
316
 
        def parententity(self):
 
380
        @util.memoized_property
 
381
        def mapper(self):
 
382
            """The target :class:`.Mapper` referred to by this
 
383
            :class:`.RelationshipProperty.Comparator.
 
384
 
 
385
            This is the "target" or "remote" side of the
 
386
            :func:`.relationship`.
 
387
 
 
388
            """
 
389
            return self.property.mapper
 
390
 
 
391
        @util.memoized_property
 
392
        def _parententity(self):
317
393
            return self.property.parent
318
394
 
319
 
        def __clause_element__(self):
 
395
        def _source_selectable(self):
320
396
            elem = self.property.parent._with_polymorphic_selectable
321
397
            if self.adapter:
322
398
                return self.adapter(elem)
323
399
            else:
324
400
                return elem
325
401
 
 
402
        def __clause_element__(self):
 
403
            adapt_from = self._source_selectable()
 
404
            if self._of_type:
 
405
                of_type = inspect(self._of_type).mapper
 
406
            else:
 
407
                of_type = None
 
408
 
 
409
            pj, sj, source, dest, \
 
410
            secondary, target_adapter = self.property._create_joins(
 
411
                            source_selectable=adapt_from,
 
412
                            source_polymorphic=True,
 
413
                            of_type=of_type)
 
414
            if sj is not None:
 
415
                return pj & sj
 
416
            else:
 
417
                return pj
 
418
 
326
419
        def of_type(self, cls):
327
420
            """Produce a construct that represents a particular 'subtype' of
328
421
            attribute for the parent class.
333
426
            """
334
427
            return RelationshipProperty.Comparator(
335
428
                                        self.property,
336
 
                                        self.mapper,
 
429
                                        self._parentmapper,
337
430
                                        cls, adapter=self.adapter)
338
431
 
339
432
        def in_(self, other):
384
477
              or many-to-many context produce a NOT EXISTS clause.
385
478
 
386
479
            """
387
 
            if isinstance(other, (NoneType, expression._Null)):
 
480
            if isinstance(other, (NoneType, expression.Null)):
388
481
                if self.property.direction in [ONETOMANY, MANYTOMANY]:
389
482
                    return ~self._criterion_exists()
390
483
                else:
400
493
 
401
494
        def _criterion_exists(self, criterion=None, **kwargs):
402
495
            if getattr(self, '_of_type', None):
403
 
                target_mapper = self._of_type
404
 
                to_selectable = target_mapper._with_polymorphic_selectable
405
 
                if self.property._is_self_referential:
 
496
                info = inspect(self._of_type)
 
497
                target_mapper, to_selectable, is_aliased_class = \
 
498
                    info.mapper, info.selectable, info.is_aliased_class
 
499
                if self.property._is_self_referential and not is_aliased_class:
406
500
                    to_selectable = to_selectable.alias()
407
501
 
408
502
                single_crit = target_mapper._single_table_criterion
412
506
                    else:
413
507
                        criterion = single_crit
414
508
            else:
 
509
                is_aliased_class = False
415
510
                to_selectable = None
416
511
 
417
512
            if self.adapter:
418
 
                source_selectable = self.__clause_element__()
 
513
                source_selectable = self._source_selectable()
419
514
            else:
420
515
                source_selectable = None
421
516
 
439
534
            else:
440
535
                j = _orm_annotate(pj, exclude=self.property.remote_side)
441
536
 
442
 
            if criterion is not None and target_adapter:
 
537
            if criterion is not None and target_adapter and not is_aliased_class:
443
538
                # limit this adapter to annotated only?
444
539
                criterion = target_adapter.traverse(criterion)
445
540
 
449
544
            # should not correlate or otherwise reach out
450
545
            # to anything in the enclosing query.
451
546
            if criterion is not None:
452
 
                criterion = criterion._annotate({'no_replacement_traverse': True})
 
547
                criterion = criterion._annotate(
 
548
                    {'no_replacement_traverse': True})
453
549
 
454
550
            crit = j & criterion
455
551
 
456
 
            return sql.exists([1], crit, from_obj=dest).\
457
 
                            correlate(source._annotate({'_orm_adapt':True}))
 
552
            ex = sql.exists([1], crit, from_obj=dest).correlate_except(dest)
 
553
            if secondary is not None:
 
554
                ex = ex.correlate_except(secondary)
 
555
            return ex
458
556
 
459
557
        def any(self, criterion=None, **kwargs):
460
558
            """Produce an expression that tests a collection against
488
586
            will produce::
489
587
 
490
588
                SELECT * FROM my_table WHERE
491
 
                NOT EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id)
 
589
                NOT EXISTS (SELECT 1 FROM related WHERE
 
590
                related.my_id=my_table.id)
492
591
 
493
592
            :meth:`~.RelationshipProperty.Comparator.any` is only
494
593
            valid for collections, i.e. a :func:`.relationship`
518
617
            Will produce a query like::
519
618
 
520
619
                SELECT * FROM my_table WHERE
521
 
                EXISTS (SELECT 1 FROM related WHERE related.id==my_table.related_id
522
 
                AND related.x=2)
 
620
                EXISTS (SELECT 1 FROM related WHERE
 
621
                related.id==my_table.related_id AND related.x=2)
523
622
 
524
623
            Because :meth:`~.RelationshipProperty.Comparator.has` uses
525
624
            a correlated subquery, its performance is not nearly as
612
711
                state = attributes.instance_state(other)
613
712
 
614
713
                def state_bindparam(x, state, col):
615
 
                    o = state.obj() # strong ref
616
 
                    return sql.bindparam(x, unique=True, callable_=lambda : \
617
 
                        self.property.mapper._get_committed_attr_by_column(o,
618
 
                            col))
 
714
                    o = state.obj()  # strong ref
 
715
                    return sql.bindparam(x, unique=True, callable_=lambda: \
 
716
                        self.property.mapper._get_committed_attr_by_column(o, col))
619
717
 
620
718
                def adapt(col):
621
719
                    if self.adapter:
626
724
                if self.property._use_get:
627
725
                    return sql.and_(*[
628
726
                        sql.or_(
629
 
                        adapt(x) != state_bindparam(adapt(x), state, y),
630
 
                        adapt(x) == None)
 
727
                            adapt(x) != state_bindparam(adapt(x), state, y),
 
728
                            adapt(x) == None)
631
729
                        for (x, y) in self.property.local_remote_pairs])
632
730
 
633
 
            criterion = sql.and_(*[x==y for (x, y) in
 
731
            criterion = sql.and_(*[x == y for (x, y) in
634
732
                                zip(
635
733
                                    self.property.mapper.primary_key,
636
734
                                    self.property.\
678
776
              or many-to-many context produce an EXISTS clause.
679
777
 
680
778
            """
681
 
            if isinstance(other, (NoneType, expression._Null)):
 
779
            if isinstance(other, (NoneType, expression.Null)):
682
780
                if self.property.direction == MANYTOONE:
683
781
                    return sql.or_(*[x != None for x in
684
782
                                   self.property._calculated_foreign_keys])
741
839
                if (source_state, r) in _recursive:
742
840
                    return
743
841
 
744
 
 
745
 
        if not "merge" in self.cascade:
 
842
        if not "merge" in self._cascade:
746
843
            return
747
844
 
748
845
        if self.key not in source_dict:
798
895
                dest_state.get_impl(self.key).set(dest_state,
799
896
                        dest_dict, obj, None)
800
897
 
801
 
    def cascade_iterator(self, type_, state, dict_, visited_states, halt_on=None):
802
 
        #assert type_ in self.cascade
 
898
    def _value_as_iterable(self, state, dict_, key,
 
899
                                    passive=attributes.PASSIVE_OFF):
 
900
        """Return a list of tuples (state, obj) for the given
 
901
        key.
 
902
 
 
903
        returns an empty list if the value is None/empty/PASSIVE_NO_RESULT
 
904
        """
 
905
 
 
906
        impl = state.manager[key].impl
 
907
        x = impl.get(state, dict_, passive=passive)
 
908
        if x is attributes.PASSIVE_NO_RESULT or x is None:
 
909
            return []
 
910
        elif hasattr(impl, 'get_collection'):
 
911
            return [
 
912
                (attributes.instance_state(o), o) for o in
 
913
                impl.get_collection(state, dict_, x, passive=passive)
 
914
            ]
 
915
        else:
 
916
            return [(attributes.instance_state(x), x)]
 
917
 
 
918
    def cascade_iterator(self, type_, state, dict_,
 
919
                         visited_states, halt_on=None):
 
920
        #assert type_ in self._cascade
803
921
 
804
922
        # only actively lazy load on the 'delete' cascade
805
923
        if type_ != 'delete' or self.passive_deletes:
812
930
                        get_all_pending(state, dict_)
813
931
 
814
932
        else:
815
 
            tuples = state.value_as_iterable(dict_, self.key,
 
933
            tuples = self._value_as_iterable(state, dict_, self.key,
816
934
                            passive=passive)
817
935
 
818
936
        skip_pending = type_ == 'refresh-expire' and 'delete-orphan' \
819
 
            not in self.cascade
 
937
            not in self._cascade
820
938
 
821
939
        for instance_state, c in tuples:
822
940
            if instance_state in visited_states:
852
970
 
853
971
            yield c, instance_mapper, instance_state, instance_dict
854
972
 
855
 
 
856
973
    def _add_reverse_property(self, key):
857
 
        other = self.mapper.get_property(key, _compile_mappers=False)
 
974
        other = self.mapper.get_property(key, _configure_mappers=False)
858
975
        self._reverse_property.add(other)
859
976
        other._reverse_property.add(self)
860
977
 
864
981
                    'does not reference mapper %s' % (key, self, other,
865
982
                    self.parent))
866
983
        if self.direction in (ONETOMANY, MANYTOONE) and self.direction \
867
 
            == other.direction:
 
984
                        == other.direction:
868
985
            raise sa_exc.ArgumentError('%s and back-reference %s are '
869
986
                    'both of the same direction %r.  Did you mean to '
870
987
                    'set remote_side on the many-to-one side ?'
880
997
        """
881
998
        if isinstance(self.argument, type):
882
999
            mapper_ = mapper.class_mapper(self.argument,
883
 
                    compile=False)
 
1000
                    configure=False)
884
1001
        elif isinstance(self.argument, mapper.Mapper):
885
1002
            mapper_ = self.argument
886
1003
        elif util.callable(self.argument):
889
1006
            # configurational schemes
890
1007
 
891
1008
            mapper_ = mapper.class_mapper(self.argument(),
892
 
                    compile=False)
 
1009
                    configure=False)
893
1010
        else:
894
1011
            raise sa_exc.ArgumentError("relationship '%s' expects "
895
1012
                    "a class or a mapper argument (received: %s)"
908
1025
    def do_init(self):
909
1026
        self._check_conflicts()
910
1027
        self._process_dependent_arguments()
911
 
        self._determine_joins()
912
 
        self._determine_synchronize_pairs()
913
 
        self._determine_direction()
914
 
        self._determine_local_remote_pairs()
 
1028
        self._setup_join_conditions()
 
1029
        self._check_cascade_settings(self._cascade)
915
1030
        self._post_init()
916
1031
        self._generate_backref()
917
1032
        super(RelationshipProperty, self).do_init()
918
1033
 
919
 
    def _check_conflicts(self):
920
 
        """Test that this relationship is legal, warn about
921
 
        inheritance conflicts."""
922
 
 
923
 
        if not self.is_primary() \
924
 
            and not mapper.class_mapper(
925
 
                                self.parent.class_,
926
 
                                compile=False).has_property(self.key):
927
 
            raise sa_exc.ArgumentError("Attempting to assign a new "
928
 
                    "relationship '%s' to a non-primary mapper on "
929
 
                    "class '%s'.  New relationships can only be added "
930
 
                    "to the primary mapper, i.e. the very first mapper "
931
 
                    "created for class '%s' " % (self.key,
932
 
                    self.parent.class_.__name__,
933
 
                    self.parent.class_.__name__))
934
 
 
935
 
        # check for conflicting relationship() on superclass
936
 
        if not self.parent.concrete:
937
 
            for inheriting in self.parent.iterate_to_root():
938
 
                if inheriting is not self.parent \
939
 
                    and inheriting.has_property(self.key):
940
 
                    util.warn("Warning: relationship '%s' on mapper "
941
 
                              "'%s' supersedes the same relationship "
942
 
                              "on inherited mapper '%s'; this can "
943
 
                              "cause dependency issues during flush"
944
 
                              % (self.key, self.parent, inheriting))
945
 
 
946
1034
    def _process_dependent_arguments(self):
947
1035
        """Convert incoming configuration arguments to their
948
1036
        proper form.
954
1042
        # deferred initialization.  This technique is used
955
1043
        # by declarative "string configs" and some recipes.
956
1044
        for attr in (
957
 
            'order_by',
958
 
            'primaryjoin',
959
 
            'secondaryjoin',
960
 
            'secondary',
961
 
            '_user_defined_foreign_keys',
962
 
            'remote_side',
963
 
            ):
 
1045
            'order_by', 'primaryjoin', 'secondaryjoin',
 
1046
            'secondary', '_user_defined_foreign_keys', 'remote_side',
 
1047
                ):
964
1048
            attr_value = getattr(self, attr)
965
1049
            if util.callable(attr_value):
966
1050
                setattr(self, attr, attr_value())
997
1081
 
998
1082
        self.target = self.mapper.mapped_table
999
1083
 
1000
 
        if self.cascade.delete_orphan:
1001
 
            self.mapper.primary_mapper().delete_orphans.append(
 
1084
 
 
1085
    def _setup_join_conditions(self):
 
1086
        self._join_condition = jc = relationships.JoinCondition(
 
1087
                    parent_selectable=self.parent.mapped_table,
 
1088
                    child_selectable=self.mapper.mapped_table,
 
1089
                    parent_local_selectable=self.parent.local_table,
 
1090
                    child_local_selectable=self.mapper.local_table,
 
1091
                    primaryjoin=self.primaryjoin,
 
1092
                    secondary=self.secondary,
 
1093
                    secondaryjoin=self.secondaryjoin,
 
1094
                    parent_equivalents=self.parent._equivalent_columns,
 
1095
                    child_equivalents=self.mapper._equivalent_columns,
 
1096
                    consider_as_foreign_keys=self._user_defined_foreign_keys,
 
1097
                    local_remote_pairs=self.local_remote_pairs,
 
1098
                    remote_side=self.remote_side,
 
1099
                    self_referential=self._is_self_referential,
 
1100
                    prop=self,
 
1101
                    support_sync=not self.viewonly,
 
1102
                    can_be_synced_fn=self._columns_are_mapped
 
1103
        )
 
1104
        self.primaryjoin = jc.deannotated_primaryjoin
 
1105
        self.secondaryjoin = jc.deannotated_secondaryjoin
 
1106
        self.direction = jc.direction
 
1107
        self.local_remote_pairs = jc.local_remote_pairs
 
1108
        self.remote_side = jc.remote_columns
 
1109
        self.local_columns = jc.local_columns
 
1110
        self.synchronize_pairs = jc.synchronize_pairs
 
1111
        self._calculated_foreign_keys = jc.foreign_key_columns
 
1112
        self.secondary_synchronize_pairs = jc.secondary_synchronize_pairs
 
1113
 
 
1114
    def _check_conflicts(self):
 
1115
        """Test that this relationship is legal, warn about
 
1116
        inheritance conflicts."""
 
1117
 
 
1118
        if not self.is_primary() \
 
1119
            and not mapper.class_mapper(
 
1120
                                self.parent.class_,
 
1121
                                configure=False).has_property(self.key):
 
1122
            raise sa_exc.ArgumentError("Attempting to assign a new "
 
1123
                    "relationship '%s' to a non-primary mapper on "
 
1124
                    "class '%s'.  New relationships can only be added "
 
1125
                    "to the primary mapper, i.e. the very first mapper "
 
1126
                    "created for class '%s' " % (self.key,
 
1127
                    self.parent.class_.__name__,
 
1128
                    self.parent.class_.__name__))
 
1129
 
 
1130
        # check for conflicting relationship() on superclass
 
1131
        if not self.parent.concrete:
 
1132
            for inheriting in self.parent.iterate_to_root():
 
1133
                if inheriting is not self.parent \
 
1134
                        and inheriting.has_property(self.key):
 
1135
                    util.warn("Warning: relationship '%s' on mapper "
 
1136
                              "'%s' supersedes the same relationship "
 
1137
                              "on inherited mapper '%s'; this can "
 
1138
                              "cause dependency issues during flush"
 
1139
                              % (self.key, self.parent, inheriting))
 
1140
 
 
1141
    def _get_cascade(self):
 
1142
        """Return the current cascade setting for this
 
1143
        :class:`.RelationshipProperty`.
 
1144
        """
 
1145
        return self._cascade
 
1146
 
 
1147
    def _set_cascade(self, cascade):
 
1148
        cascade = CascadeOptions(cascade)
 
1149
        if 'mapper' in self.__dict__:
 
1150
            self._check_cascade_settings(cascade)
 
1151
        self._cascade = cascade
 
1152
 
 
1153
        if self._dependency_processor:
 
1154
            self._dependency_processor.cascade = cascade
 
1155
 
 
1156
    cascade = property(_get_cascade, _set_cascade)
 
1157
 
 
1158
    def _check_cascade_settings(self, cascade):
 
1159
        if cascade.delete_orphan and not self.single_parent \
 
1160
            and (self.direction is MANYTOMANY or self.direction
 
1161
                 is MANYTOONE):
 
1162
            raise sa_exc.ArgumentError(
 
1163
                    'On %s, delete-orphan cascade is not supported '
 
1164
                    'on a many-to-many or many-to-one relationship '
 
1165
                    'when single_parent is not set.   Set '
 
1166
                    'single_parent=True on the relationship().'
 
1167
                    % self)
 
1168
        if self.direction is MANYTOONE and self.passive_deletes:
 
1169
            util.warn("On %s, 'passive_deletes' is normally configured "
 
1170
                      "on one-to-many, one-to-one, many-to-many "
 
1171
                      "relationships only."
 
1172
                       % self)
 
1173
 
 
1174
        if self.passive_deletes == 'all' and \
 
1175
                    ("delete" in cascade or
 
1176
                    "delete-orphan" in cascade):
 
1177
            raise sa_exc.ArgumentError(
 
1178
                    "On %s, can't set passive_deletes='all' in conjunction "
 
1179
                    "with 'delete' or 'delete-orphan' cascade" % self)
 
1180
 
 
1181
        if cascade.delete_orphan:
 
1182
            self.mapper.primary_mapper()._delete_orphans.append(
1002
1183
                            (self.key, self.parent.class_)
1003
1184
                        )
1004
1185
 
1005
 
    def _determine_joins(self):
1006
 
        """Determine the 'primaryjoin' and 'secondaryjoin' attributes,
1007
 
        if not passed to the constructor already.
1008
 
 
1009
 
        This is based on analysis of the foreign key relationships
1010
 
        between the parent and target mapped selectables.
1011
 
 
1012
 
        """
1013
 
        if self.secondaryjoin is not None and self.secondary is None:
1014
 
            raise sa_exc.ArgumentError("Property '" + self.key
1015
 
                    + "' specified with secondary join condition but "
1016
 
                    "no secondary argument")
1017
 
 
1018
 
        # if join conditions were not specified, figure them out based
1019
 
        # on foreign keys
1020
 
 
1021
 
        def _search_for_join(mapper, table):
1022
 
            # find a join between the given mapper's mapped table and
1023
 
            # the given table. will try the mapper's local table first
1024
 
            # for more specificity, then if not found will try the more
1025
 
            # general mapped table, which in the case of inheritance is
1026
 
            # a join.
1027
 
            return join_condition(mapper.mapped_table, table,
1028
 
                                        a_subset=mapper.local_table)
1029
 
 
1030
 
        try:
1031
 
            if self.secondary is not None:
1032
 
                if self.secondaryjoin is None:
1033
 
                    self.secondaryjoin = _search_for_join(self.mapper,
1034
 
                            self.secondary)
1035
 
                if self.primaryjoin is None:
1036
 
                    self.primaryjoin = _search_for_join(self.parent,
1037
 
                            self.secondary)
1038
 
            else:
1039
 
                if self.primaryjoin is None:
1040
 
                    self.primaryjoin = _search_for_join(self.parent,
1041
 
                            self.target)
1042
 
        except sa_exc.ArgumentError, e:
1043
 
            raise sa_exc.ArgumentError("Could not determine join "
1044
 
                    "condition between parent/child tables on "
1045
 
                    "relationship %s.  Specify a 'primaryjoin' "
1046
 
                    "expression.  If 'secondary' is present, "
1047
 
                    "'secondaryjoin' is needed as well."
1048
 
                    % self)
1049
 
 
1050
1186
    def _columns_are_mapped(self, *cols):
1051
1187
        """Return True if all columns in the given collection are
1052
1188
        mapped by the tables referenced by this :class:`.Relationship`.
1054
1190
        """
1055
1191
        for c in cols:
1056
1192
            if self.secondary is not None \
1057
 
                and self.secondary.c.contains_column(c):
 
1193
                    and self.secondary.c.contains_column(c):
1058
1194
                continue
1059
1195
            if not self.parent.mapped_table.c.contains_column(c) and \
1060
 
                not self.target.c.contains_column(c):
 
1196
                    not self.target.c.contains_column(c):
1061
1197
                return False
1062
1198
        return True
1063
1199
 
1064
 
    def _sync_pairs_from_join(self, join_condition, primary):
1065
 
        """Determine a list of "source"/"destination" column pairs
1066
 
        based on the given join condition, as well as the
1067
 
        foreign keys argument.
1068
 
 
1069
 
        "source" would be a column referenced by a foreign key,
1070
 
        and "destination" would be the column who has a foreign key
1071
 
        reference to "source".
1072
 
 
1073
 
        """
1074
 
 
1075
 
        fks = self._user_defined_foreign_keys
1076
 
        # locate pairs
1077
 
        eq_pairs = criterion_as_pairs(join_condition,
1078
 
                consider_as_foreign_keys=fks,
1079
 
                any_operator=self.viewonly)
1080
 
 
1081
 
        # couldn't find any fks, but we have
1082
 
        # "secondary" - assume the "secondary" columns
1083
 
        # are the fks
1084
 
        if not eq_pairs and \
1085
 
                self.secondary is not None and \
1086
 
                not fks:
1087
 
            fks = set(self.secondary.c)
1088
 
            eq_pairs = criterion_as_pairs(join_condition,
1089
 
                    consider_as_foreign_keys=fks,
1090
 
                    any_operator=self.viewonly)
1091
 
 
1092
 
            if eq_pairs:
1093
 
                util.warn("No ForeignKey objects were present "
1094
 
                            "in secondary table '%s'.  Assumed referenced "
1095
 
                            "foreign key columns %s for join condition '%s' "
1096
 
                            "on relationship %s" % (
1097
 
                            self.secondary.description,
1098
 
                            ", ".join(sorted(["'%s'" % col for col in fks])),
1099
 
                            join_condition,
1100
 
                            self
1101
 
                        ))
1102
 
 
1103
 
        # Filter out just to columns that are mapped.
1104
 
        # If viewonly, allow pairs where the FK col
1105
 
        # was part of "foreign keys" - the column it references
1106
 
        # may be in an un-mapped table - see
1107
 
        # test.orm.test_relationships.ViewOnlyComplexJoin.test_basic
1108
 
        # for an example of this.
1109
 
        eq_pairs = [(l, r) for (l, r) in eq_pairs
1110
 
                    if self._columns_are_mapped(l, r)
1111
 
                    or self.viewonly and
1112
 
                    r in fks]
1113
 
 
1114
 
        if eq_pairs:
1115
 
            return eq_pairs
1116
 
 
1117
 
        # from here below is just determining the best error message
1118
 
        # to report.  Check for a join condition using any operator
1119
 
        # (not just ==), perhaps they need to turn on "viewonly=True".
1120
 
        if not self.viewonly and criterion_as_pairs(join_condition,
1121
 
                consider_as_foreign_keys=self._user_defined_foreign_keys,
1122
 
                any_operator=True):
1123
 
 
1124
 
            err = "Could not locate any "\
1125
 
                    "foreign-key-equated, locally mapped column "\
1126
 
                    "pairs for %s "\
1127
 
                    "condition '%s' on relationship %s." % (
1128
 
                        primary and 'primaryjoin' or 'secondaryjoin',
1129
 
                        join_condition,
1130
 
                        self
1131
 
                    )
1132
 
 
1133
 
            if not self._user_defined_foreign_keys:
1134
 
                err += "  Ensure that the "\
1135
 
                        "referencing Column objects have a "\
1136
 
                        "ForeignKey present, or are otherwise part "\
1137
 
                        "of a ForeignKeyConstraint on their parent "\
1138
 
                        "Table, or specify the foreign_keys parameter "\
1139
 
                        "to this relationship."
1140
 
 
1141
 
            err += "  For more "\
1142
 
                    "relaxed rules on join conditions, the "\
1143
 
                    "relationship may be marked as viewonly=True."
1144
 
 
1145
 
            raise sa_exc.ArgumentError(err)
1146
 
        else:
1147
 
            if self._user_defined_foreign_keys:
1148
 
                raise sa_exc.ArgumentError("Could not determine "
1149
 
                        "relationship direction for %s condition "
1150
 
                        "'%s', on relationship %s, using manual "
1151
 
                        "'foreign_keys' setting.  Do the columns "
1152
 
                        "in 'foreign_keys' represent all, and "
1153
 
                        "only, the 'foreign' columns in this join "
1154
 
                        "condition?  Does the %s Table already "
1155
 
                        "have adequate ForeignKey and/or "
1156
 
                        "ForeignKeyConstraint objects established "
1157
 
                        "(in which case 'foreign_keys' is usually "
1158
 
                        "unnecessary)?"
1159
 
                        % (
1160
 
                            primary and 'primaryjoin' or 'secondaryjoin',
1161
 
                            join_condition,
1162
 
                            self,
1163
 
                            primary and 'mapped' or 'secondary'
1164
 
                        ))
1165
 
            else:
1166
 
                raise sa_exc.ArgumentError("Could not determine "
1167
 
                        "relationship direction for %s condition "
1168
 
                        "'%s', on relationship %s. Ensure that the "
1169
 
                        "referencing Column objects have a "
1170
 
                        "ForeignKey present, or are otherwise part "
1171
 
                        "of a ForeignKeyConstraint on their parent "
1172
 
                        "Table, or specify the foreign_keys parameter "
1173
 
                        "to this relationship."
1174
 
                        % (
1175
 
                            primary and 'primaryjoin' or 'secondaryjoin',
1176
 
                            join_condition,
1177
 
                            self
1178
 
                        ))
1179
 
 
1180
 
    def _determine_synchronize_pairs(self):
1181
 
        """Resolve 'primary'/foreign' column pairs from the primaryjoin
1182
 
        and secondaryjoin arguments.
1183
 
 
1184
 
        """
1185
 
        if self.local_remote_pairs:
1186
 
            if not self._user_defined_foreign_keys:
1187
 
                raise sa_exc.ArgumentError(
1188
 
                        "foreign_keys argument is "
1189
 
                        "required with _local_remote_pairs argument")
1190
 
            self.synchronize_pairs = []
1191
 
            for l, r in self.local_remote_pairs:
1192
 
                if r in self._user_defined_foreign_keys:
1193
 
                    self.synchronize_pairs.append((l, r))
1194
 
                elif l in self._user_defined_foreign_keys:
1195
 
                    self.synchronize_pairs.append((r, l))
1196
 
        else:
1197
 
            self.synchronize_pairs = self._sync_pairs_from_join(
1198
 
                                                self.primaryjoin,
1199
 
                                                True)
1200
 
 
1201
 
        self._calculated_foreign_keys = util.column_set(
1202
 
                                r for (l, r) in
1203
 
                                self.synchronize_pairs)
1204
 
 
1205
 
        if self.secondaryjoin is not None:
1206
 
            self.secondary_synchronize_pairs = self._sync_pairs_from_join(
1207
 
                                                        self.secondaryjoin,
1208
 
                                                        False)
1209
 
            self._calculated_foreign_keys.update(
1210
 
                                r for (l, r) in
1211
 
                                self.secondary_synchronize_pairs)
1212
 
        else:
1213
 
            self.secondary_synchronize_pairs = None
1214
 
 
1215
 
    def _determine_direction(self):
1216
 
        """Determine if this relationship is one to many, many to one,
1217
 
        many to many.
1218
 
 
1219
 
        This is derived from the primaryjoin, presence of "secondary",
1220
 
        and in the case of self-referential the "remote side".
1221
 
 
1222
 
        """
1223
 
        if self.secondaryjoin is not None:
1224
 
            self.direction = MANYTOMANY
1225
 
        elif self._refers_to_parent_table():
1226
 
 
1227
 
            # self referential defaults to ONETOMANY unless the "remote"
1228
 
            # side is present and does not reference any foreign key
1229
 
            # columns
1230
 
 
1231
 
            if self.local_remote_pairs:
1232
 
                remote = [r for (l, r) in self.local_remote_pairs]
1233
 
            elif self.remote_side:
1234
 
                remote = self.remote_side
1235
 
            else:
1236
 
                remote = None
1237
 
            if not remote or self._calculated_foreign_keys.difference(l for (l,
1238
 
                    r) in self.synchronize_pairs).intersection(remote):
1239
 
                self.direction = ONETOMANY
1240
 
            else:
1241
 
                self.direction = MANYTOONE
1242
 
        else:
1243
 
            parentcols = util.column_set(self.parent.mapped_table.c)
1244
 
            targetcols = util.column_set(self.mapper.mapped_table.c)
1245
 
 
1246
 
            # fk collection which suggests ONETOMANY.
1247
 
            onetomany_fk = targetcols.intersection(
1248
 
                            self._calculated_foreign_keys)
1249
 
 
1250
 
            # fk collection which suggests MANYTOONE.
1251
 
 
1252
 
            manytoone_fk = parentcols.intersection(
1253
 
                            self._calculated_foreign_keys)
1254
 
 
1255
 
            if onetomany_fk and manytoone_fk:
1256
 
                # fks on both sides.  do the same test only based on the
1257
 
                # local side.
1258
 
                referents = [c for (c, f) in self.synchronize_pairs]
1259
 
                onetomany_local = parentcols.intersection(referents)
1260
 
                manytoone_local = targetcols.intersection(referents)
1261
 
 
1262
 
                if onetomany_local and not manytoone_local:
1263
 
                    self.direction = ONETOMANY
1264
 
                elif manytoone_local and not onetomany_local:
1265
 
                    self.direction = MANYTOONE
1266
 
                else:
1267
 
                    raise sa_exc.ArgumentError(
1268
 
                            "Can't determine relationship"
1269
 
                            " direction for relationship '%s' - foreign "
1270
 
                            "key columns are present in both the parent "
1271
 
                            "and the child's mapped tables.  Specify "
1272
 
                            "'foreign_keys' argument." % self)
1273
 
            elif onetomany_fk:
1274
 
                self.direction = ONETOMANY
1275
 
            elif manytoone_fk:
1276
 
                self.direction = MANYTOONE
1277
 
            else:
1278
 
                raise sa_exc.ArgumentError("Can't determine relationship "
1279
 
                        "direction for relationship '%s' - foreign "
1280
 
                        "key columns are present in neither the parent "
1281
 
                        "nor the child's mapped tables" % self)
1282
 
 
1283
 
        if self.cascade.delete_orphan and not self.single_parent \
1284
 
            and (self.direction is MANYTOMANY or self.direction
1285
 
                 is MANYTOONE):
1286
 
            util.warn('On %s, delete-orphan cascade is not supported '
1287
 
                      'on a many-to-many or many-to-one relationship '
1288
 
                      'when single_parent is not set.   Set '
1289
 
                      'single_parent=True on the relationship().'
1290
 
                      % self)
1291
 
        if self.direction is MANYTOONE and self.passive_deletes:
1292
 
            util.warn("On %s, 'passive_deletes' is normally configured "
1293
 
                      "on one-to-many, one-to-one, many-to-many "
1294
 
                      "relationships only."
1295
 
                       % self)
1296
 
 
1297
 
    def _determine_local_remote_pairs(self):
1298
 
        """Determine pairs of columns representing "local" to
1299
 
        "remote", where "local" columns are on the parent mapper,
1300
 
        "remote" are on the target mapper.
1301
 
 
1302
 
        These pairs are used on the load side only to generate
1303
 
        lazy loading clauses.
1304
 
 
1305
 
        """
1306
 
        if not self.local_remote_pairs and not self.remote_side:
1307
 
            # the most common, trivial case.   Derive
1308
 
            # local/remote pairs from the synchronize pairs.
1309
 
            eq_pairs = util.unique_list(
1310
 
                            self.synchronize_pairs +
1311
 
                            (self.secondary_synchronize_pairs or []))
1312
 
            if self.direction is MANYTOONE:
1313
 
                self.local_remote_pairs = [(r, l) for l, r in eq_pairs]
1314
 
            else:
1315
 
                self.local_remote_pairs = eq_pairs
1316
 
 
1317
 
        # "remote_side" specified, derive from the primaryjoin
1318
 
        # plus remote_side, similarly to how synchronize_pairs
1319
 
        # were determined.
1320
 
        elif self.remote_side:
1321
 
            if self.local_remote_pairs:
1322
 
                raise sa_exc.ArgumentError('remote_side argument is '
1323
 
                    'redundant against more detailed '
1324
 
                    '_local_remote_side argument.')
1325
 
            if self.direction is MANYTOONE:
1326
 
                self.local_remote_pairs = [(r, l) for (l, r) in
1327
 
                        criterion_as_pairs(self.primaryjoin,
1328
 
                        consider_as_referenced_keys=self.remote_side,
1329
 
                        any_operator=True)]
1330
 
 
1331
 
            else:
1332
 
                self.local_remote_pairs = \
1333
 
                    criterion_as_pairs(self.primaryjoin,
1334
 
                        consider_as_foreign_keys=self.remote_side,
1335
 
                        any_operator=True)
1336
 
            if not self.local_remote_pairs:
1337
 
                raise sa_exc.ArgumentError('Relationship %s could '
1338
 
                        'not determine any local/remote column '
1339
 
                        'pairs from remote side argument %r'
1340
 
                        % (self, self.remote_side))
1341
 
        # else local_remote_pairs were sent explcitly via
1342
 
        # ._local_remote_pairs.
1343
 
 
1344
 
        # create local_side/remote_side accessors
1345
 
        self.local_side = util.ordered_column_set(
1346
 
                            l for l, r in self.local_remote_pairs)
1347
 
        self.remote_side = util.ordered_column_set(
1348
 
                            r for l, r in self.local_remote_pairs)
1349
 
 
1350
 
        # check that the non-foreign key column in the local/remote
1351
 
        # collection is mapped.  The foreign key
1352
 
        # which the individual mapped column references directly may
1353
 
        # itself be in a non-mapped table; see
1354
 
        # test.orm.test_relationships.ViewOnlyComplexJoin.test_basic
1355
 
        # for an example of this.
1356
 
        if self.direction is ONETOMANY:
1357
 
            for col in self.local_side:
1358
 
                if not self._columns_are_mapped(col):
1359
 
                    raise sa_exc.ArgumentError(
1360
 
                            "Local column '%s' is not "
1361
 
                            "part of mapping %s.  Specify remote_side "
1362
 
                            "argument to indicate which column lazy join "
1363
 
                            "condition should compare against." % (col,
1364
 
                            self.parent))
1365
 
        elif self.direction is MANYTOONE:
1366
 
            for col in self.remote_side:
1367
 
                if not self._columns_are_mapped(col):
1368
 
                    raise sa_exc.ArgumentError(
1369
 
                            "Remote column '%s' is not "
1370
 
                            "part of mapping %s. Specify remote_side "
1371
 
                            "argument to indicate which column lazy join "
1372
 
                            "condition should bind." % (col, self.mapper))
1373
 
 
1374
1200
    def _generate_backref(self):
 
1201
        """Interpret the 'backref' instruction to create a
 
1202
        :func:`.relationship` complementary to this one."""
 
1203
 
1375
1204
        if not self.is_primary():
1376
1205
            return
1377
1206
        if self.backref is not None and not self.back_populates:
1380
1209
            else:
1381
1210
                backref_key, kwargs = self.backref
1382
1211
            mapper = self.mapper.primary_mapper()
1383
 
            if mapper.has_property(backref_key):
1384
 
                raise sa_exc.ArgumentError("Error creating backref "
1385
 
                        "'%s' on relationship '%s': property of that "
1386
 
                        "name exists on mapper '%s'" % (backref_key,
1387
 
                        self, mapper))
 
1212
 
 
1213
            check = set(mapper.iterate_to_root()).\
 
1214
                        union(mapper.self_and_descendants)
 
1215
            for m in check:
 
1216
                if m.has_property(backref_key):
 
1217
                    raise sa_exc.ArgumentError("Error creating backref "
 
1218
                            "'%s' on relationship '%s': property of that "
 
1219
                            "name exists on mapper '%s'" % (backref_key,
 
1220
                            self, m))
 
1221
 
 
1222
            # determine primaryjoin/secondaryjoin for the
 
1223
            # backref.  Use the one we had, so that
 
1224
            # a custom join doesn't have to be specified in
 
1225
            # both directions.
1388
1226
            if self.secondary is not None:
1389
 
                pj = kwargs.pop('primaryjoin', self.secondaryjoin)
1390
 
                sj = kwargs.pop('secondaryjoin', self.primaryjoin)
 
1227
                # for many to many, just switch primaryjoin/
 
1228
                # secondaryjoin.   use the annotated
 
1229
                # pj/sj on the _join_condition.
 
1230
                pj = kwargs.pop('primaryjoin',
 
1231
                                self._join_condition.secondaryjoin_minus_local)
 
1232
                sj = kwargs.pop('secondaryjoin',
 
1233
                                self._join_condition.primaryjoin_minus_local)
1391
1234
            else:
1392
 
                pj = kwargs.pop('primaryjoin', self.primaryjoin)
 
1235
                pj = kwargs.pop('primaryjoin',
 
1236
                        self._join_condition.primaryjoin_reverse_remote)
1393
1237
                sj = kwargs.pop('secondaryjoin', None)
1394
1238
                if sj:
1395
1239
                    raise sa_exc.InvalidRequestError(
1396
 
                        "Can't assign 'secondaryjoin' on a backref against "
1397
 
                        "a non-secondary relationship."
1398
 
                            )
 
1240
                        "Can't assign 'secondaryjoin' on a backref "
 
1241
                        "against a non-secondary relationship."
 
1242
                    )
 
1243
 
1399
1244
            foreign_keys = kwargs.pop('foreign_keys',
1400
1245
                    self._user_defined_foreign_keys)
1401
1246
            parent = self.parent.primary_mapper()
1404
1249
            kwargs.setdefault('passive_updates', self.passive_updates)
1405
1250
            self.back_populates = backref_key
1406
1251
            relationship = RelationshipProperty(
1407
 
                parent,
1408
 
                self.secondary,
1409
 
                pj,
1410
 
                sj,
 
1252
                parent, self.secondary,
 
1253
                pj, sj,
1411
1254
                foreign_keys=foreign_keys,
1412
1255
                back_populates=self.key,
1413
 
                **kwargs
1414
 
                )
 
1256
                **kwargs)
1415
1257
            mapper._configure_property(backref_key, relationship)
1416
1258
 
1417
1259
        if self.back_populates:
1418
1260
            self._add_reverse_property(self.back_populates)
1419
1261
 
1420
1262
    def _post_init(self):
1421
 
        self.logger.info('%s setup primary join %s', self,
1422
 
                         self.primaryjoin)
1423
 
        self.logger.info('%s setup secondary join %s', self,
1424
 
                         self.secondaryjoin)
1425
 
        self.logger.info('%s synchronize pairs [%s]', self,
1426
 
                         ','.join('(%s => %s)' % (l, r) for (l, r) in
1427
 
                         self.synchronize_pairs))
1428
 
        self.logger.info('%s secondary synchronize pairs [%s]', self,
1429
 
                         ','.join('(%s => %s)' % (l, r) for (l, r) in
1430
 
                         self.secondary_synchronize_pairs or []))
1431
 
        self.logger.info('%s local/remote pairs [%s]', self,
1432
 
                         ','.join('(%s / %s)' % (l, r) for (l, r) in
1433
 
                         self.local_remote_pairs))
1434
 
        self.logger.info('%s relationship direction %s', self,
1435
 
                         self.direction)
1436
1263
        if self.uselist is None:
1437
1264
            self.uselist = self.direction is not MANYTOONE
1438
1265
        if not self.viewonly:
1447
1274
        strategy = self._get_strategy(strategies.LazyLoader)
1448
1275
        return strategy.use_get
1449
1276
 
1450
 
    def _refers_to_parent_table(self):
1451
 
        pt = self.parent.mapped_table
1452
 
        mt = self.mapper.mapped_table
1453
 
        for c, f in self.synchronize_pairs:
1454
 
            if (
1455
 
                pt.is_derived_from(c.table) and \
1456
 
                pt.is_derived_from(f.table) and \
1457
 
                mt.is_derived_from(c.table) and \
1458
 
                mt.is_derived_from(f.table)
1459
 
            ):
1460
 
                return True
1461
 
        else:
1462
 
            return False
1463
 
 
1464
1277
    @util.memoized_property
1465
1278
    def _is_self_referential(self):
1466
1279
        return self.mapper.common_parent(self.parent)
1467
1280
 
1468
 
    def per_property_preprocessors(self, uow):
1469
 
        if not self.viewonly and self._dependency_processor:
1470
 
            self._dependency_processor.per_property_preprocessors(uow)
1471
 
 
1472
1281
    def _create_joins(self, source_polymorphic=False,
1473
1282
                            source_selectable=None, dest_polymorphic=False,
1474
1283
                            dest_selectable=None, of_type=None):
1490
1299
        else:
1491
1300
            aliased = True
1492
1301
 
1493
 
        # place a barrier on the destination such that
1494
 
        # replacement traversals won't ever dig into it.
1495
 
        # its internal structure remains fixed
1496
 
        # regardless of context.
1497
 
        dest_selectable = _shallow_annotate(
1498
 
                                dest_selectable,
1499
 
                                {'no_replacement_traverse':True})
1500
 
 
1501
 
        aliased = aliased or (source_selectable is not None)
1502
 
 
1503
 
        primaryjoin, secondaryjoin, secondary = self.primaryjoin, \
1504
 
            self.secondaryjoin, self.secondary
1505
 
 
1506
 
        # adjust the join condition for single table inheritance,
1507
 
        # in the case that the join is to a subclass
1508
 
        # this is analogous to the "_adjust_for_single_table_inheritance()"
1509
 
        # method in Query.
1510
 
 
1511
1302
        dest_mapper = of_type or self.mapper
1512
1303
 
1513
1304
        single_crit = dest_mapper._single_table_criterion
1514
 
        if single_crit is not None:
1515
 
            if secondaryjoin is not None:
1516
 
                secondaryjoin = secondaryjoin & single_crit
1517
 
            else:
1518
 
                primaryjoin = primaryjoin & single_crit
 
1305
        aliased = aliased or (source_selectable is not None)
1519
1306
 
1520
 
        if aliased:
1521
 
            if secondary is not None:
1522
 
                secondary = secondary.alias()
1523
 
                primary_aliasizer = ClauseAdapter(secondary)
1524
 
                secondary_aliasizer = \
1525
 
                    ClauseAdapter(dest_selectable,
1526
 
                        equivalents=self.mapper._equivalent_columns).\
1527
 
                        chain(primary_aliasizer)
1528
 
                if source_selectable is not None:
1529
 
                    primary_aliasizer = \
1530
 
                        ClauseAdapter(secondary).\
1531
 
                            chain(ClauseAdapter(source_selectable,
1532
 
                            equivalents=self.parent._equivalent_columns))
1533
 
                secondaryjoin = \
1534
 
                    secondary_aliasizer.traverse(secondaryjoin)
1535
 
            else:
1536
 
                primary_aliasizer = ClauseAdapter(dest_selectable,
1537
 
                        exclude=self.local_side,
1538
 
                        equivalents=self.mapper._equivalent_columns)
1539
 
                if source_selectable is not None:
1540
 
                    primary_aliasizer.chain(
1541
 
                        ClauseAdapter(source_selectable,
1542
 
                            exclude=self.remote_side,
1543
 
                            equivalents=self.parent._equivalent_columns))
1544
 
                secondary_aliasizer = None
1545
 
            primaryjoin = primary_aliasizer.traverse(primaryjoin)
1546
 
            target_adapter = secondary_aliasizer or primary_aliasizer
1547
 
            target_adapter.include = target_adapter.exclude = None
1548
 
        else:
1549
 
            target_adapter = None
 
1307
        primaryjoin, secondaryjoin, secondary, target_adapter, dest_selectable = \
 
1308
            self._join_condition.join_targets(
 
1309
                source_selectable, dest_selectable, aliased, single_crit
 
1310
            )
1550
1311
        if source_selectable is None:
1551
1312
            source_selectable = self.parent.local_table
1552
1313
        if dest_selectable is None:
1553
1314
            dest_selectable = self.mapper.local_table
1554
 
        return (
1555
 
            primaryjoin,
1556
 
            secondaryjoin,
1557
 
            source_selectable,
1558
 
            dest_selectable,
1559
 
            secondary,
1560
 
            target_adapter,
1561
 
            )
 
1315
        return (primaryjoin, secondaryjoin, source_selectable,
 
1316
            dest_selectable, secondary, target_adapter)
 
1317
 
1562
1318
 
1563
1319
PropertyLoader = RelationProperty = RelationshipProperty
1564
1320
log.class_logger(RelationshipProperty)
1565