Source code for apptools.naming.dynamic_context

# (C) Copyright 2005-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!

""" Provider of a framework that dynamically determines the contents of a
    context at the time of interaction with the contents rather than at the
    time a class is written.

    This capability is particularly useful when the object acting as a context
    is part of a plug-in application -- such as Envisage.  In general, this
    capability allows the context to be:

    - Extendable by contributions from somewhere other than the original
      code writer
    - Dynamic in that the elements it is composed of can change each time
      someone interacts with the contents of the context.

    It should be noted that this capability is explicitly different from
    contexts that look at another container to determine their contents, such
    as a file system context!

    Users of this framework contribute items to a dynamic context by adding
    traits to the dynamic context instance.  (This addition can happen
    statically through the use of a Traits Category.)  The trait value is the
    context item's value and the trait definition's metadata determines how the
    item is treated within the context.  The support metadata is:

    context_name: A non-empty string
        Represents the name of the item within this context.  This must be
        present for the trait to show up as a context item though the value
        may change over time as the item gets bound to different names.
    context_order: A float value
        Indicates the position for the item within this context.  All
        dynamically contributed context items are sorted by ascending order
        of this value using the standard list sort function.
    is_context: A boolean value
        True if the item is itself a context.
"""

# Standardlibrary imports
import logging

# Local imports
from .binding import Binding
from .context import Context
from .exception import OperationNotSupportedError


# Setup a logger for this module.
logger = logging.getLogger(__name__)


[docs]class DynamicContext(Context): """A framework that dynamically determines the contents of a context at the time of interaction with the contents rather than at the time a context class is written. It should be noted that this capability is explicitly different from contexts that look at another container to determine their contents, such as a file system context! """ ########################################################################## # 'Context' interface. ########################################################################## ### protected interface ################################################## def _is_bound(self, name): """Is a name bound in this context?""" item = self._get_contributed_context_item(name) result = item != (None, None) return result def _is_context(self, name): """Returns True if a name is bound to a context.""" item = self._get_contributed_context_item(name) if item != (None, None): obj, trait = item result = trait.is_context is True else: result = False return result def _list_bindings(self): """Lists the bindings in this context.""" result = [ Binding(name=n, obj=o, context=self) for n, o, t in self._get_contributed_context_items() ] return result def _list_names(self): """Lists the names bound in this context.""" result = [n for n, o, t in self._get_contributed_context_items()] return result def _lookup(self, name): """Looks up a name in this context.""" item = self._get_contributed_context_item(name) if item != (None, None): obj, trait = item result = obj else: result = None return result def _rename(self, old_name, new_name): """Renames an object in this context.""" item = self._get_contributed_context_item(old_name) if item != (None, None): obj, trait = item trait.context_name = new_name else: raise ValueError('Name "%s" not in context', old_name) def _unbind(self, name): """Unbinds a name from this context.""" # It is an error to try to unbind any contributed context items item = self._get_contributed_context_item(name) if item != (None, None): raise OperationNotSupportedError( "Unable to unbind " + "built-in with name [%s]" % name ) ########################################################################## # 'DynamicContext' interface. ########################################################################## ### protected interface ################################################## def _get_contributed_context_item(self, name): """If the specified name matches a contributed context item then returns a tuple of the item's current value and trait definition (in that order.) Otherwise, returns a tuple of (None, None). """ result = (None, None) for n, o, t in self._get_contributed_context_items(): if n == name: result = (o, t) return result def _get_contributed_context_items(self): """Returns an ordered list of items to be treated as part of our context. Each item in the list is a tuple of its name, object, and trait definition (in that order.) """ # Our traits that get treated as context items are those that declare # themselves via metadata on the trait definition. filter = {"context_name": lambda v: v is not None and len(v) > 0} traits = self.traits(**filter) # Sort the list of context items according to the name of the item. traits = [(t.context_order, n, t) for n, t in traits.items()] traits.sort() # Convert these trait definitions into a list of name and object tuples result = [ (t.context_name, getattr(self, n), t) for order, n, t in traits ] return result