summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/attributes.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-11-20 11:39:44 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2010-11-20 11:39:44 -0500
commitd505ea71aed44ecae718052131dc0a2fb2c9dd99 (patch)
tree8ecf16d2aa1c23bf7721403d6ff13d0ee5b1da48 /lib/sqlalchemy/orm/attributes.py
parent4af7bc6cfc8790bf6ef267c059a47952de7c64fa (diff)
parent61c76f92df7c0b5b1ce0178148bab71bc4a64ec7 (diff)
downloadsqlalchemy-d505ea71aed44ecae718052131dc0a2fb2c9dd99.tar.gz
- merge events branch, [ticket:1902]
Diffstat (limited to 'lib/sqlalchemy/orm/attributes.py')
-rw-r--r--lib/sqlalchemy/orm/attributes.py787
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__