diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-11-20 11:39:44 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-11-20 11:39:44 -0500 |
commit | d505ea71aed44ecae718052131dc0a2fb2c9dd99 (patch) | |
tree | 8ecf16d2aa1c23bf7721403d6ff13d0ee5b1da48 /lib/sqlalchemy/orm/attributes.py | |
parent | 4af7bc6cfc8790bf6ef267c059a47952de7c64fa (diff) | |
parent | 61c76f92df7c0b5b1ce0178148bab71bc4a64ec7 (diff) | |
download | sqlalchemy-d505ea71aed44ecae718052131dc0a2fb2c9dd99.tar.gz |
- merge events branch, [ticket:1902]
Diffstat (limited to 'lib/sqlalchemy/orm/attributes.py')
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 787 |
1 files changed, 74 insertions, 713 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index aefccb63a..6872dd645 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -3,24 +3,20 @@ # # This module is part of SQLAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -"""Defines SQLAlchemy's system of class instrumentation.. +"""Defines instrumentation for class attributes and their interaction +with instances. This module is usually not directly visible to user applications, but defines a large part of the ORM's interactivity. -SQLA's instrumentation system is completely customizable, in which -case an understanding of the general mechanics of this module is helpful. -An example of full customization is in /examples/custom_attributes. """ import operator -from operator import attrgetter, itemgetter -import types -import weakref +from operator import itemgetter -from sqlalchemy import util -from sqlalchemy.orm import interfaces, collections, exc +from sqlalchemy import util, event +from sqlalchemy.orm import interfaces, collections, events import sqlalchemy.exceptions as sa_exc mapperutil = util.importlater("sqlalchemy.orm", "util") @@ -52,62 +48,30 @@ Loads of "previous" values during change events use this flag. PASSIVE_OFF = False #util.symbol('PASSIVE_OFF') """Symbol indicating that loader callables should be executed.""" -INSTRUMENTATION_MANAGER = '__sa_instrumentation_manager__' -"""Attribute, elects custom instrumentation when present on a mapped class. - -Allows a class to specify a slightly or wildly different technique for -tracking changes made to mapped attributes and collections. - -Only one instrumentation implementation is allowed in a given object -inheritance hierarchy. - -The value of this attribute must be a callable and will be passed a class -object. The callable must return one of: - - - An instance of an interfaces.InstrumentationManager or subclass - - An object implementing all or some of InstrumentationManager (TODO) - - A dictionary of callables, implementing all or some of the above (TODO) - - An instance of a ClassManager or subclass - -interfaces.InstrumentationManager is public API and will remain stable -between releases. ClassManager is not public and no guarantees are made -about stability. Caveat emptor. - -This attribute is consulted by the default SQLAlchemy instrumentation -resolution code. If custom finders are installed in the global -instrumentation_finders list, they may or may not choose to honor this -attribute. - -""" - -instrumentation_finders = [] -"""An extensible sequence of instrumentation implementation finding callables. - -Finders callables will be passed a class object. If None is returned, the -next finder in the sequence is consulted. Otherwise the return must be an -instrumentation factory that follows the same guidelines as -INSTRUMENTATION_MANAGER. - -By default, the only finder is find_native_user_instrumentation_hook, which -searches for INSTRUMENTATION_MANAGER. If all finders return None, standard -ClassManager instrumentation is used. - -""" class QueryableAttribute(interfaces.PropComparator): - - def __init__(self, key, impl=None, comparator=None, parententity=None): - """Construct an InstrumentedAttribute. - - comparator - a sql.Comparator to which class-level compare/math events will be - sent - """ + """Base class for class-bound attributes. """ + + def __init__(self, class_, key, impl=None, + comparator=None, parententity=None): + self.class_ = class_ self.key = key self.impl = impl self.comparator = comparator self.parententity = parententity - + + manager = manager_of_class(class_) + # manager is None in the case of AliasedClass + if manager: + # propagate existing event listeners from + # immediate superclass + for base in manager._bases: + if key in base: + self.dispatch.update(base[key].dispatch) + + dispatch = event.dispatcher(events.AttributeEvents) + dispatch.dispatch_cls.active_history = False + def get_history(self, instance, **kwargs): return self.impl.get_history(instance_state(instance), instance_dict(instance), **kwargs) @@ -151,7 +115,7 @@ class QueryableAttribute(interfaces.PropComparator): class InstrumentedAttribute(QueryableAttribute): - """Public-facing descriptor, placed in the mapped class dictionary.""" + """Class bound instrumented attribute which adds descriptor methods.""" def __set__(self, instance, value): self.impl.set(instance_state(instance), @@ -240,7 +204,7 @@ class AttributeImpl(object): """internal implementation for instrumented attributes.""" def __init__(self, class_, key, - callable_, trackparent=False, extension=None, + callable_, dispatch, trackparent=False, extension=None, compare_function=None, active_history=False, parent_token=None, expire_missing=True, **kwargs): @@ -264,7 +228,8 @@ class AttributeImpl(object): extension a single or list of AttributeExtension object(s) which will - receive set/delete/append/remove/etc. events. + receive set/delete/append/remove/etc. events. Deprecated. + The event package is now used. compare_function a function that compares two values which are normally @@ -289,18 +254,24 @@ class AttributeImpl(object): self.class_ = class_ self.key = key self.callable_ = callable_ + self.dispatch = dispatch self.trackparent = trackparent self.parent_token = parent_token or self if compare_function is None: self.is_equal = operator.eq else: self.is_equal = compare_function - self.extensions = util.to_list(extension or []) - for e in self.extensions: - if e.active_history: - active_history = True - break - self.active_history = active_history + + # TODO: pass in the manager here + # instead of doing a lookup + attr = manager_of_class(class_)[key] + + for ext in util.to_list(extension or []): + ext._adapt_listener(attr, ext) + + if active_history: + self.dispatch.active_history = True + self.expire_missing = expire_missing @@ -437,12 +408,12 @@ class ScalarAttributeImpl(AttributeImpl): def delete(self, state, dict_): # TODO: catch key errors, convert to attributeerror? - if self.active_history: + if self.dispatch.active_history: old = self.get(state, dict_) else: old = dict_.get(self.key, NO_VALUE) - if self.extensions: + if self.dispatch.on_remove: self.fire_remove_event(state, dict_, old, None) state.modified_event(dict_, self, False, old) del dict_[self.key] @@ -455,25 +426,25 @@ class ScalarAttributeImpl(AttributeImpl): if initiator and initiator.parent_token is self.parent_token: return - if self.active_history: + if self.dispatch.active_history: old = self.get(state, dict_) else: old = dict_.get(self.key, NO_VALUE) - if self.extensions: + if self.dispatch.on_set: value = self.fire_replace_event(state, dict_, value, old, initiator) state.modified_event(dict_, self, False, old) dict_[self.key] = value def fire_replace_event(self, state, dict_, value, previous, initiator): - for ext in self.extensions: - value = ext.set(state, value, previous, initiator or self) + for fn in self.dispatch.on_set: + value = fn(state, value, previous, initiator or self) return value def fire_remove_event(self, state, dict_, value, initiator): - for ext in self.extensions: - ext.remove(state, value, initiator or self) + for fn in self.dispatch.on_remove: + fn(state, value, initiator or self) @property def type(self): @@ -489,13 +460,13 @@ class MutableScalarAttributeImpl(ScalarAttributeImpl): uses_objects = False supports_population = True - def __init__(self, class_, key, callable_, + def __init__(self, class_, key, callable_, dispatch, class_manager, copy_function=None, compare_function=None, **kwargs): super(ScalarAttributeImpl, self).__init__( class_, key, - callable_, + callable_, dispatch, compare_function=compare_function, **kwargs) class_manager.mutable_attributes.add(key) @@ -534,7 +505,7 @@ class MutableScalarAttributeImpl(ScalarAttributeImpl): if initiator and initiator.parent_token is self.parent_token: return - if self.extensions: + if self.dispatch.on_set: old = self.get(state, dict_) value = self.fire_replace_event(state, dict_, value, old, initiator) @@ -556,13 +527,13 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): uses_objects = True supports_population = True - def __init__(self, class_, key, callable_, + def __init__(self, class_, key, callable_, dispatch, trackparent=False, extension=None, copy_function=None, compare_function=None, **kwargs): super(ScalarObjectAttributeImpl, self).__init__( class_, key, - callable_, + callable_, dispatch, trackparent=trackparent, extension=extension, compare_function=compare_function, @@ -596,7 +567,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): if initiator and initiator.parent_token is self.parent_token: return - if self.active_history: + if self.dispatch.active_history: old = self.get(state, dict_, passive=PASSIVE_ONLY_PERSISTENT) else: old = self.get(state, dict_, passive=PASSIVE_NO_FETCH) @@ -607,9 +578,9 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): def fire_remove_event(self, state, dict_, value, initiator): if self.trackparent and value is not None: self.sethasparent(instance_state(value), False) - - for ext in self.extensions: - ext.remove(state, value, initiator or self) + + for fn in self.dispatch.on_remove: + fn(state, value, initiator or self) state.modified_event(dict_, self, False, value) @@ -620,8 +591,8 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): previous is not PASSIVE_NO_RESULT): self.sethasparent(instance_state(previous), False) - for ext in self.extensions: - value = ext.set(state, value, previous, initiator or self) + for fn in self.dispatch.on_set: + value = fn(state, value, previous, initiator or self) state.modified_event(dict_, self, False, previous) @@ -647,13 +618,13 @@ class CollectionAttributeImpl(AttributeImpl): uses_objects = True supports_population = True - def __init__(self, class_, key, callable_, + def __init__(self, class_, key, callable_, dispatch, typecallable=None, trackparent=False, extension=None, copy_function=None, compare_function=None, **kwargs): super(CollectionAttributeImpl, self).__init__( class_, key, - callable_, + callable_, dispatch, trackparent=trackparent, extension=extension, compare_function=compare_function, @@ -675,8 +646,8 @@ class CollectionAttributeImpl(AttributeImpl): return History.from_attribute(self, state, current) def fire_append_event(self, state, dict_, value, initiator): - for ext in self.extensions: - value = ext.append(state, value, initiator or self) + for fn in self.dispatch.on_append: + value = fn(state, value, initiator or self) state.modified_event(dict_, self, True, NEVER_SET, passive=PASSIVE_NO_INITIALIZE) @@ -694,8 +665,8 @@ class CollectionAttributeImpl(AttributeImpl): if self.trackparent and value is not None: self.sethasparent(instance_state(value), False) - for ext in self.extensions: - ext.remove(state, value, initiator or self) + for fn in self.dispatch.on_remove: + fn(state, value, initiator or self) state.modified_event(dict_, self, True, NEVER_SET, passive=PASSIVE_NO_INITIALIZE) @@ -909,394 +880,6 @@ class GenericBackrefExtension(interfaces.AttributeExtension): passive=PASSIVE_NO_FETCH) -class Events(object): - def __init__(self): - self.original_init = object.__init__ - # Initialize to tuples instead of lists to minimize the memory - # footprint - self.on_init = () - self.on_init_failure = () - self.on_load = () - self.on_resurrect = () - - def run(self, event, *args): - for fn in getattr(self, event): - fn(*args) - - def add_listener(self, event, listener): - # not thread safe... problem? mb: nope - bucket = getattr(self, event) - if bucket == (): - setattr(self, event, [listener]) - else: - bucket.append(listener) - - def remove_listener(self, event, listener): - bucket = getattr(self, event) - bucket.remove(listener) - - -class ClassManager(dict): - """tracks state information at the class level.""" - - MANAGER_ATTR = '_sa_class_manager' - STATE_ATTR = '_sa_instance_state' - - event_registry_factory = Events - deferred_scalar_loader = None - - def __init__(self, class_): - self.class_ = class_ - self.factory = None # where we came from, for inheritance bookkeeping - self.info = {} - self.new_init = None - self.mutable_attributes = set() - self.local_attrs = {} - self.originals = {} - for base in class_.__mro__[-2:0:-1]: # reverse, skipping 1st and last - if not isinstance(base, type): - continue - cls_state = manager_of_class(base) - if cls_state: - self.update(cls_state) - self.events = self.event_registry_factory() - self.manage() - self._instrument_init() - - @property - def is_mapped(self): - return 'mapper' in self.__dict__ - - @util.memoized_property - def mapper(self): - raise exc.UnmappedClassError(self.class_) - - def _attr_has_impl(self, key): - """Return True if the given attribute is fully initialized. - - i.e. has an impl. - """ - - return key in self and self[key].impl is not None - - def _configure_create_arguments(self, - _source=None, - deferred_scalar_loader=None): - """Accept extra **kw arguments passed to create_manager_for_cls. - - The current contract of ClassManager and other managers is that they - take a single "cls" argument in their constructor (as per - test/orm/instrumentation.py InstrumentationCollisionTest). This - is to provide consistency with the current API of "class manager" - callables and such which may return various ClassManager and - ClassManager-like instances. So create_manager_for_cls sends - in ClassManager-specific arguments via this method once the - non-proxied ClassManager is available. - - """ - if _source: - deferred_scalar_loader = _source.deferred_scalar_loader - - if deferred_scalar_loader: - self.deferred_scalar_loader = deferred_scalar_loader - - def _subclass_manager(self, cls): - """Create a new ClassManager for a subclass of this ClassManager's - class. - - This is called automatically when attributes are instrumented so that - the attributes can be propagated to subclasses against their own - class-local manager, without the need for mappers etc. to have already - pre-configured managers for the full class hierarchy. Mappers - can post-configure the auto-generated ClassManager when needed. - - """ - manager = manager_of_class(cls) - if manager is None: - manager = _create_manager_for_cls(cls, _source=self) - return manager - - def _instrument_init(self): - # TODO: self.class_.__init__ is often the already-instrumented - # __init__ from an instrumented superclass. We still need to make - # our own wrapper, but it would - # be nice to wrap the original __init__ and not our existing wrapper - # of such, since this adds method overhead. - self.events.original_init = self.class_.__init__ - self.new_init = _generate_init(self.class_, self) - self.install_member('__init__', self.new_init) - - def _uninstrument_init(self): - if self.new_init: - self.uninstall_member('__init__') - self.new_init = None - - def _create_instance_state(self, instance): - if self.mutable_attributes: - return state.MutableAttrInstanceState(instance, self) - else: - return state.InstanceState(instance, self) - - def manage(self): - """Mark this instance as the manager for its class.""" - - setattr(self.class_, self.MANAGER_ATTR, self) - - def dispose(self): - """Dissasociate this manager from its class.""" - - delattr(self.class_, self.MANAGER_ATTR) - - def manager_getter(self): - return attrgetter(self.MANAGER_ATTR) - - def instrument_attribute(self, key, inst, propagated=False): - if propagated: - if key in self.local_attrs: - return # don't override local attr with inherited attr - else: - self.local_attrs[key] = inst - self.install_descriptor(key, inst) - self[key] = inst - - for cls in self.class_.__subclasses__(): - manager = self._subclass_manager(cls) - manager.instrument_attribute(key, inst, True) - - def post_configure_attribute(self, key): - pass - - def uninstrument_attribute(self, key, propagated=False): - if key not in self: - return - if propagated: - if key in self.local_attrs: - return # don't get rid of local attr - else: - del self.local_attrs[key] - self.uninstall_descriptor(key) - del self[key] - if key in self.mutable_attributes: - self.mutable_attributes.remove(key) - for cls in self.class_.__subclasses__(): - manager = self._subclass_manager(cls) - manager.uninstrument_attribute(key, True) - - def unregister(self): - """remove all instrumentation established by this ClassManager.""" - - self._uninstrument_init() - - self.mapper = self.events = None - self.info.clear() - - for key in list(self): - if key in self.local_attrs: - self.uninstrument_attribute(key) - - def install_descriptor(self, key, inst): - if key in (self.STATE_ATTR, self.MANAGER_ATTR): - raise KeyError("%r: requested attribute name conflicts with " - "instrumentation attribute of the same name." % - key) - setattr(self.class_, key, inst) - - def uninstall_descriptor(self, key): - delattr(self.class_, key) - - def install_member(self, key, implementation): - if key in (self.STATE_ATTR, self.MANAGER_ATTR): - raise KeyError("%r: requested attribute name conflicts with " - "instrumentation attribute of the same name." % - key) - self.originals.setdefault(key, getattr(self.class_, key, None)) - setattr(self.class_, key, implementation) - - def uninstall_member(self, key): - original = self.originals.pop(key, None) - if original is not None: - setattr(self.class_, key, original) - - def instrument_collection_class(self, key, collection_class): - return collections.prepare_instrumentation(collection_class) - - def initialize_collection(self, key, state, factory): - user_data = factory() - adapter = collections.CollectionAdapter( - self.get_impl(key), state, user_data) - return adapter, user_data - - def is_instrumented(self, key, search=False): - if search: - return key in self - else: - return key in self.local_attrs - - def get_impl(self, key): - return self[key].impl - - @property - def attributes(self): - return self.itervalues() - - ## InstanceState management - - def new_instance(self, state=None): - instance = self.class_.__new__(self.class_) - setattr(instance, self.STATE_ATTR, - state or self._create_instance_state(instance)) - return instance - - def setup_instance(self, instance, state=None): - setattr(instance, self.STATE_ATTR, - state or self._create_instance_state(instance)) - - def teardown_instance(self, instance): - delattr(instance, self.STATE_ATTR) - - def _new_state_if_none(self, instance): - """Install a default InstanceState if none is present. - - A private convenience method used by the __init__ decorator. - - """ - if hasattr(instance, self.STATE_ATTR): - return False - elif self.class_ is not instance.__class__ and \ - self.is_mapped: - # this will create a new ClassManager for the - # subclass, without a mapper. This is likely a - # user error situation but allow the object - # to be constructed, so that it is usable - # in a non-ORM context at least. - return self._subclass_manager(instance.__class__).\ - _new_state_if_none(instance) - else: - state = self._create_instance_state(instance) - setattr(instance, self.STATE_ATTR, state) - return state - - def state_getter(self): - """Return a (instance) -> InstanceState callable. - - "state getter" callables should raise either KeyError or - AttributeError if no InstanceState could be found for the - instance. - """ - - return attrgetter(self.STATE_ATTR) - - def dict_getter(self): - return attrgetter('__dict__') - - def has_state(self, instance): - return hasattr(instance, self.STATE_ATTR) - - def has_parent(self, state, key, optimistic=False): - """TODO""" - return self.get_impl(key).hasparent(state, optimistic=optimistic) - - def __nonzero__(self): - """All ClassManagers are non-zero regardless of attribute state.""" - return True - - def __repr__(self): - return '<%s of %r at %x>' % ( - self.__class__.__name__, self.class_, id(self)) - -class _ClassInstrumentationAdapter(ClassManager): - """Adapts a user-defined InstrumentationManager to a ClassManager.""" - - def __init__(self, class_, override, **kw): - self._adapted = override - self._get_state = self._adapted.state_getter(class_) - self._get_dict = self._adapted.dict_getter(class_) - - ClassManager.__init__(self, class_, **kw) - - def manage(self): - self._adapted.manage(self.class_, self) - - def dispose(self): - self._adapted.dispose(self.class_) - - def manager_getter(self): - return self._adapted.manager_getter(self.class_) - - def instrument_attribute(self, key, inst, propagated=False): - ClassManager.instrument_attribute(self, key, inst, propagated) - if not propagated: - self._adapted.instrument_attribute(self.class_, key, inst) - - def post_configure_attribute(self, key): - self._adapted.post_configure_attribute(self.class_, key, self[key]) - - def install_descriptor(self, key, inst): - self._adapted.install_descriptor(self.class_, key, inst) - - def uninstall_descriptor(self, key): - self._adapted.uninstall_descriptor(self.class_, key) - - def install_member(self, key, implementation): - self._adapted.install_member(self.class_, key, implementation) - - def uninstall_member(self, key): - self._adapted.uninstall_member(self.class_, key) - - def instrument_collection_class(self, key, collection_class): - return self._adapted.instrument_collection_class( - self.class_, key, collection_class) - - def initialize_collection(self, key, state, factory): - delegate = getattr(self._adapted, 'initialize_collection', None) - if delegate: - return delegate(key, state, factory) - else: - return ClassManager.initialize_collection(self, key, - state, factory) - - def new_instance(self, state=None): - instance = self.class_.__new__(self.class_) - self.setup_instance(instance, state) - return instance - - def _new_state_if_none(self, instance): - """Install a default InstanceState if none is present. - - A private convenience method used by the __init__ decorator. - """ - if self.has_state(instance): - return False - else: - return self.setup_instance(instance) - - def setup_instance(self, instance, state=None): - self._adapted.initialize_instance_dict(self.class_, instance) - - if state is None: - state = self._create_instance_state(instance) - - # the given instance is assumed to have no state - self._adapted.install_state(self.class_, instance, state) - return state - - def teardown_instance(self, instance): - self._adapted.remove_state(self.class_, instance) - - def has_state(self, instance): - try: - state = self._get_state(instance) - except exc.NO_STATE: - return False - else: - return True - - def state_getter(self): - return self._get_state - - def dict_getter(self): - return self._get_dict - class History(tuple): """A 3-tuple of added, unchanged and deleted values, representing the changes which have occured on an instrumented @@ -1446,22 +1029,6 @@ def has_parent(cls, obj, key, optimistic=False): state = instance_state(obj) return manager.has_parent(state, key, optimistic) -def register_class(class_, **kw): - """Register class instrumentation. - - Returns the existing or newly created class manager. - """ - - manager = manager_of_class(class_) - if manager is None: - manager = _create_manager_for_cls(class_, **kw) - return manager - -def unregister_class(class_): - """Unregister class instrumentation.""" - - instrumentation_registry.unregister(class_) - def register_attribute(class_, key, **kw): proxy_property = kw.pop('proxy_property', None) @@ -1486,21 +1053,24 @@ def register_attribute_impl(class_, key, else: typecallable = kw.pop('typecallable', None) + dispatch = manager[key].dispatch + if impl_class: - impl = impl_class(class_, key, typecallable, **kw) + impl = impl_class(class_, key, typecallable, dispatch, **kw) elif uselist: - impl = CollectionAttributeImpl(class_, key, callable_, + impl = CollectionAttributeImpl(class_, key, callable_, dispatch, typecallable=typecallable, **kw) elif useobject: - impl = ScalarObjectAttributeImpl(class_, key, callable_, **kw) + impl = ScalarObjectAttributeImpl(class_, key, callable_, + dispatch,**kw) elif mutable_scalars: - impl = MutableScalarAttributeImpl(class_, key, callable_, + impl = MutableScalarAttributeImpl(class_, key, callable_, dispatch, class_manager=manager, **kw) else: - impl = ScalarAttributeImpl(class_, key, callable_, **kw) + impl = ScalarAttributeImpl(class_, key, callable_, dispatch, **kw) manager[key].impl = impl - + manager.post_configure_attribute(key) @@ -1512,7 +1082,7 @@ def register_descriptor(class_, key, proxy_property=None, comparator=None, proxy_type = proxied_attribute_factory(proxy_property) descriptor = proxy_type(key, proxy_property, comparator, parententity) else: - descriptor = InstrumentedAttribute(key, comparator=comparator, + descriptor = InstrumentedAttribute(class_, key, comparator=comparator, parententity=parententity) descriptor.__doc__ = doc @@ -1608,212 +1178,3 @@ def del_attribute(instance, key): state, dict_ = instance_state(instance), instance_dict(instance) state.get_impl(key).delete(state, dict_) -def is_instrumented(instance, key): - """Return True if the given attribute on the given instance is - instrumented by the attributes package. - - This function may be used regardless of instrumentation - applied directly to the class, i.e. no descriptors are required. - - """ - return manager_of_class(instance.__class__).\ - is_instrumented(key, search=True) - -class InstrumentationRegistry(object): - """Private instrumentation registration singleton. - - All classes are routed through this registry - when first instrumented, however the InstrumentationRegistry - is not actually needed unless custom ClassManagers are in use. - - """ - - _manager_finders = weakref.WeakKeyDictionary() - _state_finders = util.WeakIdentityMapping() - _dict_finders = util.WeakIdentityMapping() - _extended = False - - def create_manager_for_cls(self, class_, **kw): - assert class_ is not None - assert manager_of_class(class_) is None - - for finder in instrumentation_finders: - factory = finder(class_) - if factory is not None: - break - else: - factory = ClassManager - - existing_factories = self._collect_management_factories_for(class_).\ - difference([factory]) - if existing_factories: - raise TypeError( - "multiple instrumentation implementations specified " - "in %s inheritance hierarchy: %r" % ( - class_.__name__, list(existing_factories))) - - manager = factory(class_) - if not isinstance(manager, ClassManager): - manager = _ClassInstrumentationAdapter(class_, manager) - - if factory != ClassManager and not self._extended: - # somebody invoked a custom ClassManager. - # reinstall global "getter" functions with the more - # expensive ones. - self._extended = True - _install_lookup_strategy(self) - - manager._configure_create_arguments(**kw) - - manager.factory = factory - self._manager_finders[class_] = manager.manager_getter() - self._state_finders[class_] = manager.state_getter() - self._dict_finders[class_] = manager.dict_getter() - return manager - - def _collect_management_factories_for(self, cls): - """Return a collection of factories in play or specified for a - hierarchy. - - Traverses the entire inheritance graph of a cls and returns a - collection of instrumentation factories for those classes. Factories - are extracted from active ClassManagers, if available, otherwise - instrumentation_finders is consulted. - - """ - hierarchy = util.class_hierarchy(cls) - factories = set() - for member in hierarchy: - manager = manager_of_class(member) - if manager is not None: - factories.add(manager.factory) - else: - for finder in instrumentation_finders: - factory = finder(member) - if factory is not None: - break - else: - factory = None - factories.add(factory) - factories.discard(None) - return factories - - def manager_of_class(self, cls): - # this is only called when alternate instrumentation - # has been established - if cls is None: - return None - try: - finder = self._manager_finders[cls] - except KeyError: - return None - else: - return finder(cls) - - def state_of(self, instance): - # this is only called when alternate instrumentation - # has been established - if instance is None: - raise AttributeError("None has no persistent state.") - try: - return self._state_finders[instance.__class__](instance) - except KeyError: - raise AttributeError("%r is not instrumented" % - instance.__class__) - - def dict_of(self, instance): - # this is only called when alternate instrumentation - # has been established - if instance is None: - raise AttributeError("None has no persistent state.") - try: - return self._dict_finders[instance.__class__](instance) - except KeyError: - raise AttributeError("%r is not instrumented" % - instance.__class__) - - def unregister(self, class_): - if class_ in self._manager_finders: - manager = self.manager_of_class(class_) - manager.unregister() - manager.dispose() - del self._manager_finders[class_] - del self._state_finders[class_] - del self._dict_finders[class_] - if ClassManager.MANAGER_ATTR in class_.__dict__: - delattr(class_, ClassManager.MANAGER_ATTR) - -instrumentation_registry = InstrumentationRegistry() - -def _install_lookup_strategy(implementation): - """Replace global class/object management functions - with either faster or more comprehensive implementations, - based on whether or not extended class instrumentation - has been detected. - - This function is called only by InstrumentationRegistry() - and unit tests specific to this behavior. - - """ - global instance_state, instance_dict, manager_of_class - if implementation is util.symbol('native'): - instance_state = attrgetter(ClassManager.STATE_ATTR) - instance_dict = attrgetter("__dict__") - def manager_of_class(cls): - return cls.__dict__.get(ClassManager.MANAGER_ATTR, None) - else: - instance_state = instrumentation_registry.state_of - instance_dict = instrumentation_registry.dict_of - manager_of_class = instrumentation_registry.manager_of_class - -_create_manager_for_cls = instrumentation_registry.create_manager_for_cls - -# Install default "lookup" strategies. These are basically -# very fast attrgetters for key attributes. -# When a custom ClassManager is installed, more expensive per-class -# strategies are copied over these. -_install_lookup_strategy(util.symbol('native')) - -def find_native_user_instrumentation_hook(cls): - """Find user-specified instrumentation management for a class.""" - return getattr(cls, INSTRUMENTATION_MANAGER, None) -instrumentation_finders.append(find_native_user_instrumentation_hook) - -def _generate_init(class_, class_manager): - """Build an __init__ decorator that triggers ClassManager events.""" - - # TODO: we should use the ClassManager's notion of the - # original '__init__' method, once ClassManager is fixed - # to always reference that. - original__init__ = class_.__init__ - assert original__init__ - - # Go through some effort here and don't change the user's __init__ - # calling signature. - # FIXME: need to juggle local names to avoid constructor argument - # clashes. - func_body = """\ -def __init__(%(apply_pos)s): - new_state = class_manager._new_state_if_none(%(self_arg)s) - if new_state: - return new_state.initialize_instance(%(apply_kw)s) - else: - return original__init__(%(apply_kw)s) -""" - func_vars = util.format_argspec_init(original__init__, grouped=False) - func_text = func_body % func_vars - - # Py3K - #func_defaults = getattr(original__init__, '__defaults__', None) - # Py2K - func = getattr(original__init__, 'im_func', original__init__) - func_defaults = getattr(func, 'func_defaults', None) - # end Py2K - - env = locals().copy() - exec func_text in env - __init__ = env['__init__'] - __init__.__doc__ = original__init__.__doc__ - if func_defaults: - __init__.func_defaults = func_defaults - return __init__ |