Source code for psi.core.enaml.contribution

import logging
log = logging.getLogger(__name__)

import importlib.util
import re

from atom.api import Bool, Str
import enaml
from enaml.core.api import Declarative, d_


MANIFEST_CACHE = {}
SEARCH_CACHE = {}


[docs] class ManifestNotFoundError(ImportError): pass
[docs] def load_manifest(manifest_path): module_name, manifest_name = manifest_path.rsplit('.', 1) with enaml.imports(): try: module = importlib.import_module(module_name) except ModuleNotFoundError as e: if e.name != module_name: raise raise ManifestNotFoundError() from e try: return getattr(module, manifest_name) except AttributeError as e: err = f'{manifest_path} not found.' raise ManifestNotFoundError() from e
[docs] def find_manifest_class(obj): cls = obj.__class__ if cls in MANIFEST_CACHE: return MANIFEST_CACHE[cls] search = [] for c in cls.mro(): base_module = c.__module__.split('.', 1)[0] if base_module in ('atom', 'enaml', 'builtins'): continue search.append(f'{c.__module__}.{c.__name__}Manifest') search.append(f'{c.__module__}_manifest.{c.__name__}Manifest') search.append('psi.core.enaml.manifest.PSIManifest') log.debug(f'Attempting to locate manifest for %s from candidates %s', cls.__name__, '\n ... '.join([''] + search)) for location in search: if location in SEARCH_CACHE: if SEARCH_CACHE[location] is not None: MANIFEST_CACHE[cls] = manifest = SEARCH_CACHE[location] return manifest else: continue try: MANIFEST_CACHE[cls] = manifest = load_manifest(location) SEARCH_CACHE[location] = manifest log.debug('... Found manifest at %s', location) return manifest except ManifestNotFoundError as e: SEARCH_CACHE[location] = None # I'm not sure this can actually happen anymore since it should return # the base `PSIManifest` class at a minimum. m = f'Could not find manifest for {cls.__module__}.{cls.__name__}' raise ManifestNotFoundError(m)
[docs] class PSIContribution(Declarative): #: Name of contribution. Should be unique among all contributions for that #: subclass as it is typically used by the plugins to find a particular #: contribution. name = d_(Str()).tag(metadata=True) #: Label of contribution. Not required, but a good idea as it affects how #: the contribution may be represented in the user interface. label = d_(Str()).tag(metadata=True) #: Flag indicating whether item has already been registered. registered = Bool(False) def _default_name(self): # Provide a default name if none is specified TODO: make this mandatory # (i.e., no default?) if self.parent is not None: return self.parent.name + '.' + self.__class__.__name__ return self.__class__.__name__ def _default_label(self): return self.name.replace('_', ' ')
[docs] @classmethod def valid_name(self, label): ''' Can be used to convert a label provided to a valid name ''' # TODO: Get rid of this? return re.sub(r'\W|^(?=\d)', '_', label)
[docs] def find_manifest_class(self): return find_manifest_class(self)
[docs] def load_manifest(self, workbench): if self.registered: return try: manifest_class = self.find_manifest_class() manifest = manifest_class(contribution=self) workbench.register(manifest) self.registered = True m = 'Loaded manifest for contribution %s (%s) with ID %r' log.debug(m, self.name, manifest_class.__name__, manifest.id) except ManifestNotFoundError: m = 'No manifest defined for contribution %s' log.warn(m, self.name) except ValueError as e: m = f'Manifest "{manifest.id}" for plugin "{self.name}" already registered.' raise ImportError(m) from e