Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/ext/associationproxy.py : 30%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# ext/associationproxy.py
2# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
8"""Contain the ``AssociationProxy`` class.
10The ``AssociationProxy`` is a Python property object which provides
11transparent proxied access to the endpoint of an association object.
13See the example ``examples/association/proxied_association.py``.
15"""
16import operator
18from .. import exc
19from .. import inspect
20from .. import orm
21from .. import util
22from ..orm import collections
23from ..orm import interfaces
24from ..sql import or_
25from ..sql.operators import ColumnOperators
28def association_proxy(target_collection, attr, **kw):
29 r"""Return a Python property implementing a view of a target
30 attribute which references an attribute on members of the
31 target.
33 The returned value is an instance of :class:`.AssociationProxy`.
35 Implements a Python property representing a relationship as a collection
36 of simpler values, or a scalar value. The proxied property will mimic
37 the collection type of the target (list, dict or set), or, in the case of
38 a one to one relationship, a simple scalar value.
40 :param target_collection: Name of the attribute we'll proxy to.
41 This attribute is typically mapped by
42 :func:`~sqlalchemy.orm.relationship` to link to a target collection, but
43 can also be a many-to-one or non-scalar relationship.
45 :param attr: Attribute on the associated instance or instances we'll
46 proxy for.
48 For example, given a target collection of [obj1, obj2], a list created
49 by this proxy property would look like [getattr(obj1, *attr*),
50 getattr(obj2, *attr*)]
52 If the relationship is one-to-one or otherwise uselist=False, then
53 simply: getattr(obj, *attr*)
55 :param creator: optional.
57 When new items are added to this proxied collection, new instances of
58 the class collected by the target collection will be created. For list
59 and set collections, the target class constructor will be called with
60 the 'value' for the new instance. For dict types, two arguments are
61 passed: key and value.
63 If you want to construct instances differently, supply a *creator*
64 function that takes arguments as above and returns instances.
66 For scalar relationships, creator() will be called if the target is None.
67 If the target is present, set operations are proxied to setattr() on the
68 associated object.
70 If you have an associated object with multiple attributes, you may set
71 up multiple association proxies mapping to different attributes. See
72 the unit tests for examples, and for examples of how creator() functions
73 can be used to construct the scalar relationship on-demand in this
74 situation.
76 :param \*\*kw: Passes along any other keyword arguments to
77 :class:`.AssociationProxy`.
79 """
80 return AssociationProxy(target_collection, attr, **kw)
83ASSOCIATION_PROXY = util.symbol("ASSOCIATION_PROXY")
84"""Symbol indicating an :class:`.InspectionAttr` that's
85 of type :class:`.AssociationProxy`.
87 Is assigned to the :attr:`.InspectionAttr.extension_type`
88 attribute.
90"""
93class AssociationProxy(interfaces.InspectionAttrInfo):
94 """A descriptor that presents a read/write view of an object attribute."""
96 is_attribute = True
97 extension_type = ASSOCIATION_PROXY
99 def __init__(
100 self,
101 target_collection,
102 attr,
103 creator=None,
104 getset_factory=None,
105 proxy_factory=None,
106 proxy_bulk_set=None,
107 info=None,
108 cascade_scalar_deletes=False,
109 ):
110 """Construct a new :class:`.AssociationProxy`.
112 The :func:`.association_proxy` function is provided as the usual
113 entrypoint here, though :class:`.AssociationProxy` can be instantiated
114 and/or subclassed directly.
116 :param target_collection: Name of the collection we'll proxy to,
117 usually created with :func:`_orm.relationship`.
119 :param attr: Attribute on the collected instances we'll proxy
120 for. For example, given a target collection of [obj1, obj2], a
121 list created by this proxy property would look like
122 [getattr(obj1, attr), getattr(obj2, attr)]
124 :param creator: Optional. When new items are added to this proxied
125 collection, new instances of the class collected by the target
126 collection will be created. For list and set collections, the
127 target class constructor will be called with the 'value' for the
128 new instance. For dict types, two arguments are passed:
129 key and value.
131 If you want to construct instances differently, supply a 'creator'
132 function that takes arguments as above and returns instances.
134 :param cascade_scalar_deletes: when True, indicates that setting
135 the proxied value to ``None``, or deleting it via ``del``, should
136 also remove the source object. Only applies to scalar attributes.
137 Normally, removing the proxied target will not remove the proxy
138 source, as this object may have other state that is still to be
139 kept.
141 .. versionadded:: 1.3
143 .. seealso::
145 :ref:`cascade_scalar_deletes` - complete usage example
147 :param getset_factory: Optional. Proxied attribute access is
148 automatically handled by routines that get and set values based on
149 the `attr` argument for this proxy.
151 If you would like to customize this behavior, you may supply a
152 `getset_factory` callable that produces a tuple of `getter` and
153 `setter` functions. The factory is called with two arguments, the
154 abstract type of the underlying collection and this proxy instance.
156 :param proxy_factory: Optional. The type of collection to emulate is
157 determined by sniffing the target collection. If your collection
158 type can't be determined by duck typing or you'd like to use a
159 different collection implementation, you may supply a factory
160 function to produce those collections. Only applicable to
161 non-scalar relationships.
163 :param proxy_bulk_set: Optional, use with proxy_factory. See
164 the _set() method for details.
166 :param info: optional, will be assigned to
167 :attr:`.AssociationProxy.info` if present.
169 .. versionadded:: 1.0.9
171 """
172 self.target_collection = target_collection
173 self.value_attr = attr
174 self.creator = creator
175 self.getset_factory = getset_factory
176 self.proxy_factory = proxy_factory
177 self.proxy_bulk_set = proxy_bulk_set
178 self.cascade_scalar_deletes = cascade_scalar_deletes
180 self.key = "_%s_%s_%s" % (
181 type(self).__name__,
182 target_collection,
183 id(self),
184 )
185 if info:
186 self.info = info
188 def __get__(self, obj, class_):
189 if class_ is None:
190 return self
191 inst = self._as_instance(class_, obj)
192 if inst:
193 return inst.get(obj)
195 # obj has to be None here
196 # assert obj is None
198 return self
200 def __set__(self, obj, values):
201 class_ = type(obj)
202 return self._as_instance(class_, obj).set(obj, values)
204 def __delete__(self, obj):
205 class_ = type(obj)
206 return self._as_instance(class_, obj).delete(obj)
208 def for_class(self, class_, obj=None):
209 r"""Return the internal state local to a specific mapped class.
211 E.g., given a class ``User``::
213 class User(Base):
214 # ...
216 keywords = association_proxy('kws', 'keyword')
218 If we access this :class:`.AssociationProxy` from
219 :attr:`_orm.Mapper.all_orm_descriptors`, and we want to view the
220 target class for this proxy as mapped by ``User``::
222 inspect(User).all_orm_descriptors["keywords"].for_class(User).target_class
224 This returns an instance of :class:`.AssociationProxyInstance` that
225 is specific to the ``User`` class. The :class:`.AssociationProxy`
226 object remains agnostic of its parent class.
228 :param class\_: the class that we are returning state for.
230 :param obj: optional, an instance of the class that is required
231 if the attribute refers to a polymorphic target, e.g. where we have
232 to look at the type of the actual destination object to get the
233 complete path.
235 .. versionadded:: 1.3 - :class:`.AssociationProxy` no longer stores
236 any state specific to a particular parent class; the state is now
237 stored in per-class :class:`.AssociationProxyInstance` objects.
240 """
241 return self._as_instance(class_, obj)
243 def _as_instance(self, class_, obj):
244 try:
245 inst = class_.__dict__[self.key + "_inst"]
246 except KeyError:
247 inst = None
249 # avoid exception context
250 if inst is None:
251 owner = self._calc_owner(class_)
252 if owner is not None:
253 inst = AssociationProxyInstance.for_proxy(self, owner, obj)
254 setattr(class_, self.key + "_inst", inst)
255 else:
256 inst = None
258 if inst is not None and not inst._is_canonical:
259 # the AssociationProxyInstance can't be generalized
260 # since the proxied attribute is not on the targeted
261 # class, only on subclasses of it, which might be
262 # different. only return for the specific
263 # object's current value
264 return inst._non_canonical_get_for_object(obj)
265 else:
266 return inst
268 def _calc_owner(self, target_cls):
269 # we might be getting invoked for a subclass
270 # that is not mapped yet, in some declarative situations.
271 # save until we are mapped
272 try:
273 insp = inspect(target_cls)
274 except exc.NoInspectionAvailable:
275 # can't find a mapper, don't set owner. if we are a not-yet-mapped
276 # subclass, we can also scan through __mro__ to find a mapped
277 # class, but instead just wait for us to be called again against a
278 # mapped class normally.
279 return None
280 else:
281 return insp.mapper.class_manager.class_
283 def _default_getset(self, collection_class):
284 attr = self.value_attr
285 _getter = operator.attrgetter(attr)
287 def getter(target):
288 return _getter(target) if target is not None else None
290 if collection_class is dict:
292 def setter(o, k, v):
293 setattr(o, attr, v)
295 else:
297 def setter(o, v):
298 setattr(o, attr, v)
300 return getter, setter
302 def __repr__(self):
303 return "AssociationProxy(%r, %r)" % (
304 self.target_collection,
305 self.value_attr,
306 )
309class AssociationProxyInstance(object):
310 """A per-class object that serves class- and object-specific results.
312 This is used by :class:`.AssociationProxy` when it is invoked
313 in terms of a specific class or instance of a class, i.e. when it is
314 used as a regular Python descriptor.
316 When referring to the :class:`.AssociationProxy` as a normal Python
317 descriptor, the :class:`.AssociationProxyInstance` is the object that
318 actually serves the information. Under normal circumstances, its presence
319 is transparent::
321 >>> User.keywords.scalar
322 False
324 In the special case that the :class:`.AssociationProxy` object is being
325 accessed directly, in order to get an explicit handle to the
326 :class:`.AssociationProxyInstance`, use the
327 :meth:`.AssociationProxy.for_class` method::
329 proxy_state = inspect(User).all_orm_descriptors["keywords"].for_class(User)
331 # view if proxy object is scalar or not
332 >>> proxy_state.scalar
333 False
335 .. versionadded:: 1.3
337 """ # noqa
339 def __init__(self, parent, owning_class, target_class, value_attr):
340 self.parent = parent
341 self.key = parent.key
342 self.owning_class = owning_class
343 self.target_collection = parent.target_collection
344 self.collection_class = None
345 self.target_class = target_class
346 self.value_attr = value_attr
348 target_class = None
349 """The intermediary class handled by this
350 :class:`.AssociationProxyInstance`.
352 Intercepted append/set/assignment events will result
353 in the generation of new instances of this class.
355 """
357 @classmethod
358 def for_proxy(cls, parent, owning_class, parent_instance):
359 target_collection = parent.target_collection
360 value_attr = parent.value_attr
361 prop = orm.class_mapper(owning_class).get_property(target_collection)
363 # this was never asserted before but this should be made clear.
364 if not isinstance(prop, orm.RelationshipProperty):
365 util.raise_(
366 NotImplementedError(
367 "association proxy to a non-relationship "
368 "intermediary is not supported"
369 ),
370 replace_context=None,
371 )
373 target_class = prop.mapper.class_
375 try:
376 target_assoc = cls._cls_unwrap_target_assoc_proxy(
377 target_class, value_attr
378 )
379 except AttributeError:
380 # the proxied attribute doesn't exist on the target class;
381 # return an "ambiguous" instance that will work on a per-object
382 # basis
383 return AmbiguousAssociationProxyInstance(
384 parent, owning_class, target_class, value_attr
385 )
386 else:
387 return cls._construct_for_assoc(
388 target_assoc, parent, owning_class, target_class, value_attr
389 )
391 @classmethod
392 def _construct_for_assoc(
393 cls, target_assoc, parent, owning_class, target_class, value_attr
394 ):
395 if target_assoc is not None:
396 return ObjectAssociationProxyInstance(
397 parent, owning_class, target_class, value_attr
398 )
400 attr = getattr(target_class, value_attr)
401 if not hasattr(attr, "_is_internal_proxy"):
402 return AmbiguousAssociationProxyInstance(
403 parent, owning_class, target_class, value_attr
404 )
405 is_object = attr._impl_uses_objects
406 if is_object:
407 return ObjectAssociationProxyInstance(
408 parent, owning_class, target_class, value_attr
409 )
410 else:
411 return ColumnAssociationProxyInstance(
412 parent, owning_class, target_class, value_attr
413 )
415 def _get_property(self):
416 return orm.class_mapper(self.owning_class).get_property(
417 self.target_collection
418 )
420 @property
421 def _comparator(self):
422 return self._get_property().comparator
424 @classmethod
425 def _cls_unwrap_target_assoc_proxy(cls, target_class, value_attr):
426 attr = getattr(target_class, value_attr)
427 if isinstance(attr, (AssociationProxy, AssociationProxyInstance)):
428 return attr
429 return None
431 @util.memoized_property
432 def _unwrap_target_assoc_proxy(self):
433 return self._cls_unwrap_target_assoc_proxy(
434 self.target_class, self.value_attr
435 )
437 @property
438 def remote_attr(self):
439 """The 'remote' class attribute referenced by this
440 :class:`.AssociationProxyInstance`.
442 .. seealso::
444 :attr:`.AssociationProxyInstance.attr`
446 :attr:`.AssociationProxyInstance.local_attr`
448 """
449 return getattr(self.target_class, self.value_attr)
451 @property
452 def local_attr(self):
453 """The 'local' class attribute referenced by this
454 :class:`.AssociationProxyInstance`.
456 .. seealso::
458 :attr:`.AssociationProxyInstance.attr`
460 :attr:`.AssociationProxyInstance.remote_attr`
462 """
463 return getattr(self.owning_class, self.target_collection)
465 @property
466 def attr(self):
467 """Return a tuple of ``(local_attr, remote_attr)``.
469 This attribute is convenient when specifying a join
470 using :meth:`_query.Query.join` across two relationships::
472 sess.query(Parent).join(*Parent.proxied.attr)
474 .. seealso::
476 :attr:`.AssociationProxyInstance.local_attr`
478 :attr:`.AssociationProxyInstance.remote_attr`
480 """
481 return (self.local_attr, self.remote_attr)
483 @util.memoized_property
484 def scalar(self):
485 """Return ``True`` if this :class:`.AssociationProxyInstance`
486 proxies a scalar relationship on the local side."""
488 scalar = not self._get_property().uselist
489 if scalar:
490 self._initialize_scalar_accessors()
491 return scalar
493 @util.memoized_property
494 def _value_is_scalar(self):
495 return (
496 not self._get_property()
497 .mapper.get_property(self.value_attr)
498 .uselist
499 )
501 @property
502 def _target_is_object(self):
503 raise NotImplementedError()
505 def _initialize_scalar_accessors(self):
506 if self.parent.getset_factory:
507 get, set_ = self.parent.getset_factory(None, self)
508 else:
509 get, set_ = self.parent._default_getset(None)
510 self._scalar_get, self._scalar_set = get, set_
512 def _default_getset(self, collection_class):
513 attr = self.value_attr
514 _getter = operator.attrgetter(attr)
516 def getter(target):
517 return _getter(target) if target is not None else None
519 if collection_class is dict:
521 def setter(o, k, v):
522 return setattr(o, attr, v)
524 else:
526 def setter(o, v):
527 return setattr(o, attr, v)
529 return getter, setter
531 @property
532 def info(self):
533 return self.parent.info
535 def get(self, obj):
536 if obj is None:
537 return self
539 if self.scalar:
540 target = getattr(obj, self.target_collection)
541 return self._scalar_get(target)
542 else:
543 try:
544 # If the owning instance is reborn (orm session resurrect,
545 # etc.), refresh the proxy cache.
546 creator_id, self_id, proxy = getattr(obj, self.key)
547 except AttributeError:
548 pass
549 else:
550 if id(obj) == creator_id and id(self) == self_id:
551 assert self.collection_class is not None
552 return proxy
554 self.collection_class, proxy = self._new(
555 _lazy_collection(obj, self.target_collection)
556 )
557 setattr(obj, self.key, (id(obj), id(self), proxy))
558 return proxy
560 def set(self, obj, values):
561 if self.scalar:
562 creator = (
563 self.parent.creator
564 if self.parent.creator
565 else self.target_class
566 )
567 target = getattr(obj, self.target_collection)
568 if target is None:
569 if values is None:
570 return
571 setattr(obj, self.target_collection, creator(values))
572 else:
573 self._scalar_set(target, values)
574 if values is None and self.parent.cascade_scalar_deletes:
575 setattr(obj, self.target_collection, None)
576 else:
577 proxy = self.get(obj)
578 assert self.collection_class is not None
579 if proxy is not values:
580 proxy._bulk_replace(self, values)
582 def delete(self, obj):
583 if self.owning_class is None:
584 self._calc_owner(obj, None)
586 if self.scalar:
587 target = getattr(obj, self.target_collection)
588 if target is not None:
589 delattr(target, self.value_attr)
590 delattr(obj, self.target_collection)
592 def _new(self, lazy_collection):
593 creator = (
594 self.parent.creator if self.parent.creator else self.target_class
595 )
596 collection_class = util.duck_type_collection(lazy_collection())
598 if self.parent.proxy_factory:
599 return (
600 collection_class,
601 self.parent.proxy_factory(
602 lazy_collection, creator, self.value_attr, self
603 ),
604 )
606 if self.parent.getset_factory:
607 getter, setter = self.parent.getset_factory(collection_class, self)
608 else:
609 getter, setter = self.parent._default_getset(collection_class)
611 if collection_class is list:
612 return (
613 collection_class,
614 _AssociationList(
615 lazy_collection, creator, getter, setter, self
616 ),
617 )
618 elif collection_class is dict:
619 return (
620 collection_class,
621 _AssociationDict(
622 lazy_collection, creator, getter, setter, self
623 ),
624 )
625 elif collection_class is set:
626 return (
627 collection_class,
628 _AssociationSet(
629 lazy_collection, creator, getter, setter, self
630 ),
631 )
632 else:
633 raise exc.ArgumentError(
634 "could not guess which interface to use for "
635 'collection_class "%s" backing "%s"; specify a '
636 "proxy_factory and proxy_bulk_set manually"
637 % (self.collection_class.__name__, self.target_collection)
638 )
640 def _set(self, proxy, values):
641 if self.parent.proxy_bulk_set:
642 self.parent.proxy_bulk_set(proxy, values)
643 elif self.collection_class is list:
644 proxy.extend(values)
645 elif self.collection_class is dict:
646 proxy.update(values)
647 elif self.collection_class is set:
648 proxy.update(values)
649 else:
650 raise exc.ArgumentError(
651 "no proxy_bulk_set supplied for custom "
652 "collection_class implementation"
653 )
655 def _inflate(self, proxy):
656 creator = (
657 self.parent.creator and self.parent.creator or self.target_class
658 )
660 if self.parent.getset_factory:
661 getter, setter = self.parent.getset_factory(
662 self.collection_class, self
663 )
664 else:
665 getter, setter = self.parent._default_getset(self.collection_class)
667 proxy.creator = creator
668 proxy.getter = getter
669 proxy.setter = setter
671 def _criterion_exists(self, criterion=None, **kwargs):
672 is_has = kwargs.pop("is_has", None)
674 target_assoc = self._unwrap_target_assoc_proxy
675 if target_assoc is not None:
676 inner = target_assoc._criterion_exists(
677 criterion=criterion, **kwargs
678 )
679 return self._comparator._criterion_exists(inner)
681 if self._target_is_object:
682 prop = getattr(self.target_class, self.value_attr)
683 value_expr = prop._criterion_exists(criterion, **kwargs)
684 else:
685 if kwargs:
686 raise exc.ArgumentError(
687 "Can't apply keyword arguments to column-targeted "
688 "association proxy; use =="
689 )
690 elif is_has and criterion is not None:
691 raise exc.ArgumentError(
692 "Non-empty has() not allowed for "
693 "column-targeted association proxy; use =="
694 )
696 value_expr = criterion
698 return self._comparator._criterion_exists(value_expr)
700 def any(self, criterion=None, **kwargs):
701 """Produce a proxied 'any' expression using EXISTS.
703 This expression will be a composed product
704 using the :meth:`.RelationshipProperty.Comparator.any`
705 and/or :meth:`.RelationshipProperty.Comparator.has`
706 operators of the underlying proxied attributes.
708 """
709 if self._unwrap_target_assoc_proxy is None and (
710 self.scalar
711 and (not self._target_is_object or self._value_is_scalar)
712 ):
713 raise exc.InvalidRequestError(
714 "'any()' not implemented for scalar " "attributes. Use has()."
715 )
716 return self._criterion_exists(
717 criterion=criterion, is_has=False, **kwargs
718 )
720 def has(self, criterion=None, **kwargs):
721 """Produce a proxied 'has' expression using EXISTS.
723 This expression will be a composed product
724 using the :meth:`.RelationshipProperty.Comparator.any`
725 and/or :meth:`.RelationshipProperty.Comparator.has`
726 operators of the underlying proxied attributes.
728 """
729 if self._unwrap_target_assoc_proxy is None and (
730 not self.scalar
731 or (self._target_is_object and not self._value_is_scalar)
732 ):
733 raise exc.InvalidRequestError(
734 "'has()' not implemented for collections. " "Use any()."
735 )
736 return self._criterion_exists(
737 criterion=criterion, is_has=True, **kwargs
738 )
740 def __repr__(self):
741 return "%s(%r)" % (self.__class__.__name__, self.parent)
744class AmbiguousAssociationProxyInstance(AssociationProxyInstance):
745 """an :class:`.AssociationProxyInstance` where we cannot determine
746 the type of target object.
747 """
749 _is_canonical = False
751 def _ambiguous(self):
752 raise AttributeError(
753 "Association proxy %s.%s refers to an attribute '%s' that is not "
754 "directly mapped on class %s; therefore this operation cannot "
755 "proceed since we don't know what type of object is referred "
756 "towards"
757 % (
758 self.owning_class.__name__,
759 self.target_collection,
760 self.value_attr,
761 self.target_class,
762 )
763 )
765 def get(self, obj):
766 if obj is None:
767 return self
768 else:
769 return super(AmbiguousAssociationProxyInstance, self).get(obj)
771 def __eq__(self, obj):
772 self._ambiguous()
774 def __ne__(self, obj):
775 self._ambiguous()
777 def any(self, criterion=None, **kwargs):
778 self._ambiguous()
780 def has(self, criterion=None, **kwargs):
781 self._ambiguous()
783 @util.memoized_property
784 def _lookup_cache(self):
785 # mapping of <subclass>->AssociationProxyInstance.
786 # e.g. proxy is A-> A.b -> B -> B.b_attr, but B.b_attr doesn't exist;
787 # only B1(B) and B2(B) have "b_attr", keys in here would be B1, B2
788 return {}
790 def _non_canonical_get_for_object(self, parent_instance):
791 if parent_instance is not None:
792 actual_obj = getattr(parent_instance, self.target_collection)
793 if actual_obj is not None:
794 try:
795 insp = inspect(actual_obj)
796 except exc.NoInspectionAvailable:
797 pass
798 else:
799 mapper = insp.mapper
800 instance_class = mapper.class_
801 if instance_class not in self._lookup_cache:
802 self._populate_cache(instance_class, mapper)
804 try:
805 return self._lookup_cache[instance_class]
806 except KeyError:
807 pass
809 # no object or ambiguous object given, so return "self", which
810 # is a proxy with generally only instance-level functionality
811 return self
813 def _populate_cache(self, instance_class, mapper):
814 prop = orm.class_mapper(self.owning_class).get_property(
815 self.target_collection
816 )
818 if mapper.isa(prop.mapper):
819 target_class = instance_class
820 try:
821 target_assoc = self._cls_unwrap_target_assoc_proxy(
822 target_class, self.value_attr
823 )
824 except AttributeError:
825 pass
826 else:
827 self._lookup_cache[instance_class] = self._construct_for_assoc(
828 target_assoc,
829 self.parent,
830 self.owning_class,
831 target_class,
832 self.value_attr,
833 )
836class ObjectAssociationProxyInstance(AssociationProxyInstance):
837 """an :class:`.AssociationProxyInstance` that has an object as a target.
838 """
840 _target_is_object = True
841 _is_canonical = True
843 def contains(self, obj):
844 """Produce a proxied 'contains' expression using EXISTS.
846 This expression will be a composed product
847 using the :meth:`.RelationshipProperty.Comparator.any`
848 , :meth:`.RelationshipProperty.Comparator.has`,
849 and/or :meth:`.RelationshipProperty.Comparator.contains`
850 operators of the underlying proxied attributes.
851 """
853 target_assoc = self._unwrap_target_assoc_proxy
854 if target_assoc is not None:
855 return self._comparator._criterion_exists(
856 target_assoc.contains(obj)
857 if not target_assoc.scalar
858 else target_assoc == obj
859 )
860 elif (
861 self._target_is_object
862 and self.scalar
863 and not self._value_is_scalar
864 ):
865 return self._comparator.has(
866 getattr(self.target_class, self.value_attr).contains(obj)
867 )
868 elif self._target_is_object and self.scalar and self._value_is_scalar:
869 raise exc.InvalidRequestError(
870 "contains() doesn't apply to a scalar object endpoint; use =="
871 )
872 else:
874 return self._comparator._criterion_exists(**{self.value_attr: obj})
876 def __eq__(self, obj):
877 # note the has() here will fail for collections; eq_()
878 # is only allowed with a scalar.
879 if obj is None:
880 return or_(
881 self._comparator.has(**{self.value_attr: obj}),
882 self._comparator == None,
883 )
884 else:
885 return self._comparator.has(**{self.value_attr: obj})
887 def __ne__(self, obj):
888 # note the has() here will fail for collections; eq_()
889 # is only allowed with a scalar.
890 return self._comparator.has(
891 getattr(self.target_class, self.value_attr) != obj
892 )
895class ColumnAssociationProxyInstance(
896 ColumnOperators, AssociationProxyInstance
897):
898 """an :class:`.AssociationProxyInstance` that has a database column as a
899 target.
900 """
902 _target_is_object = False
903 _is_canonical = True
905 def __eq__(self, other):
906 # special case "is None" to check for no related row as well
907 expr = self._criterion_exists(
908 self.remote_attr.operate(operator.eq, other)
909 )
910 if other is None:
911 return or_(expr, self._comparator == None)
912 else:
913 return expr
915 def operate(self, op, *other, **kwargs):
916 return self._criterion_exists(
917 self.remote_attr.operate(op, *other, **kwargs)
918 )
921class _lazy_collection(object):
922 def __init__(self, obj, target):
923 self.parent = obj
924 self.target = target
926 def __call__(self):
927 return getattr(self.parent, self.target)
929 def __getstate__(self):
930 return {"obj": self.parent, "target": self.target}
932 def __setstate__(self, state):
933 self.parent = state["obj"]
934 self.target = state["target"]
937class _AssociationCollection(object):
938 def __init__(self, lazy_collection, creator, getter, setter, parent):
939 """Constructs an _AssociationCollection.
941 This will always be a subclass of either _AssociationList,
942 _AssociationSet, or _AssociationDict.
944 lazy_collection
945 A callable returning a list-based collection of entities (usually an
946 object attribute managed by a SQLAlchemy relationship())
948 creator
949 A function that creates new target entities. Given one parameter:
950 value. This assertion is assumed::
952 obj = creator(somevalue)
953 assert getter(obj) == somevalue
955 getter
956 A function. Given an associated object, return the 'value'.
958 setter
959 A function. Given an associated object and a value, store that
960 value on the object.
962 """
963 self.lazy_collection = lazy_collection
964 self.creator = creator
965 self.getter = getter
966 self.setter = setter
967 self.parent = parent
969 col = property(lambda self: self.lazy_collection())
971 def __len__(self):
972 return len(self.col)
974 def __bool__(self):
975 return bool(self.col)
977 __nonzero__ = __bool__
979 def __getstate__(self):
980 return {"parent": self.parent, "lazy_collection": self.lazy_collection}
982 def __setstate__(self, state):
983 self.parent = state["parent"]
984 self.lazy_collection = state["lazy_collection"]
985 self.parent._inflate(self)
987 def _bulk_replace(self, assoc_proxy, values):
988 self.clear()
989 assoc_proxy._set(self, values)
992class _AssociationList(_AssociationCollection):
993 """Generic, converting, list-to-list proxy."""
995 def _create(self, value):
996 return self.creator(value)
998 def _get(self, object_):
999 return self.getter(object_)
1001 def _set(self, object_, value):
1002 return self.setter(object_, value)
1004 def __getitem__(self, index):
1005 if not isinstance(index, slice):
1006 return self._get(self.col[index])
1007 else:
1008 return [self._get(member) for member in self.col[index]]
1010 def __setitem__(self, index, value):
1011 if not isinstance(index, slice):
1012 self._set(self.col[index], value)
1013 else:
1014 if index.stop is None:
1015 stop = len(self)
1016 elif index.stop < 0:
1017 stop = len(self) + index.stop
1018 else:
1019 stop = index.stop
1020 step = index.step or 1
1022 start = index.start or 0
1023 rng = list(range(index.start or 0, stop, step))
1024 if step == 1:
1025 for i in rng:
1026 del self[start]
1027 i = start
1028 for item in value:
1029 self.insert(i, item)
1030 i += 1
1031 else:
1032 if len(value) != len(rng):
1033 raise ValueError(
1034 "attempt to assign sequence of size %s to "
1035 "extended slice of size %s" % (len(value), len(rng))
1036 )
1037 for i, item in zip(rng, value):
1038 self._set(self.col[i], item)
1040 def __delitem__(self, index):
1041 del self.col[index]
1043 def __contains__(self, value):
1044 for member in self.col:
1045 # testlib.pragma exempt:__eq__
1046 if self._get(member) == value:
1047 return True
1048 return False
1050 def __getslice__(self, start, end):
1051 return [self._get(member) for member in self.col[start:end]]
1053 def __setslice__(self, start, end, values):
1054 members = [self._create(v) for v in values]
1055 self.col[start:end] = members
1057 def __delslice__(self, start, end):
1058 del self.col[start:end]
1060 def __iter__(self):
1061 """Iterate over proxied values.
1063 For the actual domain objects, iterate over .col instead or
1064 just use the underlying collection directly from its property
1065 on the parent.
1066 """
1068 for member in self.col:
1069 yield self._get(member)
1070 return
1072 def append(self, value):
1073 col = self.col
1074 item = self._create(value)
1075 col.append(item)
1077 def count(self, value):
1078 return sum(
1079 [
1080 1
1081 for _ in util.itertools_filter(
1082 lambda v: v == value, iter(self)
1083 )
1084 ]
1085 )
1087 def extend(self, values):
1088 for v in values:
1089 self.append(v)
1091 def insert(self, index, value):
1092 self.col[index:index] = [self._create(value)]
1094 def pop(self, index=-1):
1095 return self.getter(self.col.pop(index))
1097 def remove(self, value):
1098 for i, val in enumerate(self):
1099 if val == value:
1100 del self.col[i]
1101 return
1102 raise ValueError("value not in list")
1104 def reverse(self):
1105 """Not supported, use reversed(mylist)"""
1107 raise NotImplementedError
1109 def sort(self):
1110 """Not supported, use sorted(mylist)"""
1112 raise NotImplementedError
1114 def clear(self):
1115 del self.col[0 : len(self.col)]
1117 def __eq__(self, other):
1118 return list(self) == other
1120 def __ne__(self, other):
1121 return list(self) != other
1123 def __lt__(self, other):
1124 return list(self) < other
1126 def __le__(self, other):
1127 return list(self) <= other
1129 def __gt__(self, other):
1130 return list(self) > other
1132 def __ge__(self, other):
1133 return list(self) >= other
1135 def __cmp__(self, other):
1136 return util.cmp(list(self), other)
1138 def __add__(self, iterable):
1139 try:
1140 other = list(iterable)
1141 except TypeError:
1142 return NotImplemented
1143 return list(self) + other
1145 def __radd__(self, iterable):
1146 try:
1147 other = list(iterable)
1148 except TypeError:
1149 return NotImplemented
1150 return other + list(self)
1152 def __mul__(self, n):
1153 if not isinstance(n, int):
1154 return NotImplemented
1155 return list(self) * n
1157 __rmul__ = __mul__
1159 def __iadd__(self, iterable):
1160 self.extend(iterable)
1161 return self
1163 def __imul__(self, n):
1164 # unlike a regular list *=, proxied __imul__ will generate unique
1165 # backing objects for each copy. *= on proxied lists is a bit of
1166 # a stretch anyhow, and this interpretation of the __imul__ contract
1167 # is more plausibly useful than copying the backing objects.
1168 if not isinstance(n, int):
1169 return NotImplemented
1170 if n == 0:
1171 self.clear()
1172 elif n > 1:
1173 self.extend(list(self) * (n - 1))
1174 return self
1176 def index(self, item, *args):
1177 return list(self).index(item, *args)
1179 def copy(self):
1180 return list(self)
1182 def __repr__(self):
1183 return repr(list(self))
1185 def __hash__(self):
1186 raise TypeError("%s objects are unhashable" % type(self).__name__)
1188 for func_name, func in list(locals().items()):
1189 if (
1190 util.callable(func)
1191 and func.__name__ == func_name
1192 and not func.__doc__
1193 and hasattr(list, func_name)
1194 ):
1195 func.__doc__ = getattr(list, func_name).__doc__
1196 del func_name, func
1199_NotProvided = util.symbol("_NotProvided")
1202class _AssociationDict(_AssociationCollection):
1203 """Generic, converting, dict-to-dict proxy."""
1205 def _create(self, key, value):
1206 return self.creator(key, value)
1208 def _get(self, object_):
1209 return self.getter(object_)
1211 def _set(self, object_, key, value):
1212 return self.setter(object_, key, value)
1214 def __getitem__(self, key):
1215 return self._get(self.col[key])
1217 def __setitem__(self, key, value):
1218 if key in self.col:
1219 self._set(self.col[key], key, value)
1220 else:
1221 self.col[key] = self._create(key, value)
1223 def __delitem__(self, key):
1224 del self.col[key]
1226 def __contains__(self, key):
1227 # testlib.pragma exempt:__hash__
1228 return key in self.col
1230 def has_key(self, key):
1231 # testlib.pragma exempt:__hash__
1232 return key in self.col
1234 def __iter__(self):
1235 return iter(self.col.keys())
1237 def clear(self):
1238 self.col.clear()
1240 def __eq__(self, other):
1241 return dict(self) == other
1243 def __ne__(self, other):
1244 return dict(self) != other
1246 def __lt__(self, other):
1247 return dict(self) < other
1249 def __le__(self, other):
1250 return dict(self) <= other
1252 def __gt__(self, other):
1253 return dict(self) > other
1255 def __ge__(self, other):
1256 return dict(self) >= other
1258 def __cmp__(self, other):
1259 return util.cmp(dict(self), other)
1261 def __repr__(self):
1262 return repr(dict(self.items()))
1264 def get(self, key, default=None):
1265 try:
1266 return self[key]
1267 except KeyError:
1268 return default
1270 def setdefault(self, key, default=None):
1271 if key not in self.col:
1272 self.col[key] = self._create(key, default)
1273 return default
1274 else:
1275 return self[key]
1277 def keys(self):
1278 return self.col.keys()
1280 if util.py2k:
1282 def iteritems(self):
1283 return ((key, self._get(self.col[key])) for key in self.col)
1285 def itervalues(self):
1286 return (self._get(self.col[key]) for key in self.col)
1288 def iterkeys(self):
1289 return self.col.iterkeys()
1291 def values(self):
1292 return [self._get(member) for member in self.col.values()]
1294 def items(self):
1295 return [(k, self._get(self.col[k])) for k in self]
1297 else:
1299 def items(self):
1300 return ((key, self._get(self.col[key])) for key in self.col)
1302 def values(self):
1303 return (self._get(self.col[key]) for key in self.col)
1305 def pop(self, key, default=_NotProvided):
1306 if default is _NotProvided:
1307 member = self.col.pop(key)
1308 else:
1309 member = self.col.pop(key, default)
1310 return self._get(member)
1312 def popitem(self):
1313 item = self.col.popitem()
1314 return (item[0], self._get(item[1]))
1316 def update(self, *a, **kw):
1317 if len(a) > 1:
1318 raise TypeError(
1319 "update expected at most 1 arguments, got %i" % len(a)
1320 )
1321 elif len(a) == 1:
1322 seq_or_map = a[0]
1323 # discern dict from sequence - took the advice from
1324 # http://www.voidspace.org.uk/python/articles/duck_typing.shtml
1325 # still not perfect :(
1326 if hasattr(seq_or_map, "keys"):
1327 for item in seq_or_map:
1328 self[item] = seq_or_map[item]
1329 else:
1330 try:
1331 for k, v in seq_or_map:
1332 self[k] = v
1333 except ValueError as err:
1334 util.raise_(
1335 ValueError(
1336 "dictionary update sequence "
1337 "requires 2-element tuples"
1338 ),
1339 replace_context=err,
1340 )
1342 for key, value in kw:
1343 self[key] = value
1345 def _bulk_replace(self, assoc_proxy, values):
1346 existing = set(self)
1347 constants = existing.intersection(values or ())
1348 additions = set(values or ()).difference(constants)
1349 removals = existing.difference(constants)
1351 for key, member in values.items() or ():
1352 if key in additions:
1353 self[key] = member
1354 elif key in constants:
1355 self[key] = member
1357 for key in removals:
1358 del self[key]
1360 def copy(self):
1361 return dict(self.items())
1363 def __hash__(self):
1364 raise TypeError("%s objects are unhashable" % type(self).__name__)
1366 for func_name, func in list(locals().items()):
1367 if (
1368 util.callable(func)
1369 and func.__name__ == func_name
1370 and not func.__doc__
1371 and hasattr(dict, func_name)
1372 ):
1373 func.__doc__ = getattr(dict, func_name).__doc__
1374 del func_name, func
1377class _AssociationSet(_AssociationCollection):
1378 """Generic, converting, set-to-set proxy."""
1380 def _create(self, value):
1381 return self.creator(value)
1383 def _get(self, object_):
1384 return self.getter(object_)
1386 def __len__(self):
1387 return len(self.col)
1389 def __bool__(self):
1390 if self.col:
1391 return True
1392 else:
1393 return False
1395 __nonzero__ = __bool__
1397 def __contains__(self, value):
1398 for member in self.col:
1399 # testlib.pragma exempt:__eq__
1400 if self._get(member) == value:
1401 return True
1402 return False
1404 def __iter__(self):
1405 """Iterate over proxied values.
1407 For the actual domain objects, iterate over .col instead or just use
1408 the underlying collection directly from its property on the parent.
1410 """
1411 for member in self.col:
1412 yield self._get(member)
1413 return
1415 def add(self, value):
1416 if value not in self:
1417 self.col.add(self._create(value))
1419 # for discard and remove, choosing a more expensive check strategy rather
1420 # than call self.creator()
1421 def discard(self, value):
1422 for member in self.col:
1423 if self._get(member) == value:
1424 self.col.discard(member)
1425 break
1427 def remove(self, value):
1428 for member in self.col:
1429 if self._get(member) == value:
1430 self.col.discard(member)
1431 return
1432 raise KeyError(value)
1434 def pop(self):
1435 if not self.col:
1436 raise KeyError("pop from an empty set")
1437 member = self.col.pop()
1438 return self._get(member)
1440 def update(self, other):
1441 for value in other:
1442 self.add(value)
1444 def _bulk_replace(self, assoc_proxy, values):
1445 existing = set(self)
1446 constants = existing.intersection(values or ())
1447 additions = set(values or ()).difference(constants)
1448 removals = existing.difference(constants)
1450 appender = self.add
1451 remover = self.remove
1453 for member in values or ():
1454 if member in additions:
1455 appender(member)
1456 elif member in constants:
1457 appender(member)
1459 for member in removals:
1460 remover(member)
1462 def __ior__(self, other):
1463 if not collections._set_binops_check_strict(self, other):
1464 return NotImplemented
1465 for value in other:
1466 self.add(value)
1467 return self
1469 def _set(self):
1470 return set(iter(self))
1472 def union(self, other):
1473 return set(self).union(other)
1475 __or__ = union
1477 def difference(self, other):
1478 return set(self).difference(other)
1480 __sub__ = difference
1482 def difference_update(self, other):
1483 for value in other:
1484 self.discard(value)
1486 def __isub__(self, other):
1487 if not collections._set_binops_check_strict(self, other):
1488 return NotImplemented
1489 for value in other:
1490 self.discard(value)
1491 return self
1493 def intersection(self, other):
1494 return set(self).intersection(other)
1496 __and__ = intersection
1498 def intersection_update(self, other):
1499 want, have = self.intersection(other), set(self)
1501 remove, add = have - want, want - have
1503 for value in remove:
1504 self.remove(value)
1505 for value in add:
1506 self.add(value)
1508 def __iand__(self, other):
1509 if not collections._set_binops_check_strict(self, other):
1510 return NotImplemented
1511 want, have = self.intersection(other), set(self)
1513 remove, add = have - want, want - have
1515 for value in remove:
1516 self.remove(value)
1517 for value in add:
1518 self.add(value)
1519 return self
1521 def symmetric_difference(self, other):
1522 return set(self).symmetric_difference(other)
1524 __xor__ = symmetric_difference
1526 def symmetric_difference_update(self, other):
1527 want, have = self.symmetric_difference(other), set(self)
1529 remove, add = have - want, want - have
1531 for value in remove:
1532 self.remove(value)
1533 for value in add:
1534 self.add(value)
1536 def __ixor__(self, other):
1537 if not collections._set_binops_check_strict(self, other):
1538 return NotImplemented
1539 want, have = self.symmetric_difference(other), set(self)
1541 remove, add = have - want, want - have
1543 for value in remove:
1544 self.remove(value)
1545 for value in add:
1546 self.add(value)
1547 return self
1549 def issubset(self, other):
1550 return set(self).issubset(other)
1552 def issuperset(self, other):
1553 return set(self).issuperset(other)
1555 def clear(self):
1556 self.col.clear()
1558 def copy(self):
1559 return set(self)
1561 def __eq__(self, other):
1562 return set(self) == other
1564 def __ne__(self, other):
1565 return set(self) != other
1567 def __lt__(self, other):
1568 return set(self) < other
1570 def __le__(self, other):
1571 return set(self) <= other
1573 def __gt__(self, other):
1574 return set(self) > other
1576 def __ge__(self, other):
1577 return set(self) >= other
1579 def __repr__(self):
1580 return repr(set(self))
1582 def __hash__(self):
1583 raise TypeError("%s objects are unhashable" % type(self).__name__)
1585 for func_name, func in list(locals().items()):
1586 if (
1587 util.callable(func)
1588 and func.__name__ == func_name
1589 and not func.__doc__
1590 and hasattr(set, func_name)
1591 ):
1592 func.__doc__ = getattr(set, func_name).__doc__
1593 del func_name, func