Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/ext/associationproxy.py: 31%
758 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1# ext/associationproxy.py
2# Copyright (C) 2005-2023 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: https://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 except Exception as err:
387 util.raise_(
388 exc.InvalidRequestError(
389 "Association proxy received an unexpected error when "
390 "trying to retreive attribute "
391 '"%s.%s" from '
392 'class "%s": %s'
393 % (
394 target_class.__name__,
395 parent.value_attr,
396 target_class.__name__,
397 err,
398 )
399 ),
400 from_=err,
401 )
402 else:
403 return cls._construct_for_assoc(
404 target_assoc, parent, owning_class, target_class, value_attr
405 )
407 @classmethod
408 def _construct_for_assoc(
409 cls, target_assoc, parent, owning_class, target_class, value_attr
410 ):
411 if target_assoc is not None:
412 return ObjectAssociationProxyInstance(
413 parent, owning_class, target_class, value_attr
414 )
416 attr = getattr(target_class, value_attr)
417 if not hasattr(attr, "_is_internal_proxy"):
418 return AmbiguousAssociationProxyInstance(
419 parent, owning_class, target_class, value_attr
420 )
421 is_object = attr._impl_uses_objects
422 if is_object:
423 return ObjectAssociationProxyInstance(
424 parent, owning_class, target_class, value_attr
425 )
426 else:
427 return ColumnAssociationProxyInstance(
428 parent, owning_class, target_class, value_attr
429 )
431 def _get_property(self):
432 return orm.class_mapper(self.owning_class).get_property(
433 self.target_collection
434 )
436 @property
437 def _comparator(self):
438 return self._get_property().comparator
440 def __clause_element__(self):
441 raise NotImplementedError(
442 "The association proxy can't be used as a plain column "
443 "expression; it only works inside of a comparison expression"
444 )
446 @classmethod
447 def _cls_unwrap_target_assoc_proxy(cls, target_class, value_attr):
448 attr = getattr(target_class, value_attr)
449 if isinstance(attr, (AssociationProxy, AssociationProxyInstance)):
450 return attr
451 return None
453 @util.memoized_property
454 def _unwrap_target_assoc_proxy(self):
455 return self._cls_unwrap_target_assoc_proxy(
456 self.target_class, self.value_attr
457 )
459 @property
460 def remote_attr(self):
461 """The 'remote' class attribute referenced by this
462 :class:`.AssociationProxyInstance`.
464 .. seealso::
466 :attr:`.AssociationProxyInstance.attr`
468 :attr:`.AssociationProxyInstance.local_attr`
470 """
471 return getattr(self.target_class, self.value_attr)
473 @property
474 def local_attr(self):
475 """The 'local' class attribute referenced by this
476 :class:`.AssociationProxyInstance`.
478 .. seealso::
480 :attr:`.AssociationProxyInstance.attr`
482 :attr:`.AssociationProxyInstance.remote_attr`
484 """
485 return getattr(self.owning_class, self.target_collection)
487 @property
488 def attr(self):
489 """Return a tuple of ``(local_attr, remote_attr)``.
491 This attribute was originally intended to facilitate using the
492 :meth:`_query.Query.join` method to join across the two relationships
493 at once, however this makes use of a deprecated calling style.
495 To use :meth:`_sql.select.join` or :meth:`_orm.Query.join` with
496 an association proxy, the current method is to make use of the
497 :attr:`.AssociationProxyInstance.local_attr` and
498 :attr:`.AssociationProxyInstance.remote_attr` attributes separately::
500 stmt = (
501 select(Parent).
502 join(Parent.proxied.local_attr).
503 join(Parent.proxied.remote_attr)
504 )
506 A future release may seek to provide a more succinct join pattern
507 for association proxy attributes.
509 .. seealso::
511 :attr:`.AssociationProxyInstance.local_attr`
513 :attr:`.AssociationProxyInstance.remote_attr`
515 """
516 return (self.local_attr, self.remote_attr)
518 @util.memoized_property
519 def scalar(self):
520 """Return ``True`` if this :class:`.AssociationProxyInstance`
521 proxies a scalar relationship on the local side."""
523 scalar = not self._get_property().uselist
524 if scalar:
525 self._initialize_scalar_accessors()
526 return scalar
528 @util.memoized_property
529 def _value_is_scalar(self):
530 return (
531 not self._get_property()
532 .mapper.get_property(self.value_attr)
533 .uselist
534 )
536 @property
537 def _target_is_object(self):
538 raise NotImplementedError()
540 def _initialize_scalar_accessors(self):
541 if self.parent.getset_factory:
542 get, set_ = self.parent.getset_factory(None, self)
543 else:
544 get, set_ = self.parent._default_getset(None)
545 self._scalar_get, self._scalar_set = get, set_
547 def _default_getset(self, collection_class):
548 attr = self.value_attr
549 _getter = operator.attrgetter(attr)
551 def getter(target):
552 return _getter(target) if target is not None else None
554 if collection_class is dict:
556 def setter(o, k, v):
557 return setattr(o, attr, v)
559 else:
561 def setter(o, v):
562 return setattr(o, attr, v)
564 return getter, setter
566 @property
567 def info(self):
568 return self.parent.info
570 def get(self, obj):
571 if obj is None:
572 return self
574 if self.scalar:
575 target = getattr(obj, self.target_collection)
576 return self._scalar_get(target)
577 else:
578 try:
579 # If the owning instance is reborn (orm session resurrect,
580 # etc.), refresh the proxy cache.
581 creator_id, self_id, proxy = getattr(obj, self.key)
582 except AttributeError:
583 pass
584 else:
585 if id(obj) == creator_id and id(self) == self_id:
586 assert self.collection_class is not None
587 return proxy
589 self.collection_class, proxy = self._new(
590 _lazy_collection(obj, self.target_collection)
591 )
592 setattr(obj, self.key, (id(obj), id(self), proxy))
593 return proxy
595 def set(self, obj, values):
596 if self.scalar:
597 creator = (
598 self.parent.creator
599 if self.parent.creator
600 else self.target_class
601 )
602 target = getattr(obj, self.target_collection)
603 if target is None:
604 if values is None:
605 return
606 setattr(obj, self.target_collection, creator(values))
607 else:
608 self._scalar_set(target, values)
609 if values is None and self.parent.cascade_scalar_deletes:
610 setattr(obj, self.target_collection, None)
611 else:
612 proxy = self.get(obj)
613 assert self.collection_class is not None
614 if proxy is not values:
615 proxy._bulk_replace(self, values)
617 def delete(self, obj):
618 if self.owning_class is None:
619 self._calc_owner(obj, None)
621 if self.scalar:
622 target = getattr(obj, self.target_collection)
623 if target is not None:
624 delattr(target, self.value_attr)
625 delattr(obj, self.target_collection)
627 def _new(self, lazy_collection):
628 creator = (
629 self.parent.creator if self.parent.creator else self.target_class
630 )
631 collection_class = util.duck_type_collection(lazy_collection())
633 if self.parent.proxy_factory:
634 return (
635 collection_class,
636 self.parent.proxy_factory(
637 lazy_collection, creator, self.value_attr, self
638 ),
639 )
641 if self.parent.getset_factory:
642 getter, setter = self.parent.getset_factory(collection_class, self)
643 else:
644 getter, setter = self.parent._default_getset(collection_class)
646 if collection_class is list:
647 return (
648 collection_class,
649 _AssociationList(
650 lazy_collection, creator, getter, setter, self
651 ),
652 )
653 elif collection_class is dict:
654 return (
655 collection_class,
656 _AssociationDict(
657 lazy_collection, creator, getter, setter, self
658 ),
659 )
660 elif collection_class is set:
661 return (
662 collection_class,
663 _AssociationSet(
664 lazy_collection, creator, getter, setter, self
665 ),
666 )
667 else:
668 raise exc.ArgumentError(
669 "could not guess which interface to use for "
670 'collection_class "%s" backing "%s"; specify a '
671 "proxy_factory and proxy_bulk_set manually"
672 % (self.collection_class.__name__, self.target_collection)
673 )
675 def _set(self, proxy, values):
676 if self.parent.proxy_bulk_set:
677 self.parent.proxy_bulk_set(proxy, values)
678 elif self.collection_class is list:
679 proxy.extend(values)
680 elif self.collection_class is dict:
681 proxy.update(values)
682 elif self.collection_class is set:
683 proxy.update(values)
684 else:
685 raise exc.ArgumentError(
686 "no proxy_bulk_set supplied for custom "
687 "collection_class implementation"
688 )
690 def _inflate(self, proxy):
691 creator = (
692 self.parent.creator and self.parent.creator or self.target_class
693 )
695 if self.parent.getset_factory:
696 getter, setter = self.parent.getset_factory(
697 self.collection_class, self
698 )
699 else:
700 getter, setter = self.parent._default_getset(self.collection_class)
702 proxy.creator = creator
703 proxy.getter = getter
704 proxy.setter = setter
706 def _criterion_exists(self, criterion=None, **kwargs):
707 is_has = kwargs.pop("is_has", None)
709 target_assoc = self._unwrap_target_assoc_proxy
710 if target_assoc is not None:
711 inner = target_assoc._criterion_exists(
712 criterion=criterion, **kwargs
713 )
714 return self._comparator._criterion_exists(inner)
716 if self._target_is_object:
717 prop = getattr(self.target_class, self.value_attr)
718 value_expr = prop._criterion_exists(criterion, **kwargs)
719 else:
720 if kwargs:
721 raise exc.ArgumentError(
722 "Can't apply keyword arguments to column-targeted "
723 "association proxy; use =="
724 )
725 elif is_has and criterion is not None:
726 raise exc.ArgumentError(
727 "Non-empty has() not allowed for "
728 "column-targeted association proxy; use =="
729 )
731 value_expr = criterion
733 return self._comparator._criterion_exists(value_expr)
735 def any(self, criterion=None, **kwargs):
736 """Produce a proxied 'any' expression using EXISTS.
738 This expression will be a composed product
739 using the :meth:`.RelationshipProperty.Comparator.any`
740 and/or :meth:`.RelationshipProperty.Comparator.has`
741 operators of the underlying proxied attributes.
743 """
744 if self._unwrap_target_assoc_proxy is None and (
745 self.scalar
746 and (not self._target_is_object or self._value_is_scalar)
747 ):
748 raise exc.InvalidRequestError(
749 "'any()' not implemented for scalar " "attributes. Use has()."
750 )
751 return self._criterion_exists(
752 criterion=criterion, is_has=False, **kwargs
753 )
755 def has(self, criterion=None, **kwargs):
756 """Produce a proxied 'has' expression using EXISTS.
758 This expression will be a composed product
759 using the :meth:`.RelationshipProperty.Comparator.any`
760 and/or :meth:`.RelationshipProperty.Comparator.has`
761 operators of the underlying proxied attributes.
763 """
764 if self._unwrap_target_assoc_proxy is None and (
765 not self.scalar
766 or (self._target_is_object and not self._value_is_scalar)
767 ):
768 raise exc.InvalidRequestError(
769 "'has()' not implemented for collections. " "Use any()."
770 )
771 return self._criterion_exists(
772 criterion=criterion, is_has=True, **kwargs
773 )
775 def __repr__(self):
776 return "%s(%r)" % (self.__class__.__name__, self.parent)
779class AmbiguousAssociationProxyInstance(AssociationProxyInstance):
780 """an :class:`.AssociationProxyInstance` where we cannot determine
781 the type of target object.
782 """
784 _is_canonical = False
786 def _ambiguous(self):
787 raise AttributeError(
788 "Association proxy %s.%s refers to an attribute '%s' that is not "
789 "directly mapped on class %s; therefore this operation cannot "
790 "proceed since we don't know what type of object is referred "
791 "towards"
792 % (
793 self.owning_class.__name__,
794 self.target_collection,
795 self.value_attr,
796 self.target_class,
797 )
798 )
800 def get(self, obj):
801 if obj is None:
802 return self
803 else:
804 return super(AmbiguousAssociationProxyInstance, self).get(obj)
806 def __eq__(self, obj):
807 self._ambiguous()
809 def __ne__(self, obj):
810 self._ambiguous()
812 def any(self, criterion=None, **kwargs):
813 self._ambiguous()
815 def has(self, criterion=None, **kwargs):
816 self._ambiguous()
818 @util.memoized_property
819 def _lookup_cache(self):
820 # mapping of <subclass>->AssociationProxyInstance.
821 # e.g. proxy is A-> A.b -> B -> B.b_attr, but B.b_attr doesn't exist;
822 # only B1(B) and B2(B) have "b_attr", keys in here would be B1, B2
823 return {}
825 def _non_canonical_get_for_object(self, parent_instance):
826 if parent_instance is not None:
827 actual_obj = getattr(parent_instance, self.target_collection)
828 if actual_obj is not None:
829 try:
830 insp = inspect(actual_obj)
831 except exc.NoInspectionAvailable:
832 pass
833 else:
834 mapper = insp.mapper
835 instance_class = mapper.class_
836 if instance_class not in self._lookup_cache:
837 self._populate_cache(instance_class, mapper)
839 try:
840 return self._lookup_cache[instance_class]
841 except KeyError:
842 pass
844 # no object or ambiguous object given, so return "self", which
845 # is a proxy with generally only instance-level functionality
846 return self
848 def _populate_cache(self, instance_class, mapper):
849 prop = orm.class_mapper(self.owning_class).get_property(
850 self.target_collection
851 )
853 if mapper.isa(prop.mapper):
854 target_class = instance_class
855 try:
856 target_assoc = self._cls_unwrap_target_assoc_proxy(
857 target_class, self.value_attr
858 )
859 except AttributeError:
860 pass
861 else:
862 self._lookup_cache[instance_class] = self._construct_for_assoc(
863 target_assoc,
864 self.parent,
865 self.owning_class,
866 target_class,
867 self.value_attr,
868 )
871class ObjectAssociationProxyInstance(AssociationProxyInstance):
872 """an :class:`.AssociationProxyInstance` that has an object as a target."""
874 _target_is_object = True
875 _is_canonical = True
877 def contains(self, obj):
878 """Produce a proxied 'contains' expression using EXISTS.
880 This expression will be a composed product
881 using the :meth:`.RelationshipProperty.Comparator.any`,
882 :meth:`.RelationshipProperty.Comparator.has`,
883 and/or :meth:`.RelationshipProperty.Comparator.contains`
884 operators of the underlying proxied attributes.
885 """
887 target_assoc = self._unwrap_target_assoc_proxy
888 if target_assoc is not None:
889 return self._comparator._criterion_exists(
890 target_assoc.contains(obj)
891 if not target_assoc.scalar
892 else target_assoc == obj
893 )
894 elif (
895 self._target_is_object
896 and self.scalar
897 and not self._value_is_scalar
898 ):
899 return self._comparator.has(
900 getattr(self.target_class, self.value_attr).contains(obj)
901 )
902 elif self._target_is_object and self.scalar and self._value_is_scalar:
903 raise exc.InvalidRequestError(
904 "contains() doesn't apply to a scalar object endpoint; use =="
905 )
906 else:
908 return self._comparator._criterion_exists(**{self.value_attr: obj})
910 def __eq__(self, obj):
911 # note the has() here will fail for collections; eq_()
912 # is only allowed with a scalar.
913 if obj is None:
914 return or_(
915 self._comparator.has(**{self.value_attr: obj}),
916 self._comparator == None,
917 )
918 else:
919 return self._comparator.has(**{self.value_attr: obj})
921 def __ne__(self, obj):
922 # note the has() here will fail for collections; eq_()
923 # is only allowed with a scalar.
924 return self._comparator.has(
925 getattr(self.target_class, self.value_attr) != obj
926 )
929class ColumnAssociationProxyInstance(
930 ColumnOperators, AssociationProxyInstance
931):
932 """an :class:`.AssociationProxyInstance` that has a database column as a
933 target.
934 """
936 _target_is_object = False
937 _is_canonical = True
939 def __eq__(self, other):
940 # special case "is None" to check for no related row as well
941 expr = self._criterion_exists(
942 self.remote_attr.operate(operator.eq, other)
943 )
944 if other is None:
945 return or_(expr, self._comparator == None)
946 else:
947 return expr
949 def operate(self, op, *other, **kwargs):
950 return self._criterion_exists(
951 self.remote_attr.operate(op, *other, **kwargs)
952 )
955class _lazy_collection(object):
956 def __init__(self, obj, target):
957 self.parent = obj
958 self.target = target
960 def __call__(self):
961 return getattr(self.parent, self.target)
963 def __getstate__(self):
964 return {"obj": self.parent, "target": self.target}
966 def __setstate__(self, state):
967 self.parent = state["obj"]
968 self.target = state["target"]
971class _AssociationCollection(object):
972 def __init__(self, lazy_collection, creator, getter, setter, parent):
973 """Constructs an _AssociationCollection.
975 This will always be a subclass of either _AssociationList,
976 _AssociationSet, or _AssociationDict.
978 lazy_collection
979 A callable returning a list-based collection of entities (usually an
980 object attribute managed by a SQLAlchemy relationship())
982 creator
983 A function that creates new target entities. Given one parameter:
984 value. This assertion is assumed::
986 obj = creator(somevalue)
987 assert getter(obj) == somevalue
989 getter
990 A function. Given an associated object, return the 'value'.
992 setter
993 A function. Given an associated object and a value, store that
994 value on the object.
996 """
997 self.lazy_collection = lazy_collection
998 self.creator = creator
999 self.getter = getter
1000 self.setter = setter
1001 self.parent = parent
1003 col = property(lambda self: self.lazy_collection())
1005 def __len__(self):
1006 return len(self.col)
1008 def __bool__(self):
1009 return bool(self.col)
1011 __nonzero__ = __bool__
1013 def __getstate__(self):
1014 return {"parent": self.parent, "lazy_collection": self.lazy_collection}
1016 def __setstate__(self, state):
1017 self.parent = state["parent"]
1018 self.lazy_collection = state["lazy_collection"]
1019 self.parent._inflate(self)
1021 def _bulk_replace(self, assoc_proxy, values):
1022 self.clear()
1023 assoc_proxy._set(self, values)
1026class _AssociationList(_AssociationCollection):
1027 """Generic, converting, list-to-list proxy."""
1029 def _create(self, value):
1030 return self.creator(value)
1032 def _get(self, object_):
1033 return self.getter(object_)
1035 def _set(self, object_, value):
1036 return self.setter(object_, value)
1038 def __getitem__(self, index):
1039 if not isinstance(index, slice):
1040 return self._get(self.col[index])
1041 else:
1042 return [self._get(member) for member in self.col[index]]
1044 def __setitem__(self, index, value):
1045 if not isinstance(index, slice):
1046 self._set(self.col[index], value)
1047 else:
1048 if index.stop is None:
1049 stop = len(self)
1050 elif index.stop < 0:
1051 stop = len(self) + index.stop
1052 else:
1053 stop = index.stop
1054 step = index.step or 1
1056 start = index.start or 0
1057 rng = list(range(index.start or 0, stop, step))
1058 if step == 1:
1059 for i in rng:
1060 del self[start]
1061 i = start
1062 for item in value:
1063 self.insert(i, item)
1064 i += 1
1065 else:
1066 if len(value) != len(rng):
1067 raise ValueError(
1068 "attempt to assign sequence of size %s to "
1069 "extended slice of size %s" % (len(value), len(rng))
1070 )
1071 for i, item in zip(rng, value):
1072 self._set(self.col[i], item)
1074 def __delitem__(self, index):
1075 del self.col[index]
1077 def __contains__(self, value):
1078 for member in self.col:
1079 # testlib.pragma exempt:__eq__
1080 if self._get(member) == value:
1081 return True
1082 return False
1084 def __getslice__(self, start, end):
1085 return [self._get(member) for member in self.col[start:end]]
1087 def __setslice__(self, start, end, values):
1088 members = [self._create(v) for v in values]
1089 self.col[start:end] = members
1091 def __delslice__(self, start, end):
1092 del self.col[start:end]
1094 def __iter__(self):
1095 """Iterate over proxied values.
1097 For the actual domain objects, iterate over .col instead or
1098 just use the underlying collection directly from its property
1099 on the parent.
1100 """
1102 for member in self.col:
1103 yield self._get(member)
1104 return
1106 def append(self, value):
1107 col = self.col
1108 item = self._create(value)
1109 col.append(item)
1111 def count(self, value):
1112 return sum(
1113 [
1114 1
1115 for _ in util.itertools_filter(
1116 lambda v: v == value, iter(self)
1117 )
1118 ]
1119 )
1121 def extend(self, values):
1122 for v in values:
1123 self.append(v)
1125 def insert(self, index, value):
1126 self.col[index:index] = [self._create(value)]
1128 def pop(self, index=-1):
1129 return self.getter(self.col.pop(index))
1131 def remove(self, value):
1132 for i, val in enumerate(self):
1133 if val == value:
1134 del self.col[i]
1135 return
1136 raise ValueError("value not in list")
1138 def reverse(self):
1139 """Not supported, use reversed(mylist)"""
1141 raise NotImplementedError
1143 def sort(self):
1144 """Not supported, use sorted(mylist)"""
1146 raise NotImplementedError
1148 def clear(self):
1149 del self.col[0 : len(self.col)]
1151 def __eq__(self, other):
1152 return list(self) == other
1154 def __ne__(self, other):
1155 return list(self) != other
1157 def __lt__(self, other):
1158 return list(self) < other
1160 def __le__(self, other):
1161 return list(self) <= other
1163 def __gt__(self, other):
1164 return list(self) > other
1166 def __ge__(self, other):
1167 return list(self) >= other
1169 def __cmp__(self, other):
1170 return util.cmp(list(self), other)
1172 def __add__(self, iterable):
1173 try:
1174 other = list(iterable)
1175 except TypeError:
1176 return NotImplemented
1177 return list(self) + other
1179 def __radd__(self, iterable):
1180 try:
1181 other = list(iterable)
1182 except TypeError:
1183 return NotImplemented
1184 return other + list(self)
1186 def __mul__(self, n):
1187 if not isinstance(n, int):
1188 return NotImplemented
1189 return list(self) * n
1191 __rmul__ = __mul__
1193 def __iadd__(self, iterable):
1194 self.extend(iterable)
1195 return self
1197 def __imul__(self, n):
1198 # unlike a regular list *=, proxied __imul__ will generate unique
1199 # backing objects for each copy. *= on proxied lists is a bit of
1200 # a stretch anyhow, and this interpretation of the __imul__ contract
1201 # is more plausibly useful than copying the backing objects.
1202 if not isinstance(n, int):
1203 return NotImplemented
1204 if n == 0:
1205 self.clear()
1206 elif n > 1:
1207 self.extend(list(self) * (n - 1))
1208 return self
1210 def index(self, item, *args):
1211 return list(self).index(item, *args)
1213 def copy(self):
1214 return list(self)
1216 def __repr__(self):
1217 return repr(list(self))
1219 def __hash__(self):
1220 raise TypeError("%s objects are unhashable" % type(self).__name__)
1222 for func_name, func in list(locals().items()):
1223 if (
1224 callable(func)
1225 and func.__name__ == func_name
1226 and not func.__doc__
1227 and hasattr(list, func_name)
1228 ):
1229 func.__doc__ = getattr(list, func_name).__doc__
1230 del func_name, func
1233_NotProvided = util.symbol("_NotProvided")
1236class _AssociationDict(_AssociationCollection):
1237 """Generic, converting, dict-to-dict proxy."""
1239 def _create(self, key, value):
1240 return self.creator(key, value)
1242 def _get(self, object_):
1243 return self.getter(object_)
1245 def _set(self, object_, key, value):
1246 return self.setter(object_, key, value)
1248 def __getitem__(self, key):
1249 return self._get(self.col[key])
1251 def __setitem__(self, key, value):
1252 if key in self.col:
1253 self._set(self.col[key], key, value)
1254 else:
1255 self.col[key] = self._create(key, value)
1257 def __delitem__(self, key):
1258 del self.col[key]
1260 def __contains__(self, key):
1261 # testlib.pragma exempt:__hash__
1262 return key in self.col
1264 def has_key(self, key):
1265 # testlib.pragma exempt:__hash__
1266 return key in self.col
1268 def __iter__(self):
1269 return iter(self.col.keys())
1271 def clear(self):
1272 self.col.clear()
1274 def __eq__(self, other):
1275 return dict(self) == other
1277 def __ne__(self, other):
1278 return dict(self) != other
1280 def __lt__(self, other):
1281 return dict(self) < other
1283 def __le__(self, other):
1284 return dict(self) <= other
1286 def __gt__(self, other):
1287 return dict(self) > other
1289 def __ge__(self, other):
1290 return dict(self) >= other
1292 def __cmp__(self, other):
1293 return util.cmp(dict(self), other)
1295 def __repr__(self):
1296 return repr(dict(self.items()))
1298 def get(self, key, default=None):
1299 try:
1300 return self[key]
1301 except KeyError:
1302 return default
1304 def setdefault(self, key, default=None):
1305 if key not in self.col:
1306 self.col[key] = self._create(key, default)
1307 return default
1308 else:
1309 return self[key]
1311 def keys(self):
1312 return self.col.keys()
1314 if util.py2k:
1316 def iteritems(self):
1317 return ((key, self._get(self.col[key])) for key in self.col)
1319 def itervalues(self):
1320 return (self._get(self.col[key]) for key in self.col)
1322 def iterkeys(self):
1323 return self.col.iterkeys()
1325 def values(self):
1326 return [self._get(member) for member in self.col.values()]
1328 def items(self):
1329 return [(k, self._get(self.col[k])) for k in self]
1331 else:
1333 def items(self):
1334 return ((key, self._get(self.col[key])) for key in self.col)
1336 def values(self):
1337 return (self._get(self.col[key]) for key in self.col)
1339 def pop(self, key, default=_NotProvided):
1340 if default is _NotProvided:
1341 member = self.col.pop(key)
1342 else:
1343 member = self.col.pop(key, default)
1344 return self._get(member)
1346 def popitem(self):
1347 item = self.col.popitem()
1348 return (item[0], self._get(item[1]))
1350 def update(self, *a, **kw):
1351 if len(a) > 1:
1352 raise TypeError(
1353 "update expected at most 1 arguments, got %i" % len(a)
1354 )
1355 elif len(a) == 1:
1356 seq_or_map = a[0]
1357 # discern dict from sequence - took the advice from
1358 # https://www.voidspace.org.uk/python/articles/duck_typing.shtml
1359 # still not perfect :(
1360 if hasattr(seq_or_map, "keys"):
1361 for item in seq_or_map:
1362 self[item] = seq_or_map[item]
1363 else:
1364 try:
1365 for k, v in seq_or_map:
1366 self[k] = v
1367 except ValueError as err:
1368 util.raise_(
1369 ValueError(
1370 "dictionary update sequence "
1371 "requires 2-element tuples"
1372 ),
1373 replace_context=err,
1374 )
1376 for key, value in kw:
1377 self[key] = value
1379 def _bulk_replace(self, assoc_proxy, values):
1380 existing = set(self)
1381 constants = existing.intersection(values or ())
1382 additions = set(values or ()).difference(constants)
1383 removals = existing.difference(constants)
1385 for key, member in values.items() or ():
1386 if key in additions:
1387 self[key] = member
1388 elif key in constants:
1389 self[key] = member
1391 for key in removals:
1392 del self[key]
1394 def copy(self):
1395 return dict(self.items())
1397 def __hash__(self):
1398 raise TypeError("%s objects are unhashable" % type(self).__name__)
1400 for func_name, func in list(locals().items()):
1401 if (
1402 callable(func)
1403 and func.__name__ == func_name
1404 and not func.__doc__
1405 and hasattr(dict, func_name)
1406 ):
1407 func.__doc__ = getattr(dict, func_name).__doc__
1408 del func_name, func
1411class _AssociationSet(_AssociationCollection):
1412 """Generic, converting, set-to-set proxy."""
1414 def _create(self, value):
1415 return self.creator(value)
1417 def _get(self, object_):
1418 return self.getter(object_)
1420 def __len__(self):
1421 return len(self.col)
1423 def __bool__(self):
1424 if self.col:
1425 return True
1426 else:
1427 return False
1429 __nonzero__ = __bool__
1431 def __contains__(self, value):
1432 for member in self.col:
1433 # testlib.pragma exempt:__eq__
1434 if self._get(member) == value:
1435 return True
1436 return False
1438 def __iter__(self):
1439 """Iterate over proxied values.
1441 For the actual domain objects, iterate over .col instead or just use
1442 the underlying collection directly from its property on the parent.
1444 """
1445 for member in self.col:
1446 yield self._get(member)
1447 return
1449 def add(self, value):
1450 if value not in self:
1451 self.col.add(self._create(value))
1453 # for discard and remove, choosing a more expensive check strategy rather
1454 # than call self.creator()
1455 def discard(self, value):
1456 for member in self.col:
1457 if self._get(member) == value:
1458 self.col.discard(member)
1459 break
1461 def remove(self, value):
1462 for member in self.col:
1463 if self._get(member) == value:
1464 self.col.discard(member)
1465 return
1466 raise KeyError(value)
1468 def pop(self):
1469 if not self.col:
1470 raise KeyError("pop from an empty set")
1471 member = self.col.pop()
1472 return self._get(member)
1474 def update(self, other):
1475 for value in other:
1476 self.add(value)
1478 def _bulk_replace(self, assoc_proxy, values):
1479 existing = set(self)
1480 constants = existing.intersection(values or ())
1481 additions = set(values or ()).difference(constants)
1482 removals = existing.difference(constants)
1484 appender = self.add
1485 remover = self.remove
1487 for member in values or ():
1488 if member in additions:
1489 appender(member)
1490 elif member in constants:
1491 appender(member)
1493 for member in removals:
1494 remover(member)
1496 def __ior__(self, other):
1497 if not collections._set_binops_check_strict(self, other):
1498 return NotImplemented
1499 for value in other:
1500 self.add(value)
1501 return self
1503 def _set(self):
1504 return set(iter(self))
1506 def union(self, other):
1507 return set(self).union(other)
1509 __or__ = union
1511 def difference(self, other):
1512 return set(self).difference(other)
1514 __sub__ = difference
1516 def difference_update(self, other):
1517 for value in other:
1518 self.discard(value)
1520 def __isub__(self, other):
1521 if not collections._set_binops_check_strict(self, other):
1522 return NotImplemented
1523 for value in other:
1524 self.discard(value)
1525 return self
1527 def intersection(self, other):
1528 return set(self).intersection(other)
1530 __and__ = intersection
1532 def intersection_update(self, other):
1533 want, have = self.intersection(other), set(self)
1535 remove, add = have - want, want - have
1537 for value in remove:
1538 self.remove(value)
1539 for value in add:
1540 self.add(value)
1542 def __iand__(self, other):
1543 if not collections._set_binops_check_strict(self, other):
1544 return NotImplemented
1545 want, have = self.intersection(other), set(self)
1547 remove, add = have - want, want - have
1549 for value in remove:
1550 self.remove(value)
1551 for value in add:
1552 self.add(value)
1553 return self
1555 def symmetric_difference(self, other):
1556 return set(self).symmetric_difference(other)
1558 __xor__ = symmetric_difference
1560 def symmetric_difference_update(self, other):
1561 want, have = self.symmetric_difference(other), set(self)
1563 remove, add = have - want, want - have
1565 for value in remove:
1566 self.remove(value)
1567 for value in add:
1568 self.add(value)
1570 def __ixor__(self, other):
1571 if not collections._set_binops_check_strict(self, other):
1572 return NotImplemented
1573 want, have = self.symmetric_difference(other), set(self)
1575 remove, add = have - want, want - have
1577 for value in remove:
1578 self.remove(value)
1579 for value in add:
1580 self.add(value)
1581 return self
1583 def issubset(self, other):
1584 return set(self).issubset(other)
1586 def issuperset(self, other):
1587 return set(self).issuperset(other)
1589 def clear(self):
1590 self.col.clear()
1592 def copy(self):
1593 return set(self)
1595 def __eq__(self, other):
1596 return set(self) == other
1598 def __ne__(self, other):
1599 return set(self) != other
1601 def __lt__(self, other):
1602 return set(self) < other
1604 def __le__(self, other):
1605 return set(self) <= other
1607 def __gt__(self, other):
1608 return set(self) > other
1610 def __ge__(self, other):
1611 return set(self) >= other
1613 def __repr__(self):
1614 return repr(set(self))
1616 def __hash__(self):
1617 raise TypeError("%s objects are unhashable" % type(self).__name__)
1619 for func_name, func in list(locals().items()):
1620 if (
1621 callable(func)
1622 and func.__name__ == func_name
1623 and not func.__doc__
1624 and hasattr(set, func_name)
1625 ):
1626 func.__doc__ = getattr(set, func_name).__doc__
1627 del func_name, func