Source code for napari_plugin_engine.implementation

import inspect
import sys
from typing import Any, Callable, Optional


[docs]class HookImplementation: """A class to encapsulate hook implementations.""" TAG_SUFFIX = "_impl" def __init__( self, function: Callable, plugin: Optional[Any] = None, plugin_name: Optional[str] = None, hookwrapper: bool = False, optionalhook: bool = False, tryfirst: bool = False, trylast: bool = False, specname: str = '', enabled: bool = True, ): self.function = function self.argnames, self.kwargnames = varnames(self.function) self.plugin = plugin self.plugin_name = plugin_name self.hookwrapper = hookwrapper self.optionalhook = optionalhook self.tryfirst = tryfirst self.trylast = trylast self._specname = specname self.enabled = enabled @classmethod def format_tag(cls, project_name): return project_name + cls.TAG_SUFFIX @property def opts(self) -> dict: # legacy return { x: getattr(self, x) for x in [ 'hookwrapper', 'optionalhook', 'tryfirst', 'trylast', 'specname', ] } def __repr__(self) -> str: # these are all False by default truthy = [ attr for attr in ('hookwrapper', 'optionalhook', 'tryfirst', 'trylast') if getattr(self, attr) ] suffix = (' ' + " ".join(truthy)) if truthy else '' return ( f"<HookImplementation plugin={self.plugin_name!r}" f" spec={self.specname!r}{suffix}>" ) def __call__(self, *args): return self.function(*args) @property def specname(self) -> str: return self._specname or self.function.__name__
[docs]class HookSpecification: """A class to encapsulate hook specifications.""" TAG_SUFFIX = "_spec" def __init__( self, namespace: Any, name: str, *, firstresult: bool = False, historic: bool = False, warn_on_impl: Optional[Warning] = None, ): self.namespace = namespace self.name = name self.function = getattr(namespace, name) self.argnames, self.kwargnames = varnames(self.function) for reserved in ('_plugin', '_skip_impls'): if reserved in self.argnames: raise ValueError( f'Hook specifications may not have argument: "{reserved}".' ) self.firstresult = firstresult self.historic = historic self.warn_on_impl = warn_on_impl @classmethod def format_tag(cls, project_name): return project_name + cls.TAG_SUFFIX @property def opts(self) -> dict: # legacy return { 'firstresult': self.firstresult, 'historic': self.historic, 'warn_on_impl': self.warn_on_impl, } def __repr__(self) -> str: # these are all False by default truthy = [ attr for attr in ('firstresult', 'historic', 'warn_on_impl') if getattr(self, attr) ] suffix = (' ' + " ".join(truthy)) if truthy else '' return ( f"<HookSpecification {self.name!r} args={self.argnames!r}{suffix}>" )
# TODO: can this be improved? def varnames(func): """Return tuple of positional and keywrord argument names for a function, method, class or callable. In case of a class, its ``__init__`` method is considered. For methods the ``self`` parameter is not included. """ cache = getattr(func, "__dict__", {}) try: return cache["_varnames"] except KeyError: pass if inspect.isclass(func): try: func = func.__init__ except AttributeError: return (), () elif not inspect.isroutine(func): # callable object? try: func = getattr(func, "__call__", func) except Exception: return (), () try: # func MUST be a function or method here or we won't parse any args spec = inspect.getfullargspec(func) except TypeError: return (), () args, defaults = tuple(spec.args), spec.defaults if defaults: index = -len(defaults) args, kwargs = args[:index], tuple(args[index:]) else: kwargs = () # strip any implicit instance arg # pypy3 uses "obj" instead of "self" for default dunder methods _PYPY3 = hasattr(sys, "pypy_version_info") and sys.version_info.major == 3 implicit_names = ("self",) if not _PYPY3 else ("self", "obj") if args: if inspect.ismethod(func) or ( "." in getattr(func, "__qualname__", ()) and args[0] in implicit_names ): args = args[1:] try: cache["_varnames"] = args, kwargs except TypeError: pass return args, kwargs