Source code for ymp.exceptions

"""Exceptions raised by YMP"""

import textwrap
from inspect import stack
from typing import Optional, Tuple

from click import ClickException, echo

from snakemake.exceptions import WorkflowError, RuleException

[docs]class YmpException(Exception): """Base class of all YMP Exceptions"""
[docs]class YmpPrettyException(YmpException, ClickException, WorkflowError): """Exception that does not lead to stack trace on CLI Inheriting from ClickException makes ``click`` print only the ``self.msg`` value of the exception, rather than allowing Python to print a full stack trace. This is useful for exceptions indicating usage or configuration errors. We use this, instead of `click.UsageError` and friends so that the exceptions can be caught and handled explicitly where needed. Note that click will call the ``show`` method on this object to print the exception. The default implementation from click will just prefix the ``msg`` with ``Error:``. FIXME: This does not work if the exception is raised from within the snakemake workflow as snakemake.snakemake catches and reformats exceptions. """ rule = None snakefile = None
[docs]class YmpLocateableError(YmpPrettyException): """Errors that have a file location to be shown Args: obj: The object causing the exception. Must have ``lineno`` and ``filename`` as these will be shown as part of the error message on the command line. msg: The message to display show_includes: Whether or not the "stack" of includes should be printed. """ def __init__(self, obj: object, msg: str, show_includes: bool = True) -> None: self.obj = obj self.stack = stack() if show_includes else [] super().__init__(msg)
[docs] def get_fileline(self) -> Tuple[str, int]: """Retrieve filename and linenumber from object associated with exception Returns: Tuple of filename and linenumber """ return getattr(self.obj, "filename"), getattr(self.obj, "lineno")
[docs] def show(self, file=None) -> None: super().show(file) fname, line = self.get_fileline() if fname: if line is None: echo(f"Problem occurred in {fname}:", file=file) else: echo(f"Problem occurred in line {line} of {fname}:", file=file) for entry in self.stack: if not entry.filename.endswith(".py") and not entry.filename.endswith("/ymp"): echo(f" while processing {entry.filename}:{entry.lineno}", file=file)
[docs]class YmpUsageError(YmpPrettyException): """General usage error"""
[docs]class YmpSystemError(YmpPrettyException): """Indicates problem running YMP with available system software"""
[docs]class YmpRuleError(YmpLocateableError): """Indicates an error in the rules files This could e.g. be a Stage or Environment defined twice. """
[docs]class YmpConfigError(YmpLocateableError): """Indicates an error in the ymp.yml config files Args: obj: Subtree of config causing error msg: The message to display key: Key indicating part of ``obj`` causing error exc: Upstream exception causing error """ def __init__(self, obj: object, msg: str, key: Optional[object]=None) -> None: super().__init__(obj, msg) self.key = key
[docs] def get_fileline(self): return self.obj.get_fileline(self.key)
[docs]class YmpStageError(YmpPrettyException): """Indicates an error in the requested stage stack """ def __init__(self, msg: str) -> None: super().__init__(textwrap.dedent(msg))
[docs] def show(self, file=None) -> None: echo(self.format_message(), err=True)
[docs]class YmpWorkflowError(YmpPrettyException): """Indicates an error during workflow execution E.g. failures to expand dynamic variables """