Source code for envisage.extension_point

# (C) Copyright 2007-2023 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
""" A trait type used to declare and access extension points. """


# Standard library imports.
import inspect
import weakref

# Enthought library imports.
from traits.api import List, provides, TraitType, Undefined

# Local imports.
from .i_extension_point import IExtensionPoint

# Exception message template.
INVALID_TRAIT_TYPE = (
    'extension points must be "List"s e.g. List, List(Int)'
    " but a value of %s was specified."
)


# Even though trait types do not themselves have traits, we can still
# declare that we implement an interface.
[docs]@provides(IExtensionPoint) class ExtensionPoint(TraitType): """A trait type used to declare and access extension points. Note that this is a trait *type* and hence does *NOT* have traits itself (i.e. it does *not* inherit from 'HasTraits'). """ ########################################################################### # 'ExtensionPoint' *CLASS* interface. ###########################################################################
[docs] @staticmethod def connect_extension_point_traits(obj): """Connect all of the 'ExtensionPoint' traits on an object.""" for trait_name, trait in obj.traits(__extension_point__=True).items(): trait.trait_type.connect(obj, trait_name)
[docs] @staticmethod def disconnect_extension_point_traits(obj): """Disconnect all of the 'ExtensionPoint' traits on an object.""" for trait_name, trait in obj.traits(__extension_point__=True).items(): trait.trait_type.disconnect(obj, trait_name)
########################################################################### # 'object' interface. ########################################################################### def __init__(self, trait_type=List, id=None, **metadata): """Constructor.""" # We add '__extension_point__' to the metadata to make the extension # point traits easier to find with the 'traits' and 'trait_names' # methods on 'HasTraits'. metadata["__extension_point__"] = True super().__init__(**metadata) # The trait type that describes the extension point. # # If we are handed a trait type *class* e.g. List, instead of a trait # type *instance* e.g. List() or List(Int) etc, then we instantiate it. if inspect.isclass(trait_type): trait_type = trait_type() # Currently, we only support list extension points (we may in the # future want to allow other collections e.g. dictionaries etc). if not isinstance(trait_type, List): raise TypeError(INVALID_TRAIT_TYPE % trait_type) self.trait_type = trait_type # The Id of the extension point. if id is None: raise ValueError("an extension point must have an Id") self.id = id # A dictionary that is used solely to keep a reference to all extension # point listeners alive until their associated objects are garbage # collected. # # Dict(weakref.ref(Any), Dict(Str, Callable)) self._obj_to_listeners_map = weakref.WeakKeyDictionary() def __repr__(self): """String representation of an ExtensionPoint object""" return "ExtensionPoint(id={!r})".format(self.id) ########################################################################### # 'TraitType' interface. ###########################################################################
[docs] def get(self, obj, trait_name): """Trait type getter.""" extension_registry = self._get_extension_registry(obj) # Get the extensions to this extension point. extensions = extension_registry.get_extensions(self.id) # Make sure the contributions are of the appropriate type. return self.trait_type.validate(obj, trait_name, extensions)
[docs] def set(self, obj, name, value): """Trait type setter.""" extension_registry = self._get_extension_registry(obj) # Note that some extension registry implementations may not support the # setting of extension points (the default, plugin extension registry # for exxample ;^). extension_registry.set_extensions(self.id, value)
########################################################################### # 'ExtensionPoint' interface. ###########################################################################
[docs] def connect(self, obj, trait_name): """Connect the extension point to a trait on an object. This allows the object to react when contributions are added or removed from the extension point. fixme: It would be nice to be able to make the connection automatically but we would need a slight tweak to traits to allow the trait type to be notified when a new instance that uses the trait type is created. """ def listener(extension_registry, event): """Listener called when an extension point is changed.""" # If an index was specified then we fire an '_items' changed event. if event.index is not None: name = trait_name + "_items" old = Undefined new = event # Otherwise, we fire a normal trait changed event. else: name = trait_name old = event.removed new = event.added obj.trait_property_changed(name, old, new) extension_registry = self._get_extension_registry(obj) # Add the listener to the extension registry. extension_registry.add_extension_point_listener(listener, self.id) # Save a reference to the listener so that it does not get garbage # collected until its associated object does. listeners = self._obj_to_listeners_map.setdefault(obj, {}) listeners[trait_name] = listener
[docs] def disconnect(self, obj, trait_name): """Disconnect the extension point from a trait on an object.""" extension_registry = self._get_extension_registry(obj) listener = self._obj_to_listeners_map[obj].get(trait_name) if listener is not None: # Remove the listener from the extension registry. extension_registry.remove_extension_point_listener( listener, self.id ) # Clean up. del self._obj_to_listeners_map[obj][trait_name]
########################################################################### # Private interface. ########################################################################### def _get_extension_registry(self, obj): """Return the extension registry in effect for an object.""" extension_registry = getattr(obj, "extension_registry", None) if extension_registry is None: raise ValueError( 'The "ExtensionPoint" trait type can only be used in ' "objects that have a reference to an extension registry " 'via their "extension_registry" trait. ' "Extension point Id <%s>" % self.id ) return extension_registry