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

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

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

行番号 
1# attributes.py - manages object attributes
2# Copyright (C) 2005, 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com
3#
4# This module is part of SQLAlchemy and is released under
5# the MIT License: http://www.opensource.org/licenses/mit-license.php
6"""Defines SQLAlchemy's system of class instrumentation..
7
8This module is usually not directly visible to user applications, but
9defines a large part of the ORM's interactivity.
10
11SQLA's instrumentation system is completely customizable, in which
12case an understanding of the general mechanics of this module is helpful.
13An example of full customization is in /examples/custom_attributes.
14
15"""
16
17import operator
18from operator import attrgetter, itemgetter
19import types
20import weakref
21
22from sqlalchemy import util
23from sqlalchemy.orm import interfaces, collections, exc
24import sqlalchemy.exceptions as sa_exc
25
26# lazy imports
27_entity_info = None
28identity_equal = None
29state = None
30
31PASSIVE_NORESULT = util.symbol('PASSIVE_NORESULT')
32ATTR_WAS_SET = util.symbol('ATTR_WAS_SET')
33NO_VALUE = util.symbol('NO_VALUE')
34NEVER_SET = util.symbol('NEVER_SET')
35
36# "passive" get settings
37# TODO: the True/False values need to be factored out
38# of the rest of ORM code
39# don't fire off any callables, and don't initialize the attribute to
40# an empty value
41PASSIVE_NO_INITIALIZE = True #util.symbol('PASSIVE_NO_INITIALIZE')
42
43# don't fire off any callables, but if no callables present
44# then initialize to an empty value/collection
45# this is used by backrefs.
46PASSIVE_NO_CALLABLES = util.symbol('PASSIVE_NO_CALLABLES')
47
48# fire callables/initialize as needed
49PASSIVE_OFF = False #util.symbol('PASSIVE_OFF')
50
51INSTRUMENTATION_MANAGER = '__sa_instrumentation_manager__'
52"""Attribute, elects custom instrumentation when present on a mapped class.
53
54Allows a class to specify a slightly or wildly different technique for
55tracking changes made to mapped attributes and collections.
56
57Only one instrumentation implementation is allowed in a given object
58inheritance hierarchy.
59
60The value of this attribute must be a callable and will be passed a class
61object.  The callable must return one of:
62
63  - An instance of an interfaces.InstrumentationManager or subclass
64  - An object implementing all or some of InstrumentationManager (TODO)
65  - A dictionary of callables, implementing all or some of the above (TODO)
66  - An instance of a ClassManager or subclass
67
68interfaces.InstrumentationManager is public API and will remain stable
69between releases.  ClassManager is not public and no guarantees are made
70about stability.  Caveat emptor.
71
72This attribute is consulted by the default SQLAlchemy instrumentation
73resolution code.  If custom finders are installed in the global
74instrumentation_finders list, they may or may not choose to honor this
75attribute.
76
77"""
78
79instrumentation_finders = []
80"""An extensible sequence of instrumentation implementation finding callables.
81
82Finders callables will be passed a class object.  If None is returned, the
83next finder in the sequence is consulted.  Otherwise the return must be an
84instrumentation factory that follows the same guidelines as
85INSTRUMENTATION_MANAGER.
86
87By default, the only finder is find_native_user_instrumentation_hook, which
88searches for INSTRUMENTATION_MANAGER.  If all finders return None, standard
89ClassManager instrumentation is used.
90
91"""
92
93class QueryableAttribute(interfaces.PropComparator):
94
95    def __init__(self, key, impl=None, comparator=None, parententity=None):
96        """Construct an InstrumentedAttribute.
97
98          comparator
99            a sql.Comparator to which class-level compare/math events will be sent
100        """
101        self.key = key
102        self.impl = impl
103        self.comparator = comparator
104        self.parententity = parententity
105
106    def get_history(self, instance, **kwargs):
107        return self.impl.get_history(instance_state(instance), instance_dict(instance), **kwargs)
108
109    def __selectable__(self):
110        # TODO: conditionally attach this method based on clause_element ?
111        return self
112
113    def __clause_element__(self):
114        return self.comparator.__clause_element__()
115
116    def label(self, name):
117        return self.__clause_element__().label(name)
118
119    def operate(self, op, *other, **kwargs):
120        return op(self.comparator, *other, **kwargs)
121
122    def reverse_operate(self, op, other, **kwargs):
123        return op(other, self.comparator, **kwargs)
124
125    def hasparent(self, state, optimistic=False):
126        return self.impl.hasparent(state, optimistic=optimistic)
127   
128    def __getattr__(self, key):
129        try:
130            return getattr(self.comparator, key)
131        except AttributeError:
132            raise AttributeError('Neither %r object nor %r object has an attribute %r' % (
133                    type(self).__name__,
134                    type(self.comparator).__name__,
135                    key)
136            )
137       
138    def __str__(self):
139        return repr(self.parententity) + "." + self.property.key
140
141    @property
142    def property(self):
143        return self.comparator.property
144
145
146class InstrumentedAttribute(QueryableAttribute):
147    """Public-facing descriptor, placed in the mapped class dictionary."""
148
149    def __set__(self, instance, value):
150        self.impl.set(instance_state(instance), instance_dict(instance), value, None)
151
152    def __delete__(self, instance):
153        self.impl.delete(instance_state(instance), instance_dict(instance))
154
155    def __get__(self, instance, owner):
156        if instance is None:
157            return self
158        return self.impl.get(instance_state(instance), instance_dict(instance))
159
160class _ProxyImpl(object):
161    accepts_scalar_loader = False
162    dont_expire_missing = False
163   
164    def __init__(self, key):
165        self.key = key
166
167def proxied_attribute_factory(descriptor):
168    """Create an InstrumentedAttribute / user descriptor hybrid.
169
170    Returns a new InstrumentedAttribute type that delegates descriptor
171    behavior and getattr() to the given descriptor.
172    """
173
174    class Proxy(InstrumentedAttribute):
175        """A combination of InsturmentedAttribute and a regular descriptor."""
176
177        def __init__(self, key, descriptor, comparator, parententity):
178            self.key = key
179            # maintain ProxiedAttribute.user_prop compatability.
180            self.descriptor = self.user_prop = descriptor
181            self._comparator = comparator
182            self._parententity = parententity
183            self.impl = _ProxyImpl(key)
184
185        @util.memoized_property
186        def comparator(self):
187            if util.callable(self._comparator):
188                self._comparator = self._comparator()
189            return self._comparator
190
191        def __get__(self, instance, owner):
192            """Delegate __get__ to the original descriptor."""
193            if instance is None:
194                descriptor.__get__(instance, owner)
195                return self
196            return descriptor.__get__(instance, owner)
197
198        def __set__(self, instance, value):
199            """Delegate __set__ to the original descriptor."""
200            return descriptor.__set__(instance, value)
201
202        def __delete__(self, instance):
203            """Delegate __delete__ to the original descriptor."""
204            return descriptor.__delete__(instance)
205
206        def __getattr__(self, attribute):
207            """Delegate __getattr__ to the original descriptor and/or comparator."""
208           
209            try:
210                return getattr(descriptor, attribute)
211            except AttributeError:
212                try:
213                    return getattr(self._comparator, attribute)
214                except AttributeError:
215                    raise AttributeError('Neither %r object nor %r object has an attribute %r' % (
216                            type(descriptor).__name__,
217                            type(self._comparator).__name__,
218                            attribute)
219                    )
220
221    Proxy.__name__ = type(descriptor).__name__ + 'Proxy'
222
223    util.monkeypatch_proxied_specials(Proxy, type(descriptor),
224                                      name='descriptor',
225                                      from_instance=descriptor)
226    return Proxy
227
228class AttributeImpl(object):
229    """internal implementation for instrumented attributes."""
230
231    def __init__(self, class_, key,
232                    callable_, trackparent=False, extension=None,
233                    compare_function=None, active_history=False, parent_token=None,
234                    dont_expire_missing=False,
235                    **kwargs):
236        """Construct an AttributeImpl.
237
238        \class_
239          associated class
240         
241        key
242          string name of the attribute
243
244        \callable_
245          optional function which generates a callable based on a parent
246          instance, which produces the "default" values for a scalar or
247          collection attribute when it's first accessed, if not present
248          already.
249
250        trackparent
251          if True, attempt to track if an instance has a parent attached
252          to it via this attribute.
253
254        extension
255          a single or list of AttributeExtension object(s) which will
256          receive set/delete/append/remove/etc. events.
257
258        compare_function
259          a function that compares two values which are normally
260          assignable to this attribute.
261
262        active_history
263          indicates that get_history() should always return the "old" value,
264          even if it means executing a lazy callable upon attribute change.
265
266        parent_token
267          Usually references the MapperProperty, used as a key for
268          the hasparent() function to identify an "owning" attribute.
269          Allows multiple AttributeImpls to all match a single
270          owner attribute.
271         
272        dont_expire_missing
273          if True, don't add an "expiry" callable to this attribute
274          during state.expire_attributes(None), if no value is present
275          for this key.
276         
277        """
278        self.class_ = class_
279        self.key = key
280        self.callable_ = callable_
281        self.trackparent = trackparent
282        self.parent_token = parent_token or self
283        if compare_function is None:
284            self.is_equal = operator.eq
285        else:
286            self.is_equal = compare_function
287        self.extensions = util.to_list(extension or [])
288        for e in self.extensions:
289            if e.active_history:
290                active_history = True
291                break
292        self.active_history = active_history
293        self.dont_expire_missing = dont_expire_missing
294       
295    def hasparent(self, state, optimistic=False):
296        """Return the boolean value of a `hasparent` flag attached to the given item.
297
298        The `optimistic` flag determines what the default return value
299        should be if no `hasparent` flag can be located.
300
301        As this function is used to determine if an instance is an
302        *orphan*, instances that were loaded from storage should be
303        assumed to not be orphans, until a True/False value for this
304        flag is set.
305
306        An instance attribute that is loaded by a callable function
307        will also not have a `hasparent` flag.
308
309        """
310        return state.parents.get(id(self.parent_token), optimistic)
311
312    def sethasparent(self, state, value):
313        """Set a boolean flag on the given item corresponding to
314        whether or not it is attached to a parent object via the
315        attribute represented by this ``InstrumentedAttribute``.
316
317        """
318        state.parents[id(self.parent_token)] = value
319
320    def set_callable(self, state, callable_):
321        """Set a callable function for this attribute on the given object.
322
323        This callable will be executed when the attribute is next
324        accessed, and is assumed to construct part of the instances
325        previously stored state. When its value or values are loaded,
326        they will be established as part of the instance's *committed
327        state*.  While *trackparent* information will be assembled for
328        these instances, attribute-level event handlers will not be
329        fired.
330
331        The callable overrides the class level callable set in the
332        ``InstrumentedAttribute`` constructor.
333
334        """
335        if callable_ is None:
336            self.initialize(state)
337        else:
338            state.callables[self.key] = callable_
339
340    def get_history(self, state, dict_, passive=PASSIVE_OFF):
341        raise NotImplementedError()
342
343    def _get_callable(self, state):
344        if self.key in state.callables:
345            return state.callables[self.key]
346        elif self.callable_ is not None:
347            return self.callable_(state)
348        else:
349            return None
350
351    def initialize(self, state, dict_):
352        """Initialize this attribute on the given object instance with an empty value."""
353
354        dict_[self.key] = None
355        return None
356
357    def get(self, state, dict_, passive=PASSIVE_OFF):
358        """Retrieve a value from the given object.
359
360        If a callable is assembled on this object's attribute, and
361        passive is False, the callable will be executed and the
362        resulting value will be set as the new value for this attribute.
363        """
364
365        try:
366            return dict_[self.key]
367        except KeyError:
368            # if no history, check for lazy callables, etc.
369            if state.committed_state.get(self.key, NEVER_SET) is NEVER_SET:
370                if passive is PASSIVE_NO_INITIALIZE:
371                    return PASSIVE_NORESULT
372                   
373                callable_ = self._get_callable(state)
374                if callable_ is not None:
375                    if passive is not PASSIVE_OFF:
376                        return PASSIVE_NORESULT
377                    value = callable_()
378                    if value is not ATTR_WAS_SET:
379                        return self.set_committed_value(state, dict_, value)
380                    else:
381                        if self.key not in dict_:
382                            return self.get(state, dict_, passive=passive)
383                        return dict_[self.key]
384
385            # Return a new, empty value
386            return self.initialize(state, dict_)
387
388    def append(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
389        self.set(state, dict_, value, initiator, passive=passive)
390
391    def remove(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
392        self.set(state, dict_, None, initiator, passive=passive)
393
394    def set(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
395        raise NotImplementedError()
396
397    def get_committed_value(self, state, dict_, passive=PASSIVE_OFF):
398        """return the unchanged value of this attribute"""
399
400        if self.key in state.committed_state:
401            if state.committed_state[self.key] is NO_VALUE:
402                return None
403            else:
404                return state.committed_state.get(self.key)
405        else:
406            return self.get(state, dict_, passive=passive)
407
408    def set_committed_value(self, state, dict_, value):
409        """set an attribute value on the given instance and 'commit' it."""
410
411        state.commit(dict_, [self.key])
412
413        state.callables.pop(self.key, None)
414        state.dict[self.key] = value
415
416        return value
417
418class ScalarAttributeImpl(AttributeImpl):
419    """represents a scalar value-holding InstrumentedAttribute."""
420
421    accepts_scalar_loader = True
422    uses_objects = False
423
424    def delete(self, state, dict_):
425
426        # TODO: catch key errors, convert to attributeerror?
427        if self.active_history:
428            old = self.get(state, dict_)
429        else:
430            old = dict_.get(self.key, NO_VALUE)
431
432        state.modified_event(dict_, self, False, old)
433
434        if self.extensions:
435            self.fire_remove_event(state, dict_, old, None)
436        del dict_[self.key]
437
438    def get_history(self, state, dict_, passive=PASSIVE_OFF):
439        return History.from_attribute(
440            self, state, dict_.get(self.key, NO_VALUE))
441
442    def set(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
443        if initiator is self:
444            return
445
446        if self.active_history:
447            old = self.get(state, dict_)
448        else:
449            old = dict_.get(self.key, NO_VALUE)
450
451        state.modified_event(dict_, self, False, old)
452
453        if self.extensions:
454            value = self.fire_replace_event(state, dict_, value, old, initiator)
455        dict_[self.key] = value
456
457    def fire_replace_event(self, state, dict_, value, previous, initiator):
458        for ext in self.extensions:
459            value = ext.set(state, value, previous, initiator or self)
460        return value
461
462    def fire_remove_event(self, state, dict_, value, initiator):
463        for ext in self.extensions:
464            ext.remove(state, value, initiator or self)
465
466    @property
467    def type(self):
468        self.property.columns[0].type
469
470
471class MutableScalarAttributeImpl(ScalarAttributeImpl):
472    """represents a scalar value-holding InstrumentedAttribute, which can detect
473    changes within the value itself.
474    """
475
476    uses_objects = False
477
478    def __init__(self, class_, key, callable_,
479                    class_manager, copy_function=None,
480                    compare_function=None, **kwargs):
481        super(ScalarAttributeImpl, self).__init__(class_, key, callable_,
482                                compare_function=compare_function, **kwargs)
483        class_manager.mutable_attributes.add(key)
484        if copy_function is None:
485            raise sa_exc.ArgumentError("MutableScalarAttributeImpl requires a copy function")
486        self.copy = copy_function
487
488    def get_history(self, state, dict_, passive=PASSIVE_OFF):
489        if not dict_:
490            v = state.committed_state.get(self.key, NO_VALUE)
491        else:
492            v = dict_.get(self.key, NO_VALUE)
493           
494        return History.from_attribute(
495            self, state, v)
496
497    def commit_to_state(self, state, dict_, dest):
498        dest[self.key] = self.copy(dict_[self.key])
499
500    def check_mutable_modified(self, state, dict_):
501        (added, unchanged, deleted) = self.get_history(state, dict_, passive=PASSIVE_NO_INITIALIZE)
502        return bool(added or deleted)
503
504    def get(self, state, dict_, passive=PASSIVE_OFF):
505        if self.key not in state.mutable_dict:
506            ret = ScalarAttributeImpl.get(self, state, dict_, passive=passive)
507            if ret is not PASSIVE_NORESULT:
508                state.mutable_dict[self.key] = ret
509            return ret
510        else:
511            return state.mutable_dict[self.key]
512
513    def delete(self, state, dict_):
514        ScalarAttributeImpl.delete(self, state, dict_)
515        state.mutable_dict.pop(self.key)
516
517    def set(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
518        if initiator is self:
519            return
520
521        state.modified_event(dict_, self, True, NEVER_SET)
522       
523        if self.extensions:
524            old = self.get(state, dict_)
525            value = self.fire_replace_event(state, dict_, value, old, initiator)
526            dict_[self.key] = value
527        else:
528            dict_[self.key] = value
529        state.mutable_dict[self.key] = value
530
531
532class ScalarObjectAttributeImpl(ScalarAttributeImpl):
533    """represents a scalar-holding InstrumentedAttribute, where the target object is also instrumented.
534
535    Adds events to delete/set operations.
536    """
537
538    accepts_scalar_loader = False
539    uses_objects = True
540
541    def __init__(self, class_, key, callable_,
542                    trackparent=False, extension=None, copy_function=None,
543                    compare_function=None, **kwargs):
544        super(ScalarObjectAttributeImpl, self).__init__(class_, key,
545          callable_, trackparent=trackparent, extension=extension,
546          compare_function=compare_function, **kwargs)
547        if compare_function is None:
548            self.is_equal = identity_equal
549
550    def delete(self, state, dict_):
551        old = self.get(state, dict_)
552        self.fire_remove_event(state, dict_, old, self)
553        del dict_[self.key]
554
555    def get_history(self, state, dict_, passive=PASSIVE_OFF):
556        if self.key in dict_:
557            return History.from_attribute(self, state, dict_[self.key])
558        else:
559            current = self.get(state, dict_, passive=passive)
560            if current is PASSIVE_NORESULT:
561                return HISTORY_BLANK
562            else:
563                return History.from_attribute(self, state, current)
564
565    def set(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
566        """Set a value on the given InstanceState.
567
568        `initiator` is the ``InstrumentedAttribute`` that initiated the
569        ``set()`` operation and is used to control the depth of a circular
570        setter operation.
571
572        """
573        if initiator is self:
574            return
575       
576        if self.active_history:
577            old = self.get(state, dict_)
578        else:
579            # this would be the "laziest" approach,
580            # however it breaks currently expected backref
581            # behavior
582            #old = dict_.get(self.key, None)
583            # instead, use the "passive" setting, which
584            # is only going to be PASSIVE_NOCALLABLES if it
585            # came from a backref
586            old = self.get(state, dict_, passive=passive)
587            if old is PASSIVE_NORESULT:
588                old = None
589             
590        value = self.fire_replace_event(state, dict_, value, old, initiator)
591        dict_[self.key] = value
592
593    def fire_remove_event(self, state, dict_, value, initiator):
594        state.modified_event(dict_, self, False, value)
595
596        if self.trackparent and value is not None:
597            self.sethasparent(instance_state(value), False)
598
599        for ext in self.extensions:
600            ext.remove(state, value, initiator or self)
601
602    def fire_replace_event(self, state, dict_, value, previous, initiator):
603        state.modified_event(dict_, self, False, previous)
604
605        if self.trackparent:
606            if previous is not value and previous is not None:
607                self.sethasparent(instance_state(previous), False)
608
609        for ext in self.extensions:
610            value = ext.set(state, value, previous, initiator or self)
611
612        if self.trackparent:
613            if value is not None:
614                self.sethasparent(instance_state(value), True)
615
616        return value
617
618
619class CollectionAttributeImpl(AttributeImpl):
620    """A collection-holding attribute that instruments changes in membership.
621
622    Only handles collections of instrumented objects.
623
624    InstrumentedCollectionAttribute holds an arbitrary, user-specified
625    container object (defaulting to a list) and brokers access to the
626    CollectionAdapter, a "view" onto that object that presents consistent
627    bag semantics to the orm layer independent of the user data implementation.
628
629    """
630    accepts_scalar_loader = False
631    uses_objects = True
632
633    def __init__(self, class_, key, callable_,
634                    typecallable=None, trackparent=False, extension=None,
635                    copy_function=None, compare_function=None, **kwargs):
636        super(CollectionAttributeImpl, self).__init__(class_, key, callable_, trackparent=trackparent,
637          extension=extension, compare_function=compare_function, **kwargs)
638
639        if copy_function is None:
640            copy_function = self.__copy
641        self.copy = copy_function
642
643        self.collection_factory = typecallable
644        # may be removed in 0.5:
645        self.collection_interface = \
646          util.duck_type_collection(self.collection_factory())
647
648    def __copy(self, item):
649        return [y for y in list(collections.collection_adapter(item))]
650
651    def get_history(self, state, dict_, passive=PASSIVE_OFF):
652        current = self.get(state, dict_, passive=passive)
653        if current is PASSIVE_NORESULT:
654            return HISTORY_BLANK
655        else:
656            return History.from_attribute(self, state, current)
657
658    def fire_append_event(self, state, dict_, value, initiator):
659        state.modified_event(dict_, self, True, NEVER_SET, passive=PASSIVE_NO_INITIALIZE)
660
661        for ext in self.extensions:
662            value = ext.append(state, value, initiator or self)
663
664        if self.trackparent and value is not None:
665            self.sethasparent(instance_state(value), True)
666
667        return value
668
669    def fire_pre_remove_event(self, state, dict_, initiator):
670        state.modified_event(dict_, self, True, NEVER_SET, passive=PASSIVE_NO_INITIALIZE)
671
672    def fire_remove_event(self, state, dict_, value, initiator):
673        state.modified_event(dict_, self, True, NEVER_SET, passive=PASSIVE_NO_INITIALIZE)
674
675        if self.trackparent and value is not None:
676            self.sethasparent(instance_state(value), False)
677
678        for ext in self.extensions:
679            ext.remove(state, value, initiator or self)
680
681    def delete(self, state, dict_):
682        if self.key not in dict_:
683            return
684
685        state.modified_event(dict_, self, True, NEVER_SET)
686
687        collection = self.get_collection(state, state.dict)
688        collection.clear_with_event()
689        # TODO: catch key errors, convert to attributeerror?
690        del dict_[self.key]
691
692    def initialize(self, state, dict_):
693        """Initialize this attribute with an empty collection."""
694
695        _, user_data = self._initialize_collection(state)
696        dict_[self.key] = user_data
697        return user_data
698
699    def _initialize_collection(self, state):
700        return state.manager.initialize_collection(
701            self.key, state, self.collection_factory)
702
703    def append(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
704        if initiator is self:
705            return
706
707        collection = self.get_collection(state, dict_, passive=passive)
708        if collection is PASSIVE_NORESULT:
709            value = self.fire_append_event(state, dict_, value, initiator)
710            assert self.key not in dict_, "Collection was loaded during event handling."
711            state.get_pending(self.key).append(value)
712        else:
713            collection.append_with_event(value, initiator)
714
715    def remove(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
716        if initiator is self:
717            return
718
719        collection = self.get_collection(state, state.dict, passive=passive)
720        if collection is PASSIVE_NORESULT:
721            self.fire_remove_event(state, dict_, value, initiator)
722            assert self.key not in dict_, "Collection was loaded during event handling."
723            state.get_pending(self.key).remove(value)
724        else:
725            collection.remove_with_event(value, initiator)
726
727    def set(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
728        """Set a value on the given object.
729
730        `initiator` is the ``InstrumentedAttribute`` that initiated the
731        ``set()`` operation and is used to control the depth of a circular
732        setter operation.
733        """
734
735        if initiator is self:
736            return
737
738        self._set_iterable(
739            state, dict_, value,
740            lambda adapter, i: adapter.adapt_like_to_iterable(i))
741
742    def _set_iterable(self, state, dict_, iterable, adapter=None):
743        """Set a collection value from an iterable of state-bearers.
744
745        ``adapter`` is an optional callable invoked with a CollectionAdapter
746        and the iterable.  Should return an iterable of state-bearing
747        instances suitable for appending via a CollectionAdapter.  Can be used
748        for, e.g., adapting an incoming dictionary into an iterator of values
749        rather than keys.
750
751        """
752        # pulling a new collection first so that an adaptation exception does
753        # not trigger a lazy load of the old collection.
754        new_collection, user_data = self._initialize_collection(state)
755        if adapter:
756            new_values = list(adapter(new_collection, iterable))
757        else:
758            new_values = list(iterable)
759
760        old = self.get(state, dict_)
761
762        # ignore re-assignment of the current collection, as happens
763        # implicitly with in-place operators (foo.collection |= other)
764        if old is iterable:
765            return
766
767        state.modified_event(dict_, self, True, old)
768
769        old_collection = self.get_collection(state, dict_, old)
770
771        dict_[self.key] = user_data
772
773        collections.bulk_replace(new_values, old_collection, new_collection)
774        old_collection.unlink(old)
775
776
777    def set_committed_value(self, state, dict_, value):
778        """Set an attribute value on the given instance and 'commit' it."""
779
780        collection, user_data = self._initialize_collection(state)
781
782        if value:
783            for item in value:
784                collection.append_without_event(item)
785
786        state.callables.pop(self.key, None)
787        state.dict[self.key] = user_data
788
789        state.commit(dict_, [self.key])
790
791        if self.key in state.pending:
792           
793            # pending items exist.  issue a modified event,
794            # add/remove new items.
795            state.modified_event(dict_, self, True, user_data)
796
797            pending = state.pending.pop(self.key)
798            added = pending.added_items
799            removed = pending.deleted_items
800            for item in added:
801                collection.append_without_event(item)
802            for item in removed:
803                collection.remove_without_event(item)
804
805        return user_data
806
807    def get_collection(self, state, dict_, user_data=None, passive=PASSIVE_OFF):
808        """Retrieve the CollectionAdapter associated with the given state.
809
810        Creates a new CollectionAdapter if one does not exist.
811
812        """
813        if user_data is None:
814            user_data = self.get(state, dict_, passive=passive)
815            if user_data is PASSIVE_NORESULT:
816                return user_data
817
818        return getattr(user_data, '_sa_adapter')
819
820class GenericBackrefExtension(interfaces.AttributeExtension):
821    """An extension which synchronizes a two-way relationship.
822
823    A typical two-way relationship is a parent object containing a list of
824    child objects, where each child object references the parent.  The other
825    are two objects which contain scalar references to each other.
826
827    """
828   
829    active_history = False
830   
831    def __init__(self, key):
832        self.key = key
833
834    def set(self, state, child, oldchild, initiator):
835        if oldchild is child:
836            return child
837        if oldchild is not None:
838            # With lazy=None, there's no guarantee that the full collection is
839            # present when updating via a backref.
840            old_state, old_dict = instance_state(oldchild), instance_dict(oldchild)
841            impl = old_state.get_impl(self.key)
842            try:
843                impl.remove(old_state, old_dict, state.obj(), initiator, passive=PASSIVE_NO_CALLABLES)
844            except (ValueError, KeyError, IndexError):
845                pass
846        if child is not None:
847            new_state,  new_dict = instance_state(child), instance_dict(child)
848            new_state.get_impl(self.key).append(new_state, new_dict, state.obj(), initiator, passive=PASSIVE_NO_CALLABLES)
849        return child
850
851    def append(self, state, child, initiator):
852        child_state, child_dict = instance_state(child), instance_dict(child)
853        child_state.get_impl(self.key).append(child_state, child_dict, state.obj(), initiator, passive=PASSIVE_NO_CALLABLES)
854        return child
855
856    def remove(self, state, child, initiator):
857        if child is not None:
858            child_state, child_dict = instance_state(child), instance_dict(child)
859            child_state.get_impl(self.key).remove(child_state, child_dict, state.obj(), initiator, passive=PASSIVE_NO_CALLABLES)
860
861
862class Events(object):
863    def __init__(self):
864        self.original_init = object.__init__
865        self.on_init = ()
866        self.on_init_failure = ()
867        self.on_load = ()
868        self.on_resurrect = ()
869
870    def run(self, event, *args, **kwargs):
871        for fn in getattr(self, event):
872            fn(*args, **kwargs)
873
874    def add_listener(self, event, listener):
875        # not thread safe... problem?  mb: nope
876        bucket = getattr(self, event)
877        if bucket == ():
878            setattr(self, event, [listener])
879        else:
880            bucket.append(listener)
881
882    def remove_listener(self, event, listener):
883        bucket = getattr(self, event)
884        bucket.remove(listener)
885
886
887class ClassManager(dict):
888    """tracks state information at the class level."""
889
890    MANAGER_ATTR = '_sa_class_manager'
891    STATE_ATTR = '_sa_instance_state'
892
893    event_registry_factory = Events
894    deferred_scalar_loader = None
895   
896    def __init__(self, class_):
897        self.class_ = class_
898        self.factory = None  # where we came from, for inheritance bookkeeping
899        self.info = {}
900        self.mapper = None
901        self.new_init = None
902        self.mutable_attributes = set()
903        self.local_attrs = {}
904        self.originals = {}
905        for base in class_.__mro__[-2:0:-1]:   # reverse, skipping 1st and last
906            if not isinstance(base, type):
907                continue
908            cls_state = manager_of_class(base)
909            if cls_state:
910                self.update(cls_state)
911        self.events = self.event_registry_factory()
912        self.manage()
913        self._instrument_init()
914   
915    def _configure_create_arguments(self,
916                            _source=None,
917                            deferred_scalar_loader=None):
918        """Accept extra **kw arguments passed to create_manager_for_cls.
919       
920        The current contract of ClassManager and other managers is that they
921        take a single "cls" argument in their constructor (as per
922        test/orm/instrumentation.py InstrumentationCollisionTest).  This
923        is to provide consistency with the current API of "class manager"
924        callables and such which may return various ClassManager and
925        ClassManager-like instances.   So create_manager_for_cls sends
926        in ClassManager-specific arguments via this method once the
927        non-proxied ClassManager is available.
928       
929        """
930        if _source:
931            deferred_scalar_loader = _source.deferred_scalar_loader
932
933        if deferred_scalar_loader:
934            self.deferred_scalar_loader = deferred_scalar_loader
935   
936    def _subclass_manager(self, cls):
937        """Create a new ClassManager for a subclass of this ClassManager's class.
938       
939        This is called automatically when attributes are instrumented so that
940        the attributes can be propagated to subclasses against their own
941        class-local manager, without the need for mappers etc. to have already
942        pre-configured managers for the full class hierarchy.   Mappers
943        can post-configure the auto-generated ClassManager when needed.
944       
945        """
946        manager = manager_of_class(cls)
947        if manager is None:
948            manager = _create_manager_for_cls(cls, _source=self)
949        return manager
950       
951    def _instrument_init(self):
952        # TODO: self.class_.__init__ is often the already-instrumented
953        # __init__ from an instrumented superclass.  We still need to make
954        # our own wrapper, but it would
955        # be nice to wrap the original __init__ and not our existing wrapper
956        # of such, since this adds method overhead.
957        self.events.original_init = self.class_.__init__
958        self.new_init = _generate_init(self.class_, self)
959        self.install_member('__init__', self.new_init)
960       
961    def _uninstrument_init(self):
962        if self.new_init:
963            self.uninstall_member('__init__')
964            self.new_init = None
965   
966    def _create_instance_state(self, instance):
967        global state
968        if state is None:
969            from sqlalchemy.orm import state
970        if self.mutable_attributes:
971            return state.MutableAttrInstanceState(instance, self)
972        else:
973            return state.InstanceState(instance, self)
974       
975    def manage(self):
976        """Mark this instance as the manager for its class."""
977       
978        setattr(self.class_, self.MANAGER_ATTR, self)
979
980    def dispose(self):
981        """Dissasociate this manager from its class."""
982       
983        delattr(self.class_, self.MANAGER_ATTR)
984
985    def manager_getter(self):
986        return attrgetter(self.MANAGER_ATTR)
987
988    def instrument_attribute(self, key, inst, propagated=False):
989        if propagated:
990            if key in self.local_attrs:
991                return  # don't override local attr with inherited attr
992        else:
993            self.local_attrs[key] = inst
994            self.install_descriptor(key, inst)
995        self[key] = inst
996        for cls in self.class_.__subclasses__():
997            if isinstance(cls, types.ClassType):
998                continue
999            manager = self._subclass_manager(cls)
1000            manager.instrument_attribute(key, inst, True)
1001
1002    def post_configure_attribute(self, key):
1003        pass
1004       
1005    def uninstrument_attribute(self, key, propagated=False):
1006        if key not in self:
1007            return
1008        if propagated:
1009            if key in self.local_attrs:
1010                return  # don't get rid of local attr
1011        else:
1012            del self.local_attrs[key]
1013            self.uninstall_descriptor(key)
1014        del self[key]
1015        if key in self.mutable_attributes:
1016            self.mutable_attributes.remove(key)
1017        for cls in self.class_.__subclasses__():
1018            if isinstance(cls, types.ClassType):
1019                continue
1020            manager = self._subclass_manager(cls)
1021            manager.uninstrument_attribute(key, True)
1022
1023    def unregister(self):
1024        """remove all instrumentation established by this ClassManager."""
1025       
1026        self._uninstrument_init()
1027
1028        self.mapper = self.events = None
1029        self.info.clear()
1030       
1031        for key in list(self):
1032            if key in self.local_attrs:
1033                self.uninstrument_attribute(key)
1034
1035    def install_descriptor(self, key, inst):
1036        if key in (self.STATE_ATTR, self.MANAGER_ATTR):
1037            raise KeyError("%r: requested attribute name conflicts with "
1038                           "instrumentation attribute of the same name." % key)
1039        setattr(self.class_, key, inst)
1040
1041    def uninstall_descriptor(self, key):
1042        delattr(self.class_, key)
1043
1044    def install_member(self, key, implementation):
1045        if key in (self.STATE_ATTR, self.MANAGER_ATTR):
1046            raise KeyError("%r: requested attribute name conflicts with "
1047                           "instrumentation attribute of the same name." % key)
1048        self.originals.setdefault(key, getattr(self.class_, key, None))
1049        setattr(self.class_, key, implementation)
1050
1051    def uninstall_member(self, key):
1052        original = self.originals.pop(key, None)
1053        if original is not None:
1054            setattr(self.class_, key, original)
1055
1056    def instrument_collection_class(self, key, collection_class):
1057        return collections.prepare_instrumentation(collection_class)
1058
1059    def initialize_collection(self, key, state, factory):
1060        user_data = factory()
1061        adapter = collections.CollectionAdapter(
1062            self.get_impl(key), state, user_data)
1063        return adapter, user_data
1064
1065    def is_instrumented(self, key, search=False):
1066        if search:
1067            return key in self
1068        else:
1069            return key in self.local_attrs
1070
1071    def get_impl(self, key):
1072        return self[key].impl
1073
1074    @property
1075    def attributes(self):
1076        return self.itervalues()
1077
1078    ## InstanceState management
1079
1080    def new_instance(self, state=None):
1081        instance = self.class_.__new__(self.class_)
1082        setattr(instance, self.STATE_ATTR, state or self._create_instance_state(instance))
1083        return instance
1084
1085    def setup_instance(self, instance, state=None):
1086        setattr(instance, self.STATE_ATTR, state or self._create_instance_state(instance))
1087   
1088    def teardown_instance(self, instance):
1089        delattr(instance, self.STATE_ATTR)
1090       
1091    def _new_state_if_none(self, instance):
1092        """Install a default InstanceState if none is present.
1093
1094        A private convenience method used by the __init__ decorator.
1095       
1096        """
1097        if hasattr(instance, self.STATE_ATTR):
1098            return False
1099        else:
1100            state = self._create_instance_state(instance)
1101            setattr(instance, self.STATE_ATTR, state)
1102            return state
1103   
1104    def state_getter(self):
1105        """Return a (instance) -> InstanceState callable.
1106
1107        "state getter" callables should raise either KeyError or
1108        AttributeError if no InstanceState could be found for the
1109        instance.
1110        """
1111
1112        return attrgetter(self.STATE_ATTR)
1113   
1114    def dict_getter(self):
1115        return attrgetter('__dict__')
1116       
1117    def has_state(self, instance):
1118        return hasattr(instance, self.STATE_ATTR)
1119       
1120    def has_parent(self, state, key, optimistic=False):
1121        """TODO"""
1122        return self.get_impl(key).hasparent(state, optimistic=optimistic)
1123
1124    def __nonzero__(self):
1125        """All ClassManagers are non-zero regardless of attribute state."""
1126        return True
1127
1128    def __repr__(self):
1129        return '<%s of %r at %x>' % (
1130            self.__class__.__name__, self.class_, id(self))
1131
1132class _ClassInstrumentationAdapter(ClassManager):
1133    """Adapts a user-defined InstrumentationManager to a ClassManager."""
1134
1135    def __init__(self, class_, override, **kw):
1136        self._adapted = override
1137        self._get_state = self._adapted.state_getter(class_)
1138        self._get_dict = self._adapted.dict_getter(class_)
1139       
1140        ClassManager.__init__(self, class_, **kw)
1141
1142    def manage(self):
1143        self._adapted.manage(self.class_, self)
1144
1145    def dispose(self):
1146        self._adapted.dispose(self.class_)
1147
1148    def manager_getter(self):
1149        return self._adapted.manager_getter(self.class_)
1150
1151    def instrument_attribute(self, key, inst, propagated=False):
1152        ClassManager.instrument_attribute(self, key, inst, propagated)
1153        if not propagated:
1154            self._adapted.instrument_attribute(self.class_, key, inst)
1155
1156    def post_configure_attribute(self, key):
1157        self._adapted.post_configure_attribute(self.class_, key, self[key])
1158
1159    def install_descriptor(self, key, inst):
1160        self._adapted.install_descriptor(self.class_, key, inst)
1161
1162    def uninstall_descriptor(self, key):
1163        self._adapted.uninstall_descriptor(self.class_, key)
1164
1165    def install_member(self, key, implementation):
1166        self._adapted.install_member(self.class_, key, implementation)
1167
1168    def uninstall_member(self, key):
1169        self._adapted.uninstall_member(self.class_, key)
1170
1171    def instrument_collection_class(self, key, collection_class):
1172        return self._adapted.instrument_collection_class(
1173            self.class_, key, collection_class)
1174
1175    def initialize_collection(self, key, state, factory):
1176        delegate = getattr(self._adapted, 'initialize_collection', None)
1177        if delegate:
1178            return delegate(key, state, factory)
1179        else:
1180            return ClassManager.initialize_collection(self, key, state, factory)
1181
1182    def new_instance(self, state=None):
1183        instance = self.class_.__new__(self.class_)
1184        self.setup_instance(instance, state)
1185        return instance
1186
1187    def _new_state_if_none(self, instance):
1188        """Install a default InstanceState if none is present.
1189
1190        A private convenience method used by the __init__ decorator.
1191        """
1192        if self.has_state(instance):
1193            return False
1194        else:
1195            return self.setup_instance(instance)
1196
1197    def setup_instance(self, instance, state=None):
1198        self._adapted.initialize_instance_dict(self.class_, instance)
1199       
1200        if state is None:
1201            state = self._create_instance_state(instance)
1202           
1203        # the given instance is assumed to have no state
1204        self._adapted.install_state(self.class_, instance, state)
1205        return state
1206
1207    def teardown_instance(self, instance):
1208        self._adapted.remove_state(self.class_, instance)
1209
1210    def has_state(self, instance):
1211        try:
1212            state = self._get_state(instance)
1213            return True
1214        except exc.NO_STATE:
1215            return False
1216
1217    def state_getter(self):
1218        return self._get_state
1219
1220    def dict_getter(self):
1221        return self._get_dict
1222
1223class History(tuple):
1224    """A 3-tuple of added, unchanged and deleted values.
1225
1226    Each tuple member is an iterable sequence.
1227
1228    """
1229
1230    __slots__ = ()
1231
1232    added = property(itemgetter(0))
1233    unchanged = property(itemgetter(1))
1234    deleted = property(itemgetter(2))
1235
1236    def __new__(cls, added, unchanged, deleted):
1237        return tuple.__new__(cls, (added, unchanged, deleted))
1238   
1239    def __nonzero__(self):
1240        return self != HISTORY_BLANK
1241   
1242    def sum(self):
1243        return self.added + self.unchanged + self.deleted
1244   
1245    def non_deleted(self):
1246        return self.added + self.unchanged
1247   
1248    def non_added(self):
1249        return self.unchanged + self.deleted
1250   
1251    def has_changes(self):
1252        return bool(self.added or self.deleted)
1253       
1254    def as_state(self):
1255        return History(
1256            [c is not None and instance_state(c) or None for c in self.added],
1257            [c is not None and instance_state(c) or None for c in self.unchanged],
1258            [c is not None and instance_state(c) or None for c in self.deleted],
1259        )
1260   
1261    @classmethod
1262    def from_attribute(cls, attribute, state, current):
1263        original = state.committed_state.get(attribute.key, NEVER_SET)
1264
1265        if hasattr(attribute, 'get_collection'):
1266            current = attribute.get_collection(state, state.dict, current)
1267            if original is NO_VALUE:
1268                return cls(list(current), (), ())
1269            elif original is NEVER_SET:
1270                return cls((), list(current), ())
1271            else:
1272                current_set = util.IdentitySet(current)
1273                original_set = util.IdentitySet(original)
1274
1275                # ensure duplicates are maintained
1276                return cls(
1277                    [x for x in current if x not in original_set],
1278                    [x for x in current if x in original_set],
1279                    [x for x in original if x not in current_set]
1280                )
1281        else:
1282            if current is NO_VALUE:
1283                if original not in [None, NEVER_SET, NO_VALUE]:
1284                    deleted = [original]
1285                else:
1286                    deleted = ()
1287                return cls((), (), deleted)
1288            elif original is NO_VALUE:
1289                return cls([current], (), ())
1290            elif (original is NEVER_SET or
1291                  attribute.is_equal(current, original) is True):
1292                # dont let ClauseElement expressions here trip things up
1293                return cls((), [current], ())
1294            else:
1295                if original is not None:
1296                    deleted = [original]
1297                else:
1298                    deleted = ()
1299                return cls([current], (), deleted)
1300
1301HISTORY_BLANK = History(None, None, None)
1302
1303def _conditional_instance_state(obj):
1304    if not isinstance(obj, state.InstanceState):
1305        obj = instance_state(obj)
1306    return obj
1307       
1308def get_history(obj, key, **kwargs):
1309    """Return a History record for the given object and attribute key.
1310   
1311    obj is an instrumented object instance.  An InstanceState
1312    is accepted directly for backwards compatibility but
1313    this usage is deprecated.
1314   
1315    """
1316    return get_state_history(_conditional_instance_state(obj), key, **kwargs)
1317
1318def get_state_history(state, key, **kwargs):
1319    return state.get_history(key, **kwargs)
1320
1321def has_parent(cls, obj, key, optimistic=False):
1322    """TODO"""
1323    manager = manager_of_class(cls)
1324    state = instance_state(obj)
1325    return manager.has_parent(state, key, optimistic)
1326
1327def register_class(class_, **kw):
1328    """Register class instrumentation.
1329   
1330    Returns the existing or newly created class manager.
1331    """
1332
1333    manager = manager_of_class(class_)
1334    if manager is None:
1335        manager = _create_manager_for_cls(class_, **kw)
1336    return manager
1337   
1338def unregister_class(class_):
1339    """Unregister class instrumentation."""
1340   
1341    instrumentation_registry.unregister(class_)
1342
1343def register_attribute(class_, key, **kw):
1344
1345    proxy_property = kw.pop('proxy_property', None)
1346   
1347    comparator = kw.pop('comparator', None)
1348    parententity = kw.pop('parententity', None)
1349    register_descriptor(class_, key, proxy_property, comparator, parententity)
1350    if not proxy_property:
1351        register_attribute_impl(class_, key, **kw)
1352   
1353def register_attribute_impl(class_, key,         
1354        uselist=False, callable_=None,
1355        useobject=False, mutable_scalars=False,
1356        impl_class=None, **kw):
1357   
1358    manager = manager_of_class(class_)
1359    if uselist:
1360        factory = kw.pop('typecallable', None)
1361        typecallable = manager.instrument_collection_class(
1362            key, factory or list)
1363    else:
1364        typecallable = kw.pop('typecallable', None)
1365
1366    if impl_class:
1367        impl = impl_class(class_, key, typecallable, **kw)
1368    elif uselist:
1369        impl = CollectionAttributeImpl(class_, key, callable_,
1370                                       typecallable=typecallable, **kw)
1371    elif useobject:
1372        impl = ScalarObjectAttributeImpl(class_, key, callable_, **kw)
1373    elif mutable_scalars:
1374        impl = MutableScalarAttributeImpl(class_, key, callable_,
1375                                          class_manager=manager, **kw)
1376    else:
1377        impl = ScalarAttributeImpl(class_, key, callable_, **kw)
1378
1379    manager[key].impl = impl
1380   
1381    manager.post_configure_attribute(key)
1382   
1383def register_descriptor(class_, key, proxy_property=None, comparator=None, parententity=None, property_=None):
1384    manager = manager_of_class(class_)
1385
1386    if proxy_property:
1387        proxy_type = proxied_attribute_factory(proxy_property)
1388        descriptor = proxy_type(key, proxy_property, comparator, parententity)
1389    else:
1390        descriptor = InstrumentedAttribute(key, comparator=comparator, parententity=parententity)
1391
1392    manager.instrument_attribute(key, descriptor)
1393
1394def unregister_attribute(class_, key):
1395    manager_of_class(class_).uninstrument_attribute(key)
1396
1397def init_collection(obj, key):
1398    """Initialize a collection attribute and return the collection adapter.
1399   
1400    This function is used to provide direct access to collection internals
1401    for a previously unloaded attribute.  e.g.::
1402       
1403        collection_adapter = init_collection(someobject, 'elements')
1404        for elem in values:
1405            collection_adapter.append_without_event(elem)
1406   
1407    For an easier way to do the above, see :func:`~sqlalchemy.orm.attributes.set_committed_value`.
1408   
1409    obj is an instrumented object instance.  An InstanceState
1410    is accepted directly for backwards compatibility but
1411    this usage is deprecated.
1412   
1413    """
1414    state = _conditional_instance_state(obj)
1415    dict_ = state.dict
1416    return init_state_collection(state, dict_, key)
1417   
1418def init_state_collection(state, dict_, key):
1419    """Initialize a collection attribute and return the collection adapter."""
1420   
1421    attr = state.get_impl(key)
1422    user_data = attr.initialize(state, dict_)
1423    return attr.get_collection(state, dict_, user_data)
1424
1425def set_committed_value(instance, key, value):
1426    """Set the value of an attribute with no history events.
1427   
1428    Cancels any previous history present.  The value should be
1429    a scalar value for scalar-holding attributes, or
1430    an iterable for any collection-holding attribute.
1431
1432    This is the same underlying method used when a lazy loader
1433    fires off and loads additional data from the database.
1434    In particular, this method can be used by application code
1435    which has loaded additional attributes or collections through
1436    separate queries, which can then be attached to an instance
1437    as though it were part of its original loaded state.
1438   
1439    """
1440    state, dict_ = instance_state(instance), instance_dict(instance)
1441    state.get_impl(key).set_committed_value(state, dict_, value)
1442   
1443def set_attribute(instance, key, value):
1444    """Set the value of an attribute, firing history events.
1445   
1446    This function may be used regardless of instrumentation
1447    applied directly to the class, i.e. no descriptors are required.
1448    Custom attribute management schemes will need to make usage
1449    of this method to establish attribute state as understood
1450    by SQLAlchemy.
1451   
1452    """
1453    state, dict_ = instance_state(instance), instance_dict(instance)
1454    state.get_impl(key).set(state, dict_, value, None)
1455
1456def get_attribute(instance, key):
1457    """Get the value of an attribute, firing any callables required.
1458
1459    This function may be used regardless of instrumentation
1460    applied directly to the class, i.e. no descriptors are required.
1461    Custom attribute management schemes will need to make usage
1462    of this method to make usage of attribute state as understood
1463    by SQLAlchemy.
1464   
1465    """
1466    state, dict_ = instance_state(instance), instance_dict(instance)
1467    return state.get_impl(key).get(state, dict_)
1468
1469def del_attribute(instance, key):
1470    """Delete the value of an attribute, firing history events.
1471
1472    This function may be used regardless of instrumentation
1473    applied directly to the class, i.e. no descriptors are required.
1474    Custom attribute management schemes will need to make usage
1475    of this method to establish attribute state as understood
1476    by SQLAlchemy.
1477   
1478    """
1479    state, dict_ = instance_state(instance), instance_dict(instance)
1480    state.get_impl(key).delete(state, dict_)
1481
1482def is_instrumented(instance, key):
1483    """Return True if the given attribute on the given instance is instrumented
1484    by the attributes package.
1485   
1486    This function may be used regardless of instrumentation
1487    applied directly to the class, i.e. no descriptors are required.
1488   
1489    """
1490    return manager_of_class(instance.__class__).is_instrumented(key, search=True)
1491
1492class InstrumentationRegistry(object):
1493    """Private instrumentation registration singleton."""
1494
1495    _manager_finders = weakref.WeakKeyDictionary()
1496    _state_finders = util.WeakIdentityMapping()
1497    _dict_finders = util.WeakIdentityMapping()
1498    _extended = False
1499
1500    def create_manager_for_cls(self, class_, **kw):
1501        assert class_ is not None
1502        assert manager_of_class(class_) is None
1503
1504        for finder in instrumentation_finders:
1505            factory = finder(class_)
1506            if factory is not None:
1507                break
1508        else:
1509            factory = ClassManager
1510
1511        existing_factories = self._collect_management_factories_for(class_).\
1512                                difference([factory])
1513        if existing_factories:
1514            raise TypeError(
1515                "multiple instrumentation implementations specified "
1516                "in %s inheritance hierarchy: %r" % (
1517                    class_.__name__, list(existing_factories)))
1518
1519        manager = factory(class_)
1520        if not isinstance(manager, ClassManager):
1521            manager = _ClassInstrumentationAdapter(class_, manager)
1522           
1523        if factory != ClassManager and not self._extended:
1524            self._extended = True
1525            _install_lookup_strategy(self)
1526       
1527        manager._configure_create_arguments(**kw)
1528
1529        manager.factory = factory
1530        self._manager_finders[class_] = manager.manager_getter()
1531        self._state_finders[class_] = manager.state_getter()
1532        self._dict_finders[class_] = manager.dict_getter()
1533        return manager
1534
1535    def _collect_management_factories_for(self, cls):
1536        """Return a collection of factories in play or specified for a hierarchy.
1537
1538        Traverses the entire inheritance graph of a cls and returns a collection
1539        of instrumentation factories for those classes.  Factories are extracted
1540        from active ClassManagers, if available, otherwise
1541        instrumentation_finders is consulted.
1542
1543        """
1544        hierarchy = util.class_hierarchy(cls)
1545        factories = set()
1546        for member in hierarchy:
1547            manager = manager_of_class(member)
1548            if manager is not None:
1549                factories.add(manager.factory)
1550            else:
1551                for finder in instrumentation_finders:
1552                    factory = finder(member)
1553                    if factory is not None:
1554                        break
1555                else:
1556                    factory = None
1557                factories.add(factory)
1558        factories.discard(None)
1559        return factories
1560
1561    def manager_of_class(self, cls):
1562        if cls is None:
1563            return None
1564        try:
1565            finder = self._manager_finders[cls]
1566        except KeyError:
1567            return None
1568        else:
1569            return finder(cls)
1570
1571    def state_of(self, instance):
1572        # this is only called when alternate instrumentation has been established
1573        if instance is None:
1574            raise AttributeError("None has no persistent state.")
1575        try:
1576            return self._state_finders[instance.__class__](instance)
1577        except KeyError:
1578            raise AttributeError("%r is not instrumented" % instance.__class__)
1579
1580    def dict_of(self, instance):
1581        # this is only called when alternate instrumentation has been established
1582        if instance is None:
1583            raise AttributeError("None has no persistent state.")
1584        try:
1585            return self._dict_finders[instance.__class__](instance)
1586        except KeyError:
1587            raise AttributeError("%r is not instrumented" % instance.__class__)
1588       
1589    def unregister(self, class_):
1590        if class_ in self._manager_finders:
1591            manager = self.manager_of_class(class_)
1592            manager.unregister()
1593            manager.dispose()
1594            del self._manager_finders[class_]
1595            del self._state_finders[class_]
1596            del self._dict_finders[class_]
1597
1598instrumentation_registry = InstrumentationRegistry()
1599
1600def _install_lookup_strategy(implementation):
1601    """Replace global class/object management functions
1602    with either faster or more comprehensive implementations,
1603    based on whether or not extended class instrumentation
1604    has been detected.
1605   
1606    This function is called only by InstrumentationRegistry()
1607    and unit tests specific to this behavior.
1608   
1609    """
1610    global instance_state, instance_dict
1611    if implementation is util.symbol('native'):
1612        instance_state = attrgetter(ClassManager.STATE_ATTR)
1613        instance_dict = attrgetter("__dict__")
1614    else:
1615        instance_state = instrumentation_registry.state_of
1616        instance_dict = instrumentation_registry.dict_of
1617       
1618manager_of_class = instrumentation_registry.manager_of_class
1619_create_manager_for_cls = instrumentation_registry.create_manager_for_cls
1620_install_lookup_strategy(util.symbol('native'))
1621
1622def find_native_user_instrumentation_hook(cls):
1623    """Find user-specified instrumentation management for a class."""
1624    return getattr(cls, INSTRUMENTATION_MANAGER, None)
1625instrumentation_finders.append(find_native_user_instrumentation_hook)
1626
1627def _generate_init(class_, class_manager):
1628    """Build an __init__ decorator that triggers ClassManager events."""
1629
1630    # TODO: we should use the ClassManager's notion of the
1631    # original '__init__' method, once ClassManager is fixed
1632    # to always reference that.
1633    original__init__ = class_.__init__
1634    assert original__init__
1635
1636    # Go through some effort here and don't change the user's __init__
1637    # calling signature.
1638    # FIXME: need to juggle local names to avoid constructor argument
1639    # clashes.
1640    func_body = """\
1641def __init__(%(apply_pos)s):
1642    new_state = class_manager._new_state_if_none(%(self_arg)s)
1643    if new_state:
1644        return new_state.initialize_instance(%(apply_kw)s)
1645    else:
1646        return original__init__(%(apply_kw)s)
1647"""
1648    func_vars = util.format_argspec_init(original__init__, grouped=False)
1649    func_text = func_body % func_vars
1650
1651    func = getattr(original__init__, 'im_func', original__init__)
1652    func_defaults = getattr(func, 'func_defaults', None)
1653
1654    env = locals().copy()
1655    exec func_text in env
1656    __init__ = env['__init__']
1657    __init__.__doc__ = original__init__.__doc__
1658    if func_defaults:
1659        __init__.func_defaults = func_defaults
1660    return __init__
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。