Source code for napari_plugin_engine.exceptions

import logging
from enum import Enum
from types import TracebackType
from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Type, Union

from .dist import standard_metadata

if TYPE_CHECKING:
    from .manager import PluginManager  # noqa: F401


ExcInfoTuple = Tuple[Type[Exception], Exception, Optional[TracebackType]]


# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions
class Empty(Enum):
    token = 0


_empty = Empty.token


[docs]class PluginError(Exception): """Base class for exceptions relating to plugins. Parameters ---------- message : str, optional An optional error message, by default '' namespace : Optional[Any], optional The python object that caused the error, by default None cause : Exception, optional Exception that caused the error. Same as ``raise * from``. by default None """ _record: List['PluginError'] = [] def __init__( self, message: str = '', *, plugin: Optional[Any] = None, plugin_name: Optional[str] = None, cause: Optional[BaseException] = None, ): self.plugin = plugin self.plugin_name = plugin_name if not message: name = plugin_name or getattr(plugin, '__name__', str(id(plugin))) message = f'Error in plugin "{name}"' if cause: message += f': {cause}' super().__init__(message) self.__cause__ = cause # store all PluginError instances. can be retrieved with get() PluginError._record.append(self)
[docs] @classmethod def get( cls, *, plugin: Union[Any, Empty] = _empty, plugin_name: Union[str, Empty] = _empty, error_type: Union[Type['PluginError'], Empty] = _empty, ) -> List['PluginError']: """Return errors that have been logged, filtered by parameters. Parameters ---------- manager : PluginManager, optional If provided, will restrict errors to those that are owned by ``manager``. plugin_name : str If provided, will restrict errors to those that were raised by ``plugin_name``. error_type : Exception If provided, will restrict errors to instances of ``error_type``. Returns ------- list of PluginError A list of PluginErrors that have been instantiated during this session that match the provided parameters. Raises ------ TypeError If ``error_type`` is provided and is not an exception class. """ errors: List['PluginError'] = [] for error in cls._record: if plugin is not _empty and error.plugin != plugin: continue if plugin_name is not _empty and error.plugin_name != plugin_name: continue if error_type is not _empty: import inspect if not ( inspect.isclass(error_type) and issubclass(error_type, BaseException) ): raise TypeError( "The `error_type` argument must be an exception class" ) if not isinstance(error.__cause__, error_type): continue errors.append(error) return errors
def format(self, package_info: bool = True): msg = f'PluginError: {self}' if self.__cause__: msg = msg.replace(str(self.__cause__), '').strip(": ") + "\n" cause = repr(self.__cause__).replace("\n", "\n" + " " * 13) msg += f' Cause was: {cause}' # show the exact file and line where the error occured cause_tb = self.__cause__.__traceback__ if cause_tb: while True: if not cause_tb.tb_next: break cause_tb = cause_tb.tb_next msg += f'\n in file: {cause_tb.tb_frame.f_code.co_filename}' msg += f'\n at line: {cause_tb.tb_lineno}' else: msg += "\n" if package_info and self.plugin: try: meta = standard_metadata(self.plugin) meta.pop('license', None) meta.pop('summary', None) if meta: msg += "\n" + "\n".join( [ f'{k: >11}: {v}' for k, v in sorted(meta.items()) if v ] ) except ValueError: pass msg += '\n' return msg
[docs] def log( self, package_info: bool = True, logger: Union[logging.Logger, None, str] = None, level: int = logging.ERROR, ): """Log this error with metadata, optionally provide logger and level. Parameters ---------- package_info : bool, optional If true, will include package metadata in log, by default True logger : logging.Logger or str, optional A Logger instance or name of a logger to use, by default None level : int, optional The logging level to use, by default logging.ERROR """ if not isinstance(logger, logging.Logger): logger = logging.getLogger(logger) logger.log(level, self.format(package_info=package_info))
[docs] def info(self) -> ExcInfoTuple: """Return info as would be returned from sys.exc_info().""" return (self.__class__, self, self.__traceback__)
[docs]class HookCallError(PluginError): """If a hook is called incorrectly. Usually this results when a HookCaller is called without the appropriate arguments. """
[docs]class PluginImportError(PluginError, ImportError): """Plugin module is unimportable."""
[docs]class PluginRegistrationError(PluginError): """If an unexpected error occurs during registration."""
[docs]class PluginImplementationError(PluginError): """Base class for errors pertaining to a specific hook implementation.""" def __init__(self, hook_implementation, msg=None, cause=None): plugin = hook_implementation.plugin plugin_name = hook_implementation.plugin_name specname = hook_implementation.specname if not msg: msg = f"Error in plugin '{plugin_name}', hook '{specname}'" if cause: msg += f": {str(cause)}" super().__init__( msg, plugin=plugin, plugin_name=plugin_name, cause=cause, )
[docs]class PluginValidationError(PluginImplementationError): """When a plugin implementation fails validation."""
[docs]class PluginCallError(PluginImplementationError): """Raised when an error is raised when calling a plugin implementation."""