Source code for envisage.extension_point
# (C) Copyright 2007-2024 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