root/galaxy-central/eggs/SQLAlchemy-0.5.6_dev_r6498-py2.6.egg/sqlalchemy/orm/properties.py @ 3

リビジョン 3, 51.3 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# properties.py
2# Copyright (C) 2005, 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com
3#
4# This module is part of SQLAlchemy and is released under
5# the MIT License: http://www.opensource.org/licenses/mit-license.php
6
7"""MapperProperty implementations.
8
9This is a private module which defines the behavior of invidual ORM-mapped
10attributes.
11
12"""
13
14from sqlalchemy import sql, util, log
15import sqlalchemy.exceptions as sa_exc
16from sqlalchemy.sql.util import ClauseAdapter, criterion_as_pairs, join_condition
17from sqlalchemy.sql import operators, expression
18from sqlalchemy.orm import (
19    attributes, dependency, mapper, object_mapper, strategies,
20    )
21from sqlalchemy.orm.util import CascadeOptions, _class_to_mapper, _orm_annotate, _orm_deannotate
22from sqlalchemy.orm.interfaces import (
23    MANYTOMANY, MANYTOONE, MapperProperty, ONETOMANY, PropComparator,
24    StrategizedProperty,
25    )
26
27__all__ = ('ColumnProperty', 'CompositeProperty', 'SynonymProperty',
28           'ComparableProperty', 'RelationProperty', 'BackRef')
29
30
31class ColumnProperty(StrategizedProperty):
32    """Describes an object attribute that corresponds to a table column."""
33
34    def __init__(self, *columns, **kwargs):
35        """Construct a ColumnProperty.
36
37        :param \*columns: The list of `columns` describes a single
38          object property. If there are multiple tables joined
39          together for the mapper, this list represents the equivalent
40          column as it appears across each table.
41
42        :param group:
43
44        :param deferred:
45
46        :param comparator_factory:
47
48        :param descriptor:
49
50        :param extension:
51
52        """
53        self.columns = [expression._labeled(c) for c in columns]
54        self.group = kwargs.pop('group', None)
55        self.deferred = kwargs.pop('deferred', False)
56        self.no_instrument = kwargs.pop('_no_instrument', False)
57        self.comparator_factory = kwargs.pop('comparator_factory', self.__class__.Comparator)
58        self.descriptor = kwargs.pop('descriptor', None)
59        self.extension = kwargs.pop('extension', None)
60        if kwargs:
61            raise TypeError(
62                "%s received unexpected keyword argument(s): %s" % (
63                    self.__class__.__name__, ', '.join(sorted(kwargs.keys()))))
64
65        util.set_creation_order(self)
66        if self.no_instrument:
67            self.strategy_class = strategies.UninstrumentedColumnLoader
68        elif self.deferred:
69            self.strategy_class = strategies.DeferredColumnLoader
70        else:
71            self.strategy_class = strategies.ColumnLoader
72   
73    def instrument_class(self, mapper):
74        if self.no_instrument:
75            return
76       
77        attributes.register_descriptor(
78            mapper.class_,
79            self.key,
80            comparator=self.comparator_factory(self, mapper),
81            parententity=mapper,
82            property_=self
83            )
84       
85    def do_init(self):
86        super(ColumnProperty, self).do_init()
87        if len(self.columns) > 1 and self.parent.primary_key.issuperset(self.columns):
88            util.warn(
89                ("On mapper %s, primary key column '%s' is being combined "
90                 "with distinct primary key column '%s' in attribute '%s'.  "
91                 "Use explicit properties to give each column its own mapped "
92                 "attribute name.") % (self.parent, self.columns[1],
93                                       self.columns[0], self.key))
94
95    def copy(self):
96        return ColumnProperty(deferred=self.deferred, group=self.group, *self.columns)
97
98    def getattr(self, state, column):
99        return state.get_impl(self.key).get(state, state.dict)
100
101    def getcommitted(self, state, column, passive=False):
102        return state.get_impl(self.key).get_committed_value(state, state.dict, passive=passive)
103
104    def setattr(self, state, value, column):
105        state.get_impl(self.key).set(state, state.dict, value, None)
106
107    def merge(self, session, source, dest, dont_load, _recursive):
108        value = attributes.instance_state(source).value_as_iterable(
109            self.key, passive=True)
110        if value:
111            setattr(dest, self.key, value[0])
112        else:
113            attributes.instance_state(dest).expire_attributes([self.key])
114
115    def get_col_value(self, column, value):
116        return value
117
118    class Comparator(PropComparator):
119        @util.memoized_instancemethod
120        def __clause_element__(self):
121            if self.adapter:
122                return self.adapter(self.prop.columns[0])
123            else:
124                return self.prop.columns[0]._annotate({"parententity": self.mapper, "parentmapper":self.mapper})
125               
126        def operate(self, op, *other, **kwargs):
127            return op(self.__clause_element__(), *other, **kwargs)
128
129        def reverse_operate(self, op, other, **kwargs):
130            col = self.__clause_element__()
131            return op(col._bind_param(other), col, **kwargs)
132   
133    # TODO: legacy..do we need this ? (0.5)
134    ColumnComparator = Comparator
135   
136    def __str__(self):
137        return str(self.parent.class_.__name__) + "." + self.key
138
139log.class_logger(ColumnProperty)
140
141class CompositeProperty(ColumnProperty):
142    """subclasses ColumnProperty to provide composite type support."""
143   
144    def __init__(self, class_, *columns, **kwargs):
145        if 'comparator' in kwargs:
146            util.warn_deprecated("The 'comparator' argument to CompositeProperty is deprecated.  Use comparator_factory.")
147            kwargs['comparator_factory'] = kwargs['comparator']
148        super(CompositeProperty, self).__init__(*columns, **kwargs)
149        self._col_position_map = util.column_dict((c, i) for i, c in enumerate(columns))
150        self.composite_class = class_
151        self.strategy_class = strategies.CompositeColumnLoader
152
153    def copy(self):
154        return CompositeProperty(deferred=self.deferred, group=self.group, composite_class=self.composite_class, *self.columns)
155
156    def do_init(self):
157        # skip over ColumnProperty's do_init(),
158        # which issues assertions that do not apply to CompositeColumnProperty
159        super(ColumnProperty, self).do_init()
160
161    def getattr(self, state, column):
162        obj = state.get_impl(self.key).get(state, state.dict)
163        return self.get_col_value(column, obj)
164
165    def getcommitted(self, state, column, passive=False):
166        # TODO: no coverage here
167        obj = state.get_impl(self.key).get_committed_value(state, state.dict, passive=passive)
168        return self.get_col_value(column, obj)
169
170    def setattr(self, state, value, column):
171
172        obj = state.get_impl(self.key).get(state, state.dict)
173        if obj is None:
174            obj = self.composite_class(*[None for c in self.columns])
175            state.get_impl(self.key).set(state, state.dict, obj, None)
176
177        if hasattr(obj, '__set_composite_values__'):
178            values = list(obj.__composite_values__())
179            values[self._col_position_map[column]] = value
180            obj.__set_composite_values__(*values)
181        else:
182            setattr(obj, column.key, value)
183           
184    def get_col_value(self, column, value):
185        if value is None:
186            return None
187        for a, b in zip(self.columns, value.__composite_values__()):
188            if a is column:
189                return b
190
191    class Comparator(PropComparator):
192        def __clause_element__(self):
193            if self.adapter:
194                # TODO: test coverage for adapted composite comparison
195                return expression.ClauseList(*[self.adapter(x) for x in self.prop.columns])
196            else:
197                return expression.ClauseList(*self.prop.columns)
198       
199        __hash__ = None
200       
201        def __eq__(self, other):
202            if other is None:
203                values = [None] * len(self.prop.columns)
204            else:
205                values = other.__composite_values__()
206            return sql.and_(*[a==b for a, b in zip(self.prop.columns, values)])
207           
208        def __ne__(self, other):
209            return sql.not_(self.__eq__(other))
210
211    def __str__(self):
212        return str(self.parent.class_.__name__) + "." + self.key
213
214class ConcreteInheritedProperty(MapperProperty):
215    extension = None
216
217    def setup(self, context, entity, path, adapter, **kwargs):
218        pass
219
220    def create_row_processor(self, selectcontext, path, mapper, row, adapter):
221        return (None, None)
222
223    def instrument_class(self, mapper):
224        def warn():
225            raise AttributeError("Concrete %s does not implement attribute %r at "
226                "the instance level.  Add this property explicitly to %s." %
227                (self.parent, self.key, self.parent))
228
229        class NoninheritedConcreteProp(object):
230            def __set__(s, obj, value):
231                warn()
232            def __delete__(s, obj):
233                warn()
234            def __get__(s, obj, owner):
235                warn()
236
237        comparator_callable = None
238        # TODO: put this process into a deferred callable?
239        for m in self.parent.iterate_to_root():
240            p = m._get_property(self.key)
241            if not isinstance(p, ConcreteInheritedProperty):
242                comparator_callable = p.comparator_factory
243                break
244
245        attributes.register_descriptor(
246            mapper.class_,
247            self.key,
248            comparator=comparator_callable(self, mapper),
249            parententity=mapper,
250            property_=self,
251            proxy_property=NoninheritedConcreteProp()
252            )
253
254
255class SynonymProperty(MapperProperty):
256
257    extension = None
258
259    def __init__(self, name, map_column=None, descriptor=None, comparator_factory=None):
260        self.name = name
261        self.map_column = map_column
262        self.descriptor = descriptor
263        self.comparator_factory = comparator_factory
264        util.set_creation_order(self)
265
266    def setup(self, context, entity, path, adapter, **kwargs):
267        pass
268
269    def create_row_processor(self, selectcontext, path, mapper, row, adapter):
270        return (None, None)
271
272    def instrument_class(self, mapper):
273        class_ = self.parent.class_
274
275        if self.descriptor is None:
276            class SynonymProp(object):
277                def __set__(s, obj, value):
278                    setattr(obj, self.name, value)
279                def __delete__(s, obj):
280                    delattr(obj, self.name)
281                def __get__(s, obj, owner):
282                    if obj is None:
283                        return s
284                    return getattr(obj, self.name)
285
286            self.descriptor = SynonymProp()
287
288        def comparator_callable(prop, mapper):
289            def comparator():
290                prop = self.parent._get_property(self.key, resolve_synonyms=True)
291                if self.comparator_factory:
292                    return self.comparator_factory(prop, mapper)
293                else:
294                    return prop.comparator_factory(prop, mapper)
295            return comparator
296
297        attributes.register_descriptor(
298            mapper.class_,
299            self.key,
300            comparator=comparator_callable(self, mapper),
301            parententity=mapper,
302            property_=self,
303            proxy_property=self.descriptor
304            )
305
306    def merge(self, session, source, dest, dont_load, _recursive):
307        pass
308       
309log.class_logger(SynonymProperty)
310
311class ComparableProperty(MapperProperty):
312    """Instruments a Python property for use in query expressions."""
313
314    extension = None
315   
316    def __init__(self, comparator_factory, descriptor=None):
317        self.descriptor = descriptor
318        self.comparator_factory = comparator_factory
319        util.set_creation_order(self)
320
321    def instrument_class(self, mapper):
322        """Set up a proxy to the unmanaged descriptor."""
323
324        attributes.register_descriptor(
325            mapper.class_,
326            self.key,
327            comparator=self.comparator_factory(self, mapper),
328            parententity=mapper,
329            property_=self,
330            proxy_property=self.descriptor
331            )
332
333    def setup(self, context, entity, path, adapter, **kwargs):
334        pass
335
336    def create_row_processor(self, selectcontext, path, mapper, row, adapter):
337        return (None, None)
338
339    def merge(self, session, source, dest, dont_load, _recursive):
340        pass
341
342
343class RelationProperty(StrategizedProperty):
344    """Describes an object property that holds a single item or list
345    of items that correspond to a related database table.
346    """
347
348    def __init__(self, argument,
349        secondary=None, primaryjoin=None,
350        secondaryjoin=None,
351        foreign_keys=None,
352        uselist=None,
353        order_by=False,
354        backref=None,
355        back_populates=None,
356        post_update=False,
357        cascade=False, extension=None,
358        viewonly=False, lazy=True,
359        collection_class=None, passive_deletes=False,
360        passive_updates=True, remote_side=None,
361        enable_typechecks=True, join_depth=None,
362        comparator_factory=None,
363        single_parent=False,
364        strategy_class=None, _local_remote_pairs=None, query_class=None):
365
366        self.uselist = uselist
367        self.argument = argument
368        self.secondary = secondary
369        self.primaryjoin = primaryjoin
370        self.secondaryjoin = secondaryjoin
371        self.post_update = post_update
372        self.direction = None
373        self.viewonly = viewonly
374        self.lazy = lazy
375        self.single_parent = single_parent
376        self._foreign_keys = foreign_keys
377        self.collection_class = collection_class
378        self.passive_deletes = passive_deletes
379        self.passive_updates = passive_updates
380        self.remote_side = remote_side
381        self.enable_typechecks = enable_typechecks
382        self.query_class = query_class
383
384        self.join_depth = join_depth
385        self.local_remote_pairs = _local_remote_pairs
386        self.extension = extension
387        self.comparator_factory = comparator_factory or RelationProperty.Comparator
388        self.comparator = self.comparator_factory(self, None)
389        util.set_creation_order(self)
390
391        if strategy_class:
392            self.strategy_class = strategy_class
393        elif self.lazy == 'dynamic':
394            from sqlalchemy.orm import dynamic
395            self.strategy_class = dynamic.DynaLoader
396        elif self.lazy is False:
397            self.strategy_class = strategies.EagerLoader
398        elif self.lazy is None:
399            self.strategy_class = strategies.NoLoader
400        else:
401            self.strategy_class = strategies.LazyLoader
402
403        self._reverse_property = set()
404
405        if cascade is not False:
406            self.cascade = CascadeOptions(cascade)
407        else:
408            self.cascade = CascadeOptions("save-update, merge")
409
410        if self.passive_deletes == 'all' and ("delete" in self.cascade or "delete-orphan" in self.cascade):
411            raise sa_exc.ArgumentError("Can't set passive_deletes='all' in conjunction with 'delete' or 'delete-orphan' cascade")
412
413        self.order_by = order_by
414
415        self.back_populates = back_populates
416
417        if self.back_populates:
418            if backref:
419                raise sa_exc.ArgumentError("backref and back_populates keyword arguments are mutually exclusive")
420            self.backref = None
421        elif isinstance(backref, basestring):
422            # propagate explicitly sent primary/secondary join conditions to the BackRef object if
423            # just a string was sent
424            if secondary is not None:
425                # reverse primary/secondary in case of a many-to-many
426                self.backref = BackRef(backref, primaryjoin=secondaryjoin,
427                                    secondaryjoin=primaryjoin, passive_updates=self.passive_updates)
428            else:
429                self.backref = BackRef(backref, primaryjoin=primaryjoin,
430                                    secondaryjoin=secondaryjoin, passive_updates=self.passive_updates)
431        else:
432            self.backref = backref
433
434    def instrument_class(self, mapper):
435        attributes.register_descriptor(
436            mapper.class_,
437            self.key,
438            comparator=self.comparator_factory(self, mapper),
439            parententity=mapper,
440            property_=self
441            )
442
443    class Comparator(PropComparator):
444        def __init__(self, prop, mapper, of_type=None, adapter=None):
445            self.prop = prop
446            self.mapper = mapper
447            self.adapter = adapter
448            if of_type:
449                self._of_type = _class_to_mapper(of_type)
450
451        def adapted(self, adapter):
452            """Return a copy of this PropComparator which will use the given adaption function
453            on the local side of generated expressions.
454
455            """
456            return self.__class__(self.property, self.mapper, getattr(self, '_of_type', None), adapter)
457       
458        @property
459        def parententity(self):
460            return self.property.parent
461
462        def __clause_element__(self):
463            elem = self.property.parent._with_polymorphic_selectable
464            if self.adapter:
465                return self.adapter(elem)
466            else:
467                return elem
468
469        def operate(self, op, *other, **kwargs):
470            return op(self, *other, **kwargs)
471
472        def reverse_operate(self, op, other, **kwargs):
473            return op(self, *other, **kwargs)
474
475        def of_type(self, cls):
476            return RelationProperty.Comparator(self.property, self.mapper, cls, adapter=self.adapter)
477
478        def in_(self, other):
479            raise NotImplementedError("in_() not yet supported for relations.  For a "
480                    "simple many-to-one, use in_() against the set of foreign key values.")
481           
482        __hash__ = None
483       
484        def __eq__(self, other):
485            if other is None:
486                if self.property.direction in [ONETOMANY, MANYTOMANY]:
487                    return ~self._criterion_exists()
488                else:
489                    return _orm_annotate(self.property._optimized_compare(None, adapt_source=self.adapter))
490            elif self.property.uselist:
491                raise sa_exc.InvalidRequestError("Can't compare a collection to an object or collection; use contains() to test for membership.")
492            else:
493                return _orm_annotate(self.property._optimized_compare(other, adapt_source=self.adapter))
494
495        def _criterion_exists(self, criterion=None, **kwargs):
496            if getattr(self, '_of_type', None):
497                target_mapper = self._of_type
498                to_selectable = target_mapper._with_polymorphic_selectable
499                if self.property._is_self_referential():
500                    to_selectable = to_selectable.alias()
501
502                single_crit = target_mapper._single_table_criterion
503                if single_crit:
504                    if criterion is not None:
505                        criterion = single_crit & criterion
506                    else:
507                        criterion = single_crit
508            else:
509                to_selectable = None
510
511            if self.adapter:
512                source_selectable = self.__clause_element__()
513            else:
514                source_selectable = None
515           
516            pj, sj, source, dest, secondary, target_adapter = \
517                self.property._create_joins(dest_polymorphic=True, dest_selectable=to_selectable, source_selectable=source_selectable)
518
519            for k in kwargs:
520                crit = self.property.mapper.class_manager[k] == kwargs[k]
521                if criterion is None:
522                    criterion = crit
523                else:
524                    criterion = criterion & crit
525           
526            # annotate the *local* side of the join condition, in the case of pj + sj this
527            # is the full primaryjoin, in the case of just pj its the local side of
528            # the primaryjoin. 
529            if sj:
530                j = _orm_annotate(pj) & sj
531            else:
532                j = _orm_annotate(pj, exclude=self.property.remote_side)
533           
534            if criterion and target_adapter:
535                # limit this adapter to annotated only?
536                criterion = target_adapter.traverse(criterion)
537
538            # only have the "joined left side" of what we return be subject to Query adaption.  The right
539            # side of it is used for an exists() subquery and should not correlate or otherwise reach out
540            # to anything in the enclosing query.
541            if criterion:
542                criterion = criterion._annotate({'_halt_adapt': True})
543           
544            crit = j & criterion
545           
546            return sql.exists([1], crit, from_obj=dest).correlate(source)
547
548        def any(self, criterion=None, **kwargs):
549            if not self.property.uselist:
550                raise sa_exc.InvalidRequestError("'any()' not implemented for scalar attributes. Use has().")
551
552            return self._criterion_exists(criterion, **kwargs)
553
554        def has(self, criterion=None, **kwargs):
555            if self.property.uselist:
556                raise sa_exc.InvalidRequestError("'has()' not implemented for collections.  Use any().")
557            return self._criterion_exists(criterion, **kwargs)
558
559        def contains(self, other, **kwargs):
560            if not self.property.uselist:
561                raise sa_exc.InvalidRequestError("'contains' not implemented for scalar attributes.  Use ==")
562            clause = self.property._optimized_compare(other, adapt_source=self.adapter)
563
564            if self.property.secondaryjoin:
565                clause.negation_clause = self.__negated_contains_or_equals(other)
566
567            return clause
568
569        def __negated_contains_or_equals(self, other):
570            if self.property.direction == MANYTOONE:
571                state = attributes.instance_state(other)
572                strategy = self.property._get_strategy(strategies.LazyLoader)
573               
574                def state_bindparam(state, col):
575                    o = state.obj() # strong ref
576                    return lambda: self.property.mapper._get_committed_attr_by_column(o, col)
577               
578                def adapt(col):
579                    if self.adapter:
580                        return self.adapter(col)
581                    else:
582                        return col
583                       
584                if strategy.use_get:
585                    return sql.and_(*[
586                        sql.or_(
587                        adapt(x) != state_bindparam(state, y),
588                        adapt(x) == None)
589                        for (x, y) in self.property.local_remote_pairs])
590                   
591            criterion = sql.and_(*[x==y for (x, y) in zip(self.property.mapper.primary_key, self.property.mapper.primary_key_from_instance(other))])
592            return ~self._criterion_exists(criterion)
593
594        def __ne__(self, other):
595            if other is None:
596                if self.property.direction == MANYTOONE:
597                    return sql.or_(*[x!=None for x in self.property._foreign_keys])
598                else:
599                    return self._criterion_exists()
600            elif self.property.uselist:
601                raise sa_exc.InvalidRequestError("Can't compare a collection to an object or collection; use contains() to test for membership.")
602            else:
603                return self.__negated_contains_or_equals(other)
604
605        @util.memoized_property
606        def property(self):
607            self.prop.parent.compile()
608            return self.prop
609
610    def compare(self, op, value, value_is_parent=False):
611        if op == operators.eq:
612            if value is None:
613                if self.uselist:
614                    return ~sql.exists([1], self.primaryjoin)
615                else:
616                    return self._optimized_compare(None, value_is_parent=value_is_parent)
617            else:
618                return self._optimized_compare(value, value_is_parent=value_is_parent)
619        else:
620            return op(self.comparator, value)
621
622    def _optimized_compare(self, value, value_is_parent=False, adapt_source=None):
623        if value is not None:
624            value = attributes.instance_state(value)
625        return self._get_strategy(strategies.LazyLoader).\
626                lazy_clause(value, reverse_direction=not value_is_parent, alias_secondary=True, adapt_source=adapt_source)
627
628    def __str__(self):
629        return str(self.parent.class_.__name__) + "." + self.key
630
631    def merge(self, session, source, dest, dont_load, _recursive):
632        if not dont_load:
633            # TODO: no test coverage for recursive check
634            for r in self._reverse_property:
635                if (source, r) in _recursive:
636                    return
637
638        source_state = attributes.instance_state(source)
639        dest_state, dest_dict = attributes.instance_state(dest), attributes.instance_dict(dest)
640
641        if not "merge" in self.cascade:
642            dest_state.expire_attributes([self.key])
643            return
644
645        instances = source_state.value_as_iterable(self.key, passive=True)
646
647        if not instances:
648            return
649
650        if self.uselist:
651            dest_list = []
652            for current in instances:
653                _recursive[(current, self)] = True
654                obj = session._merge(current, dont_load=dont_load, _recursive=_recursive)
655                if obj is not None:
656                    dest_list.append(obj)
657            if dont_load:
658                coll = attributes.init_collection(dest_state, self.key)
659                for c in dest_list:
660                    coll.append_without_event(c)
661            else:
662                getattr(dest.__class__, self.key).impl._set_iterable(dest_state, dest_dict, dest_list)
663        else:
664            current = instances[0]
665            if current is not None:
666                _recursive[(current, self)] = True
667                obj = session._merge(current, dont_load=dont_load, _recursive=_recursive)
668                if obj is not None:
669                    if dont_load:
670                        dest_state.dict[self.key] = obj
671                    else:
672                        setattr(dest, self.key, obj)
673
674    def cascade_iterator(self, type_, state, visited_instances, halt_on=None):
675        if not type_ in self.cascade:
676            return
677
678        # only actively lazy load on the 'delete' cascade
679        if type_ != 'delete' or self.passive_deletes:
680            passive = attributes.PASSIVE_NO_INITIALIZE
681        else:
682            passive = attributes.PASSIVE_OFF
683
684        mapper = self.mapper.primary_mapper()
685        instances = state.value_as_iterable(self.key, passive=passive)
686        if instances:
687            for c in instances:
688                if c is not None and c not in visited_instances and (halt_on is None or not halt_on(c)):
689                    if not isinstance(c, self.mapper.class_):
690                        raise AssertionError("Attribute '%s' on class '%s' doesn't handle objects "
691                                    "of type '%s'" % (self.key, str(self.parent.class_), str(c.__class__)))
692                    visited_instances.add(c)
693
694                    # cascade using the mapper local to this object, so that its individual properties are located
695                    instance_mapper = object_mapper(c)
696                    yield (c, instance_mapper, attributes.instance_state(c))
697
698    def _add_reverse_property(self, key):
699        other = self.mapper._get_property(key)
700        self._reverse_property.add(other)
701        other._reverse_property.add(self)
702       
703        if not other._get_target().common_parent(self.parent):
704            raise sa_exc.ArgumentError("reverse_property %r on relation %s references "
705                    "relation %s, which does not reference mapper %s" % (key, self, other, self.parent))
706       
707        if self.direction in (ONETOMANY, MANYTOONE) and self.direction == other.direction:
708            raise sa_exc.ArgumentError("%s and back-reference %s are both of the same direction %r."
709                "  Did you mean to set remote_side on the many-to-one side ?" % (self, other, self.direction))
710       
711    def do_init(self):
712        self._get_target()
713        self._process_dependent_arguments()
714        self._determine_joins()
715        self._determine_synchronize_pairs()
716        self._determine_direction()
717        self._determine_local_remote_pairs()
718        self._post_init()
719        super(RelationProperty, self).do_init()
720
721    def _get_target(self):
722        if not hasattr(self, 'mapper'):
723            if isinstance(self.argument, type):
724                self.mapper = mapper.class_mapper(self.argument, compile=False)
725            elif isinstance(self.argument, mapper.Mapper):
726                self.mapper = self.argument
727            elif util.callable(self.argument):
728                # accept a callable to suit various deferred-configurational schemes
729                self.mapper = mapper.class_mapper(self.argument(), compile=False)
730            else:
731                raise sa_exc.ArgumentError("relation '%s' expects a class or a mapper argument (received: %s)" % (self.key, type(self.argument)))
732            assert isinstance(self.mapper, mapper.Mapper), self.mapper
733        return self.mapper
734       
735    def _process_dependent_arguments(self):
736
737        # accept callables for other attributes which may require deferred initialization
738        for attr in ('order_by', 'primaryjoin', 'secondaryjoin', 'secondary', '_foreign_keys', 'remote_side'):
739            if util.callable(getattr(self, attr)):
740                setattr(self, attr, getattr(self, attr)())
741
742        # in the case that InstrumentedAttributes were used to construct
743        # primaryjoin or secondaryjoin, remove the "_orm_adapt" annotation so these
744        # interact with Query in the same way as the original Table-bound Column objects
745        for attr in ('primaryjoin', 'secondaryjoin'):
746            val = getattr(self, attr)
747            if val is not None:
748                util.assert_arg_type(val, sql.ClauseElement, attr)
749                setattr(self, attr, _orm_deannotate(val))
750       
751        if self.order_by:
752            self.order_by = [expression._literal_as_column(x) for x in util.to_list(self.order_by)]
753       
754        self._foreign_keys = util.column_set(expression._literal_as_column(x) for x in util.to_column_set(self._foreign_keys))
755        self.remote_side = util.column_set(expression._literal_as_column(x) for x in util.to_column_set(self.remote_side))
756
757        if not self.parent.concrete:
758            for inheriting in self.parent.iterate_to_root():
759                if inheriting is not self.parent and inheriting._get_property(self.key, raiseerr=False):
760                    util.warn(
761                        ("Warning: relation '%s' on mapper '%s' supercedes "
762                         "the same relation on inherited mapper '%s'; this "
763                         "can cause dependency issues during flush") %
764                        (self.key, self.parent, inheriting))
765
766        # TODO: remove 'self.table'
767        self.target = self.table = self.mapper.mapped_table
768
769        if self.cascade.delete_orphan:
770            if self.parent.class_ is self.mapper.class_:
771                raise sa_exc.ArgumentError("In relationship '%s', can't establish 'delete-orphan' cascade "
772                            "rule on a self-referential relationship.  "
773                            "You probably want cascade='all', which includes delete cascading but not orphan detection." %(str(self)))
774            self.mapper.primary_mapper().delete_orphans.append((self.key, self.parent.class_))
775
776    def _determine_joins(self):
777        if self.secondaryjoin is not None and self.secondary is None:
778            raise sa_exc.ArgumentError("Property '" + self.key + "' specified with secondary join condition but no secondary argument")
779        # if join conditions were not specified, figure them out based on foreign keys
780
781        def _search_for_join(mapper, table):
782            # find a join between the given mapper's mapped table and the given table.
783            # will try the mapper's local table first for more specificity, then if not
784            # found will try the more general mapped table, which in the case of inheritance
785            # is a join.
786            try:
787                return join_condition(mapper.local_table, table)
788            except sa_exc.ArgumentError, e:
789                return join_condition(mapper.mapped_table, table)
790
791        try:
792            if self.secondary is not None:
793                if self.secondaryjoin is None:
794                    self.secondaryjoin = _search_for_join(self.mapper, self.secondary)
795                if self.primaryjoin is None:
796                    self.primaryjoin = _search_for_join(self.parent, self.secondary)
797            else:
798                if self.primaryjoin is None:
799                    self.primaryjoin = _search_for_join(self.parent, self.target)
800        except sa_exc.ArgumentError, e:
801            raise sa_exc.ArgumentError("Could not determine join condition between "
802                        "parent/child tables on relation %s.  "
803                        "Specify a 'primaryjoin' expression.  If this is a "
804                        "many-to-many relation, 'secondaryjoin' is needed as well." % (self))
805
806    def _col_is_part_of_mappings(self, column):
807        if self.secondary is None:
808            return self.parent.mapped_table.c.contains_column(column) or \
809                self.target.c.contains_column(column)
810        else:
811            return self.parent.mapped_table.c.contains_column(column) or \
812                self.target.c.contains_column(column) or \
813                self.secondary.c.contains_column(column) is not None
814
815    def _determine_synchronize_pairs(self):
816
817        if self.local_remote_pairs:
818            if not self._foreign_keys:
819                raise sa_exc.ArgumentError("foreign_keys argument is required with _local_remote_pairs argument")
820
821            self.synchronize_pairs = []
822
823            for l, r in self.local_remote_pairs:
824                if r in self._foreign_keys:
825                    self.synchronize_pairs.append((l, r))
826                elif l in self._foreign_keys:
827                    self.synchronize_pairs.append((r, l))
828        else:
829            eq_pairs = criterion_as_pairs(
830                            self.primaryjoin,
831                            consider_as_foreign_keys=self._foreign_keys,
832                            any_operator=self.viewonly
833                        )
834            eq_pairs = [
835                            (l, r) for l, r in eq_pairs if
836                            (self._col_is_part_of_mappings(l) and
837                                self._col_is_part_of_mappings(r))
838                                or self.viewonly and r in self._foreign_keys
839                        ]
840
841            if not eq_pairs:
842                if not self.viewonly and criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=self._foreign_keys, any_operator=True):
843                    raise sa_exc.ArgumentError("Could not locate any equated, locally "
844                        "mapped column pairs for primaryjoin condition '%s' on relation %s. "
845                        "For more relaxed rules on join conditions, the relation may be "
846                        "marked as viewonly=True." % (self.primaryjoin, self)
847                    )
848                else:
849                    if self._foreign_keys:
850                        raise sa_exc.ArgumentError("Could not determine relation direction for "
851                            "primaryjoin condition '%s', on relation %s. "
852                            "Do the columns in 'foreign_keys' represent only the 'foreign' columns "
853                            "in this join condition ?" % (self.primaryjoin, self))
854                    else:
855                        raise sa_exc.ArgumentError("Could not determine relation direction for "
856                            "primaryjoin condition '%s', on relation %s. "
857                            "Specify the 'foreign_keys' argument to indicate which columns "
858                            "on the relation are foreign." % (self.primaryjoin, self))
859
860            self.synchronize_pairs = eq_pairs
861
862        if self.secondaryjoin:
863            sq_pairs = criterion_as_pairs(self.secondaryjoin, consider_as_foreign_keys=self._foreign_keys, any_operator=self.viewonly)
864            sq_pairs = [(l, r) for l, r in sq_pairs if (self._col_is_part_of_mappings(l) and self._col_is_part_of_mappings(r)) or r in self._foreign_keys]
865
866            if not sq_pairs:
867                if not self.viewonly and criterion_as_pairs(self.secondaryjoin, consider_as_foreign_keys=self._foreign_keys, any_operator=True):
868                    raise sa_exc.ArgumentError("Could not locate any equated, locally mapped "
869                        "column pairs for secondaryjoin condition '%s' on relation %s. "
870                        "For more relaxed rules on join conditions, the "
871                        "relation may be marked as viewonly=True." % (self.secondaryjoin, self)
872                    )
873                else:
874                    raise sa_exc.ArgumentError("Could not determine relation direction "
875                    "for secondaryjoin condition '%s', on relation %s. "
876                    "Specify the foreign_keys argument to indicate which "
877                    "columns on the relation are foreign." % (self.secondaryjoin, self))
878
879            self.secondary_synchronize_pairs = sq_pairs
880        else:
881            self.secondary_synchronize_pairs = None
882
883        self._foreign_keys = util.column_set(r for l, r in self.synchronize_pairs)
884        if self.secondary_synchronize_pairs:
885            self._foreign_keys.update(r for l, r in self.secondary_synchronize_pairs)
886
887    def _determine_direction(self):
888        if self.secondaryjoin is not None:
889            self.direction = MANYTOMANY
890        elif self._refers_to_parent_table():
891            # self referential defaults to ONETOMANY unless the "remote" side is present
892            # and does not reference any foreign key columns
893
894            if self.local_remote_pairs:
895                remote = [r for l, r in self.local_remote_pairs]
896            elif self.remote_side:
897                remote = self.remote_side
898            else:
899                remote = None
900
901            if not remote or self._foreign_keys.\
902                                    difference(l for l, r in self.synchronize_pairs).\
903                                    intersection(remote):
904                self.direction = ONETOMANY
905            else:
906                self.direction = MANYTOONE
907
908        else:
909            foreign_keys = [f for c, f in self.synchronize_pairs]
910
911            parentcols = util.column_set(self.parent.mapped_table.c)
912            targetcols = util.column_set(self.mapper.mapped_table.c)
913
914            # fk collection which suggests ONETOMANY.
915            onetomany_fk = targetcols.intersection(foreign_keys)
916
917            # fk collection which suggests MANYTOONE.
918            manytoone_fk = parentcols.intersection(foreign_keys)
919           
920            if not onetomany_fk and not manytoone_fk:
921                raise sa_exc.ArgumentError(
922                    "Can't determine relation direction for relationship '%s' "
923                    "- foreign key columns are present in neither the "
924                    "parent nor the child's mapped tables" % self )
925
926            elif onetomany_fk and manytoone_fk:
927                # fks on both sides.  do the same
928                # test only based on the local side.
929                referents = [c for c, f in self.synchronize_pairs]
930                onetomany_local = parentcols.intersection(referents)
931                manytoone_local = targetcols.intersection(referents)
932
933                if onetomany_local and not manytoone_local:
934                    self.direction = ONETOMANY
935                elif manytoone_local and not onetomany_local:
936                    self.direction = MANYTOONE
937            elif onetomany_fk:
938                self.direction = ONETOMANY
939            elif manytoone_fk:
940                self.direction = MANYTOONE
941               
942            if not self.direction:
943                raise sa_exc.ArgumentError(
944                    "Can't determine relation direction for relationship '%s' "
945                    "- foreign key columns are present in both the parent and "
946                    "the child's mapped tables.  Specify 'foreign_keys' "
947                    "argument." % self)
948       
949        if self.cascade.delete_orphan and not self.single_parent and \
950            (self.direction is MANYTOMANY or self.direction is MANYTOONE):
951            util.warn("On %s, delete-orphan cascade is not supported on a "
952                    "many-to-many or many-to-one relationship when single_parent is not set.  "
953                    " Set single_parent=True on the relation()." % self)
954       
955    def _determine_local_remote_pairs(self):
956        if not self.local_remote_pairs:
957            if self.remote_side:
958                if self.direction is MANYTOONE:
959                    self.local_remote_pairs = [
960                        (r, l) for l, r in
961                        criterion_as_pairs(self.primaryjoin, consider_as_referenced_keys=self.remote_side, any_operator=True)
962                    ]
963                else:
964                    self.local_remote_pairs = criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=self.remote_side, any_operator=True)
965
966                if not self.local_remote_pairs:
967                    raise sa_exc.ArgumentError("Relation %s could not determine any local/remote column pairs from remote side argument %r" % (self, self.remote_side))
968
969            else:
970                if self.viewonly:
971                    eq_pairs = self.synchronize_pairs
972                    if self.secondaryjoin:
973                        eq_pairs += self.secondary_synchronize_pairs
974                else:
975                    eq_pairs = criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=self._foreign_keys, any_operator=True)
976                    if self.secondaryjoin:
977                        eq_pairs += criterion_as_pairs(self.secondaryjoin, consider_as_foreign_keys=self._foreign_keys, any_operator=True)
978                    eq_pairs = [(l, r) for l, r in eq_pairs if self._col_is_part_of_mappings(l) and self._col_is_part_of_mappings(r)]
979
980                if self.direction is MANYTOONE:
981                    self.local_remote_pairs = [(r, l) for l, r in eq_pairs]
982                else:
983                    self.local_remote_pairs = eq_pairs
984        elif self.remote_side:
985            raise sa_exc.ArgumentError("remote_side argument is redundant against more detailed _local_remote_side argument.")
986       
987        for l, r in self.local_remote_pairs:
988
989            if self.direction is ONETOMANY and not self._col_is_part_of_mappings(l):
990                raise sa_exc.ArgumentError("Local column '%s' is not part of mapping %s.  "
991                        "Specify remote_side argument to indicate which column "
992                        "lazy join condition should compare against." % (l, self.parent))
993
994            elif self.direction is MANYTOONE and not self._col_is_part_of_mappings(r):
995                raise sa_exc.ArgumentError("Remote column '%s' is not part of mapping %s. "
996                        "Specify remote_side argument to indicate which column lazy "
997                        "join condition should bind." % (r, self.mapper))
998
999        self.local_side, self.remote_side = [util.ordered_column_set(x) for x in zip(*list(self.local_remote_pairs))]
1000
1001
1002    def _post_init(self):
1003        if self._should_log_info:
1004            self.logger.info(str(self) + " setup primary join %s" % self.primaryjoin)
1005            self.logger.info(str(self) + " setup secondary join %s" % self.secondaryjoin)
1006            self.logger.info(str(self) + " synchronize pairs [%s]" % ",".join("(%s => %s)" % (l, r) for l, r in self.synchronize_pairs))
1007            self.logger.info(str(self) + " secondary synchronize pairs [%s]" % ",".join(("(%s => %s)" % (l, r) for l, r in self.secondary_synchronize_pairs or [])))
1008            self.logger.info(str(self) + " local/remote pairs [%s]" % ",".join("(%s / %s)" % (l, r) for l, r in self.local_remote_pairs))
1009            self.logger.info(str(self) + " relation direction %s" % self.direction)
1010
1011        if self.uselist is None and self.direction is MANYTOONE:
1012            self.uselist = False
1013
1014        if self.uselist is None:
1015            self.uselist = True
1016
1017        if not self.viewonly:
1018            self._dependency_processor = dependency.create_dependency_processor(self)
1019
1020        # primary property handler, set up class attributes
1021        if self.is_primary():
1022            if self.back_populates:
1023                self.extension = list(util.to_list(self.extension, default=[]))
1024                self.extension.append(attributes.GenericBackrefExtension(self.back_populates))
1025                self._add_reverse_property(self.back_populates)
1026           
1027            if self.backref is not None:
1028                self.backref.compile(self)
1029        elif not mapper.class_mapper(self.parent.class_, compile=False)._get_property(self.key, raiseerr=False):
1030            raise sa_exc.ArgumentError("Attempting to assign a new relation '%s' to "
1031                "a non-primary mapper on class '%s'.  New relations can only be "
1032                "added to the primary mapper, i.e. the very first "
1033                "mapper created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__))
1034       
1035
1036    def _refers_to_parent_table(self):
1037        for c, f in self.synchronize_pairs:
1038            if c.table is f.table:
1039                return True
1040        else:
1041            return False
1042
1043    def _is_self_referential(self):
1044        return self.mapper.common_parent(self.parent)
1045
1046    def _create_joins(self, source_polymorphic=False, source_selectable=None, dest_polymorphic=False, dest_selectable=None, of_type=None):
1047        if source_selectable is None:
1048            if source_polymorphic and self.parent.with_polymorphic:
1049                source_selectable = self.parent._with_polymorphic_selectable
1050
1051        aliased = False
1052        if dest_selectable is None:
1053            if dest_polymorphic and self.mapper.with_polymorphic:
1054                dest_selectable = self.mapper._with_polymorphic_selectable
1055                aliased = True
1056            else:
1057                dest_selectable = self.mapper.mapped_table
1058
1059            if self._is_self_referential() and source_selectable is None:
1060                dest_selectable = dest_selectable.alias()
1061                aliased = True
1062        else:
1063            aliased = True
1064
1065        aliased = aliased or bool(source_selectable)
1066
1067        primaryjoin, secondaryjoin, secondary = self.primaryjoin, self.secondaryjoin, self.secondary
1068       
1069        # adjust the join condition for single table inheritance,
1070        # in the case that the join is to a subclass
1071        # this is analgous to the "_adjust_for_single_table_inheritance()"
1072        # method in Query.
1073
1074        dest_mapper = of_type or self.mapper
1075       
1076        single_crit = dest_mapper._single_table_criterion
1077        if single_crit:
1078            if secondaryjoin:
1079                secondaryjoin = secondaryjoin & single_crit
1080            else:
1081                primaryjoin = primaryjoin & single_crit
1082           
1083
1084        if aliased:
1085            if secondary:
1086                secondary = secondary.alias()
1087                primary_aliasizer = ClauseAdapter(secondary)
1088                if dest_selectable:
1089                    secondary_aliasizer = ClauseAdapter(dest_selectable, equivalents=self.mapper._equivalent_columns).chain(primary_aliasizer)
1090                else:
1091                    secondary_aliasizer = primary_aliasizer
1092
1093                if source_selectable:
1094                    primary_aliasizer = ClauseAdapter(secondary).chain(ClauseAdapter(source_selectable, equivalents=self.parent._equivalent_columns))
1095
1096                secondaryjoin = secondary_aliasizer.traverse(secondaryjoin)
1097            else:
1098                if dest_selectable:
1099                    primary_aliasizer = ClauseAdapter(dest_selectable, exclude=self.local_side, equivalents=self.mapper._equivalent_columns)
1100                    if source_selectable:
1101                        primary_aliasizer.chain(ClauseAdapter(source_selectable, exclude=self.remote_side, equivalents=self.parent._equivalent_columns))
1102                elif source_selectable:
1103                    primary_aliasizer = ClauseAdapter(source_selectable, exclude=self.remote_side, equivalents=self.parent._equivalent_columns)
1104
1105                secondary_aliasizer = None
1106
1107            primaryjoin = primary_aliasizer.traverse(primaryjoin)
1108            target_adapter = secondary_aliasizer or primary_aliasizer
1109            target_adapter.include = target_adapter.exclude = None
1110        else:
1111            target_adapter = None
1112
1113        return (primaryjoin, secondaryjoin,
1114                (source_selectable or self.parent.local_table),
1115                (dest_selectable or self.mapper.local_table), secondary, target_adapter)
1116
1117    def _get_join(self, parent, primary=True, secondary=True, polymorphic_parent=True):
1118        """deprecated.  use primary_join_against(), secondary_join_against(), full_join_against()"""
1119
1120        pj, sj, source, dest, secondarytable, adapter = self._create_joins(source_polymorphic=polymorphic_parent)
1121
1122        if primary and secondary:
1123            return pj & sj
1124        elif primary:
1125            return pj
1126        elif secondary:
1127            return sj
1128        else:
1129            raise AssertionError("illegal condition")
1130
1131    def register_dependencies(self, uowcommit):
1132        if not self.viewonly:
1133            self._dependency_processor.register_dependencies(uowcommit)
1134
1135    def register_processors(self, uowcommit):
1136        if not self.viewonly:
1137            self._dependency_processor.register_processors(uowcommit)
1138
1139PropertyLoader = RelationProperty
1140log.class_logger(RelationProperty)
1141
1142class BackRef(object):
1143    """Attached to a RelationProperty to indicate a complementary reverse relationship.
1144
1145    Handles the job of creating the opposite RelationProperty according to configuration.
1146   
1147    Alternatively, two explicit RelationProperty objects can be associated bidirectionally
1148    using the back_populates keyword argument on each.
1149   
1150    """
1151
1152    def __init__(self, key, _prop=None, **kwargs):
1153        self.key = key
1154        self.kwargs = kwargs
1155        self.prop = _prop
1156        self.extension = attributes.GenericBackrefExtension(self.key)
1157
1158    def compile(self, prop):
1159        if self.prop:
1160            return
1161
1162        self.prop = prop
1163
1164        mapper = prop.mapper.primary_mapper()
1165        if mapper._get_property(self.key, raiseerr=False) is None:
1166            if prop.secondary:
1167                pj = self.kwargs.pop('primaryjoin', prop.secondaryjoin)
1168                sj = self.kwargs.pop('secondaryjoin', prop.primaryjoin)
1169            else:
1170                pj = self.kwargs.pop('primaryjoin', prop.primaryjoin)
1171                sj = self.kwargs.pop('secondaryjoin', None)
1172                if sj:
1173                    raise sa_exc.InvalidRequestError(
1174                        "Can't assign 'secondaryjoin' on a backref against "
1175                        "a non-secondary relation.")
1176           
1177            foreign_keys = self.kwargs.pop('foreign_keys', prop._foreign_keys)
1178           
1179            parent = prop.parent.primary_mapper()
1180            self.kwargs.setdefault('viewonly', prop.viewonly)
1181            self.kwargs.setdefault('post_update', prop.post_update)
1182
1183            relation = RelationProperty(parent, prop.secondary, pj, sj, foreign_keys=foreign_keys,
1184                                      backref=BackRef(prop.key, _prop=prop),
1185                                      **self.kwargs)
1186
1187            mapper._configure_property(self.key, relation);
1188
1189            prop._add_reverse_property(self.key)
1190
1191        else:
1192            raise sa_exc.ArgumentError("Error creating backref '%s' on relation '%s': "
1193                "property of that name exists on mapper '%s'" % (self.key, prop, mapper))
1194
1195mapper.ColumnProperty = ColumnProperty
1196mapper.SynonymProperty = SynonymProperty
1197mapper.ComparableProperty = ComparableProperty
1198mapper.RelationProperty = RelationProperty
1199mapper.ConcreteInheritedProperty = ConcreteInheritedProperty
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。