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

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

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

行番号 
1"""Contain the ``AssociationProxy`` class.
2
3The ``AssociationProxy`` is a Python property object which provides
4transparent proxied access to the endpoint of an association object.
5
6See the example ``examples/association/proxied_association.py``.
7
8"""
9import itertools
10import operator
11import weakref
12from sqlalchemy import exceptions
13from sqlalchemy import orm
14from sqlalchemy import util
15from sqlalchemy.orm import collections
16
17
18def association_proxy(target_collection, attr, **kw):
19    """Return a Python property implementing a view of *attr* over a collection.
20
21    Implements a read/write view over an instance's *target_collection*,
22    extracting *attr* from each member of the collection.  The property acts
23    somewhat like this list comprehension::
24
25      [getattr(member, *attr*)
26       for member in getattr(instance, *target_collection*)]
27
28    Unlike the list comprehension, the collection returned by the property is
29    always in sync with *target_collection*, and mutations made to either
30    collection will be reflected in both.
31
32    Implements a Python property representing a relation as a collection of
33    simpler values.  The proxied property will mimic the collection type of
34    the target (list, dict or set), or, in the case of a one to one relation,
35    a simple scalar value.
36
37    :param target_collection: Name of the relation attribute we'll proxy to,
38      usually created with :func:`~sqlalchemy.orm.relation`.
39
40    :param attr: Attribute on the associated instances we'll proxy for.
41
42      For example, given a target collection of [obj1, obj2], a list created
43      by this proxy property would look like [getattr(obj1, *attr*),
44      getattr(obj2, *attr*)]
45
46      If the relation is one-to-one or otherwise uselist=False, then simply:
47      getattr(obj, *attr*)
48
49    :param creator: optional.
50
51      When new items are added to this proxied collection, new instances of
52      the class collected by the target collection will be created.  For list
53      and set collections, the target class constructor will be called with
54      the 'value' for the new instance.  For dict types, two arguments are
55      passed: key and value.
56
57      If you want to construct instances differently, supply a *creator*
58      function that takes arguments as above and returns instances.
59
60      For scalar relations, creator() will be called if the target is None.
61      If the target is present, set operations are proxied to setattr() on the
62      associated object.
63
64      If you have an associated object with multiple attributes, you may set
65      up multiple association proxies mapping to different attributes.  See
66      the unit tests for examples, and for examples of how creator() functions
67      can be used to construct the scalar relation on-demand in this
68      situation.
69
70    :param \*\*kw: Passes along any other keyword arguments to
71      :class:`AssociationProxy`.
72
73    """
74    return AssociationProxy(target_collection, attr, **kw)
75
76
77class AssociationProxy(object):
78    """A descriptor that presents a read/write view of an object attribute."""
79
80    def __init__(self, target_collection, attr, creator=None,
81                 getset_factory=None, proxy_factory=None, proxy_bulk_set=None):
82        """Arguments are:
83
84        target_collection
85          Name of the collection we'll proxy to, usually created with
86          'relation()' in a mapper setup.
87
88        attr
89          Attribute on the collected instances we'll proxy for.  For example,
90          given a target collection of [obj1, obj2], a list created by this
91          proxy property would look like [getattr(obj1, attr), getattr(obj2,
92          attr)]
93
94        creator
95          Optional. When new items are added to this proxied collection, new
96          instances of the class collected by the target collection will be
97          created.  For list and set collections, the target class constructor
98          will be called with the 'value' for the new instance.  For dict
99          types, two arguments are passed: key and value.
100
101          If you want to construct instances differently, supply a 'creator'
102          function that takes arguments as above and returns instances.
103
104        getset_factory
105          Optional.  Proxied attribute access is automatically handled by
106          routines that get and set values based on the `attr` argument for
107          this proxy.
108
109          If you would like to customize this behavior, you may supply a
110          `getset_factory` callable that produces a tuple of `getter` and
111          `setter` functions.  The factory is called with two arguments, the
112          abstract type of the underlying collection and this proxy instance.
113
114        proxy_factory
115          Optional.  The type of collection to emulate is determined by
116          sniffing the target collection.  If your collection type can't be
117          determined by duck typing or you'd like to use a different
118          collection implementation, you may supply a factory function to
119          produce those collections.  Only applicable to non-scalar relations.
120
121        proxy_bulk_set
122          Optional, use with proxy_factory.  See the _set() method for
123          details.
124
125        """
126        self.target_collection = target_collection
127        self.value_attr = attr
128        self.creator = creator
129        self.getset_factory = getset_factory
130        self.proxy_factory = proxy_factory
131        self.proxy_bulk_set = proxy_bulk_set
132
133        self.scalar = None
134        self.owning_class = None
135        self.key = '_%s_%s_%s' % (
136            type(self).__name__, target_collection, id(self))
137        self.collection_class = None
138
139    def _get_property(self):
140        return (orm.class_mapper(self.owning_class).
141                get_property(self.target_collection))
142
143    @property
144    def target_class(self):
145        """The class the proxy is attached to."""
146        return self._get_property().mapper.class_
147
148    def _target_is_scalar(self):
149        return not self._get_property().uselist
150
151    def __get__(self, obj, class_):
152        if self.owning_class is None:
153            self.owning_class = class_ and class_ or type(obj)
154        if obj is None:
155            return self
156        elif self.scalar is None:
157            self.scalar = self._target_is_scalar()
158            if self.scalar:
159                self._initialize_scalar_accessors()
160
161        if self.scalar:
162            return self._scalar_get(getattr(obj, self.target_collection))
163        else:
164            try:
165                # If the owning instance is reborn (orm session resurrect,
166                # etc.), refresh the proxy cache.
167                creator_id, proxy = getattr(obj, self.key)
168                if id(obj) == creator_id:
169                    return proxy
170            except AttributeError:
171                pass
172            proxy = self._new(_lazy_collection(obj, self.target_collection))
173            setattr(obj, self.key, (id(obj), proxy))
174            return proxy
175   
176    def __set__(self, obj, values):
177        if self.owning_class is None:
178            self.owning_class = type(obj)
179        if self.scalar is None:
180            self.scalar = self._target_is_scalar()
181            if self.scalar:
182                self._initialize_scalar_accessors()
183
184        if self.scalar:
185            creator = self.creator and self.creator or self.target_class
186            target = getattr(obj, self.target_collection)
187            if target is None:
188                setattr(obj, self.target_collection, creator(values))
189            else:
190                self._scalar_set(target, values)
191        else:
192            proxy = self.__get__(obj, None)
193            if proxy is not values:
194                proxy.clear()
195                self._set(proxy, values)
196
197    def __delete__(self, obj):
198        if self.owning_class is None:
199            self.owning_class = type(obj)
200        delattr(obj, self.key)
201
202    def _initialize_scalar_accessors(self):
203        if self.getset_factory:
204            get, set = self.getset_factory(None, self)
205        else:
206            get, set = self._default_getset(None)
207        self._scalar_get, self._scalar_set = get, set
208
209    def _default_getset(self, collection_class):
210        attr = self.value_attr
211        getter = operator.attrgetter(attr)
212        if collection_class is dict:
213            setter = lambda o, k, v: setattr(o, attr, v)
214        else:
215            setter = lambda o, v: setattr(o, attr, v)
216        return getter, setter
217
218    def _new(self, lazy_collection):
219        creator = self.creator and self.creator or self.target_class
220        self.collection_class = util.duck_type_collection(lazy_collection())
221
222        if self.proxy_factory:
223            return self.proxy_factory(lazy_collection, creator, self.value_attr)
224
225        if self.getset_factory:
226            getter, setter = self.getset_factory(self.collection_class, self)
227        else:
228            getter, setter = self._default_getset(self.collection_class)
229       
230        if self.collection_class is list:
231            return _AssociationList(lazy_collection, creator, getter, setter, self)
232        elif self.collection_class is dict:
233            return _AssociationDict(lazy_collection, creator, getter, setter, self)
234        elif self.collection_class is set:
235            return _AssociationSet(lazy_collection, creator, getter, setter, self)
236        else:
237            raise exceptions.ArgumentError(
238                'could not guess which interface to use for '
239                'collection_class "%s" backing "%s"; specify a '
240                'proxy_factory and proxy_bulk_set manually' %
241                (self.collection_class.__name__, self.target_collection))
242
243    def _inflate(self, proxy):
244        creator = self.creator and self.creator or self.target_class
245
246        if self.getset_factory:
247            getter, setter = self.getset_factory(self.collection_class, self)
248        else:
249            getter, setter = self._default_getset(self.collection_class)
250       
251        proxy.creator = creator
252        proxy.getter = getter
253        proxy.setter = setter
254
255    def _set(self, proxy, values):
256        if self.proxy_bulk_set:
257            self.proxy_bulk_set(proxy, values)
258        elif self.collection_class is list:
259            proxy.extend(values)
260        elif self.collection_class is dict:
261            proxy.update(values)
262        elif self.collection_class is set:
263            proxy.update(values)
264        else:
265            raise exceptions.ArgumentError(
266               'no proxy_bulk_set supplied for custom '
267               'collection_class implementation')
268
269class _lazy_collection(object):
270    def __init__(self, obj, target):
271        self.ref = weakref.ref(obj)
272        self.target = target
273
274    def __call__(self):
275        obj = self.ref()
276        if obj is None:
277            raise exceptions.InvalidRequestError(
278               "stale association proxy, parent object has gone out of "
279               "scope")
280        return getattr(obj, self.target)
281
282    def __getstate__(self):
283        return {'obj':self.ref(), 'target':self.target}
284   
285    def __setstate__(self, state):
286        self.ref = weakref.ref(state['obj'])
287        self.target = state['target']
288
289class _AssociationCollection(object):
290    def __init__(self, lazy_collection, creator, getter, setter, parent):
291        """Constructs an _AssociationCollection. 
292       
293        This will always be a subclass of either _AssociationList,
294        _AssociationSet, or _AssociationDict.
295
296        lazy_collection
297          A callable returning a list-based collection of entities (usually an
298          object attribute managed by a SQLAlchemy relation())
299
300        creator
301          A function that creates new target entities.  Given one parameter:
302          value.  This assertion is assumed::
303
304            obj = creator(somevalue)
305            assert getter(obj) == somevalue
306
307        getter
308          A function.  Given an associated object, return the 'value'.
309
310        setter
311          A function.  Given an associated object and a value, store that
312          value on the object.
313
314        """
315        self.lazy_collection = lazy_collection
316        self.creator = creator
317        self.getter = getter
318        self.setter = setter
319        self.parent = parent
320
321    col = property(lambda self: self.lazy_collection())
322
323    def __len__(self):
324        return len(self.col)
325
326    def __nonzero__(self):
327        return bool(self.col)
328
329    def __getstate__(self):
330        return {'parent':self.parent, 'lazy_collection':self.lazy_collection}
331
332    def __setstate__(self, state):
333        self.parent = state['parent']
334        self.lazy_collection = state['lazy_collection']
335        self.parent._inflate(self)
336   
337class _AssociationList(_AssociationCollection):
338    """Generic, converting, list-to-list proxy."""
339
340    def _create(self, value):
341        return self.creator(value)
342
343    def _get(self, object):
344        return self.getter(object)
345
346    def _set(self, object, value):
347        return self.setter(object, value)
348
349    def __getitem__(self, index):
350        return self._get(self.col[index])
351
352    def __setitem__(self, index, value):
353        if not isinstance(index, slice):
354            self._set(self.col[index], value)
355        else:
356            if index.stop is None:
357                stop = len(self)
358            elif index.stop < 0:
359                stop = len(self) + index.stop
360            else:
361                stop = index.stop
362            step = index.step or 1
363
364            rng = range(index.start or 0, stop, step)
365            if step == 1:
366                for i in rng:
367                    del self[index.start]
368                i = index.start
369                for item in value:
370                    self.insert(i, item)
371                    i += 1
372            else:
373                if len(value) != len(rng):
374                    raise ValueError(
375                        "attempt to assign sequence of size %s to "
376                        "extended slice of size %s" % (len(value),
377                                                       len(rng)))
378                for i, item in zip(rng, value):
379                    self._set(self.col[i], item)
380
381    def __delitem__(self, index):
382        del self.col[index]
383
384    def __contains__(self, value):
385        for member in self.col:
386            # testlib.pragma exempt:__eq__
387            if self._get(member) == value:
388                return True
389        return False
390
391    def __getslice__(self, start, end):
392        return [self._get(member) for member in self.col[start:end]]
393
394    def __setslice__(self, start, end, values):
395        members = [self._create(v) for v in values]
396        self.col[start:end] = members
397
398    def __delslice__(self, start, end):
399        del self.col[start:end]
400
401    def __iter__(self):
402        """Iterate over proxied values.
403
404        For the actual domain objects, iterate over .col instead or
405        just use the underlying collection directly from its property
406        on the parent.
407        """
408
409        for member in self.col:
410            yield self._get(member)
411        raise StopIteration
412
413    def append(self, value):
414        item = self._create(value)
415        self.col.append(item)
416
417    def count(self, value):
418        return sum([1 for _ in
419                    itertools.ifilter(lambda v: v == value, iter(self))])
420
421    def extend(self, values):
422        for v in values:
423            self.append(v)
424
425    def insert(self, index, value):
426        self.col[index:index] = [self._create(value)]
427
428    def pop(self, index=-1):
429        return self.getter(self.col.pop(index))
430
431    def remove(self, value):
432        for i, val in enumerate(self):
433            if val == value:
434                del self.col[i]
435                return
436        raise ValueError("value not in list")
437
438    def reverse(self):
439        """Not supported, use reversed(mylist)"""
440
441        raise NotImplementedError
442
443    def sort(self):
444        """Not supported, use sorted(mylist)"""
445
446        raise NotImplementedError
447
448    def clear(self):
449        del self.col[0:len(self.col)]
450
451    def __eq__(self, other):
452        return list(self) == other
453
454    def __ne__(self, other):
455        return list(self) != other
456
457    def __lt__(self, other):
458        return list(self) < other
459
460    def __le__(self, other):
461        return list(self) <= other
462
463    def __gt__(self, other):
464        return list(self) > other
465
466    def __ge__(self, other):
467        return list(self) >= other
468
469    def __cmp__(self, other):
470        return cmp(list(self), other)
471
472    def __add__(self, iterable):
473        try:
474            other = list(iterable)
475        except TypeError:
476            return NotImplemented
477        return list(self) + other
478
479    def __radd__(self, iterable):
480        try:
481            other = list(iterable)
482        except TypeError:
483            return NotImplemented
484        return other + list(self)
485
486    def __mul__(self, n):
487        if not isinstance(n, int):
488            return NotImplemented
489        return list(self) * n
490    __rmul__ = __mul__
491
492    def __iadd__(self, iterable):
493        self.extend(iterable)
494        return self
495
496    def __imul__(self, n):
497        # unlike a regular list *=, proxied __imul__ will generate unique
498        # backing objects for each copy.  *= on proxied lists is a bit of
499        # a stretch anyhow, and this interpretation of the __imul__ contract
500        # is more plausibly useful than copying the backing objects.
501        if not isinstance(n, int):
502            return NotImplemented
503        if n == 0:
504            self.clear()
505        elif n > 1:
506            self.extend(list(self) * (n - 1))
507        return self
508
509    def copy(self):
510        return list(self)
511
512    def __repr__(self):
513        return repr(list(self))
514
515    def __hash__(self):
516        raise TypeError("%s objects are unhashable" % type(self).__name__)
517
518    for func_name, func in locals().items():
519        if (util.callable(func) and func.func_name == func_name and
520            not func.__doc__ and hasattr(list, func_name)):
521            func.__doc__ = getattr(list, func_name).__doc__
522    del func_name, func
523
524
525_NotProvided = util.symbol('_NotProvided')
526class _AssociationDict(_AssociationCollection):
527    """Generic, converting, dict-to-dict proxy."""
528
529    def _create(self, key, value):
530        return self.creator(key, value)
531
532    def _get(self, object):
533        return self.getter(object)
534
535    def _set(self, object, key, value):
536        return self.setter(object, key, value)
537
538    def __getitem__(self, key):
539        return self._get(self.col[key])
540
541    def __setitem__(self, key, value):
542        if key in self.col:
543            self._set(self.col[key], key, value)
544        else:
545            self.col[key] = self._create(key, value)
546
547    def __delitem__(self, key):
548        del self.col[key]
549
550    def __contains__(self, key):
551        # testlib.pragma exempt:__hash__
552        return key in self.col
553
554    def has_key(self, key):
555        # testlib.pragma exempt:__hash__
556        return key in self.col
557
558    def __iter__(self):
559        return self.col.iterkeys()
560
561    def clear(self):
562        self.col.clear()
563
564    def __eq__(self, other):
565        return dict(self) == other
566
567    def __ne__(self, other):
568        return dict(self) != other
569
570    def __lt__(self, other):
571        return dict(self) < other
572
573    def __le__(self, other):
574        return dict(self) <= other
575
576    def __gt__(self, other):
577        return dict(self) > other
578
579    def __ge__(self, other):
580        return dict(self) >= other
581
582    def __cmp__(self, other):
583        return cmp(dict(self), other)
584
585    def __repr__(self):
586        return repr(dict(self.items()))
587
588    def get(self, key, default=None):
589        try:
590            return self[key]
591        except KeyError:
592            return default
593
594    def setdefault(self, key, default=None):
595        if key not in self.col:
596            self.col[key] = self._create(key, default)
597            return default
598        else:
599            return self[key]
600
601    def keys(self):
602        return self.col.keys()
603
604    def iterkeys(self):
605        return self.col.iterkeys()
606
607    def values(self):
608        return [ self._get(member) for member in self.col.values() ]
609
610    def itervalues(self):
611        for key in self.col:
612            yield self._get(self.col[key])
613        raise StopIteration
614
615    def items(self):
616        return [(k, self._get(self.col[k])) for k in self]
617
618    def iteritems(self):
619        for key in self.col:
620            yield (key, self._get(self.col[key]))
621        raise StopIteration
622
623    def pop(self, key, default=_NotProvided):
624        if default is _NotProvided:
625            member = self.col.pop(key)
626        else:
627            member = self.col.pop(key, default)
628        return self._get(member)
629
630    def popitem(self):
631        item = self.col.popitem()
632        return (item[0], self._get(item[1]))
633
634    def update(self, *a, **kw):
635        if len(a) > 1:
636            raise TypeError('update expected at most 1 arguments, got %i' %
637                            len(a))
638        elif len(a) == 1:
639            seq_or_map = a[0]
640            for item in seq_or_map:
641                if isinstance(item, tuple):
642                    self[item[0]] = item[1]
643                else:
644                    self[item] = seq_or_map[item]
645
646        for key, value in kw:
647            self[key] = value
648
649    def copy(self):
650        return dict(self.items())
651
652    def __hash__(self):
653        raise TypeError("%s objects are unhashable" % type(self).__name__)
654
655    for func_name, func in locals().items():
656        if (util.callable(func) and func.func_name == func_name and
657            not func.__doc__ and hasattr(dict, func_name)):
658            func.__doc__ = getattr(dict, func_name).__doc__
659    del func_name, func
660
661
662class _AssociationSet(_AssociationCollection):
663    """Generic, converting, set-to-set proxy."""
664
665    def _create(self, value):
666        return self.creator(value)
667
668    def _get(self, object):
669        return self.getter(object)
670
671    def _set(self, object, value):
672        return self.setter(object, value)
673
674    def __len__(self):
675        return len(self.col)
676
677    def __nonzero__(self):
678        if self.col:
679            return True
680        else:
681            return False
682
683    def __contains__(self, value):
684        for member in self.col:
685            # testlib.pragma exempt:__eq__
686            if self._get(member) == value:
687                return True
688        return False
689
690    def __iter__(self):
691        """Iterate over proxied values.
692
693        For the actual domain objects, iterate over .col instead or just use
694        the underlying collection directly from its property on the parent.
695
696        """
697        for member in self.col:
698            yield self._get(member)
699        raise StopIteration
700
701    def add(self, value):
702        if value not in self:
703            self.col.add(self._create(value))
704
705    # for discard and remove, choosing a more expensive check strategy rather
706    # than call self.creator()
707    def discard(self, value):
708        for member in self.col:
709            if self._get(member) == value:
710                self.col.discard(member)
711                break
712
713    def remove(self, value):
714        for member in self.col:
715            if self._get(member) == value:
716                self.col.discard(member)
717                return
718        raise KeyError(value)
719
720    def pop(self):
721        if not self.col:
722            raise KeyError('pop from an empty set')
723        member = self.col.pop()
724        return self._get(member)
725
726    def update(self, other):
727        for value in other:
728            self.add(value)
729
730    def __ior__(self, other):
731        if not collections._set_binops_check_strict(self, other):
732            return NotImplemented
733        for value in other:
734            self.add(value)
735        return self
736
737    def _set(self):
738        return set(iter(self))
739
740    def union(self, other):
741        return set(self).union(other)
742
743    __or__ = union
744
745    def difference(self, other):
746        return set(self).difference(other)
747
748    __sub__ = difference
749
750    def difference_update(self, other):
751        for value in other:
752            self.discard(value)
753
754    def __isub__(self, other):
755        if not collections._set_binops_check_strict(self, other):
756            return NotImplemented
757        for value in other:
758            self.discard(value)
759        return self
760
761    def intersection(self, other):
762        return set(self).intersection(other)
763
764    __and__ = intersection
765
766    def intersection_update(self, other):
767        want, have = self.intersection(other), set(self)
768
769        remove, add = have - want, want - have
770
771        for value in remove:
772            self.remove(value)
773        for value in add:
774            self.add(value)
775
776    def __iand__(self, other):
777        if not collections._set_binops_check_strict(self, other):
778            return NotImplemented
779        want, have = self.intersection(other), set(self)
780
781        remove, add = have - want, want - have
782
783        for value in remove:
784            self.remove(value)
785        for value in add:
786            self.add(value)
787        return self
788
789    def symmetric_difference(self, other):
790        return set(self).symmetric_difference(other)
791
792    __xor__ = symmetric_difference
793
794    def symmetric_difference_update(self, other):
795        want, have = self.symmetric_difference(other), set(self)
796
797        remove, add = have - want, want - have
798
799        for value in remove:
800            self.remove(value)
801        for value in add:
802            self.add(value)
803
804    def __ixor__(self, other):
805        if not collections._set_binops_check_strict(self, other):
806            return NotImplemented
807        want, have = self.symmetric_difference(other), set(self)
808
809        remove, add = have - want, want - have
810
811        for value in remove:
812            self.remove(value)
813        for value in add:
814            self.add(value)
815        return self
816
817    def issubset(self, other):
818        return set(self).issubset(other)
819
820    def issuperset(self, other):
821        return set(self).issuperset(other)
822
823    def clear(self):
824        self.col.clear()
825
826    def copy(self):
827        return set(self)
828
829    def __eq__(self, other):
830        return set(self) == other
831
832    def __ne__(self, other):
833        return set(self) != other
834
835    def __lt__(self, other):
836        return set(self) < other
837
838    def __le__(self, other):
839        return set(self) <= other
840
841    def __gt__(self, other):
842        return set(self) > other
843
844    def __ge__(self, other):
845        return set(self) >= other
846
847    def __repr__(self):
848        return repr(set(self))
849
850    def __hash__(self):
851        raise TypeError("%s objects are unhashable" % type(self).__name__)
852
853    for func_name, func in locals().items():
854        if (util.callable(func) and func.func_name == func_name and
855            not func.__doc__ and hasattr(set, func_name)):
856            func.__doc__ = getattr(set, func_name).__doc__
857    del func_name, func
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。