Source code for troika.hooks.base

"""Base hook definitions"""

import functools
import logging

from .. import ConfigurationError
from ..components import get_entrypoint

_logger = logging.getLogger(__name__)


class Hook:
    """Helper class to manage hooks

    Parameters
    ----------
    name: str
        Hook name
    """

    #: Entrypoint namespace
    _namespace = "troika.hooks"

    registered_hooks = {}

    @classmethod
    def declare(cls, func, name=None):
        """Register a hook

        Parameters
        ----------
        func: callable
            Hook specification function (code is discarded)
        name: str or None
            Name of the hook (default: ``func.__name__``)
        """
        if name is None:
            name = func.__name__
        hook = cls(name)
        functools.update_wrapper(hook, func)
        cls.registered_hooks[name] = hook
        return hook

    def __init__(self, name):
        self.name = name
        self._hooks = {}
        self._impl = None

    def __call__(self, *args, **kwargs):
        if self._impl is None:
            raise ValueError("Attempt to call a non-instantiated hook registry")
        _logger.debug("Executing %s hooks", self.name)
        results = []
        for funcname, func in self._impl:
            _logger.debug("Calling hook function %s", funcname)
            res = func(*args, **kwargs)
            results.append(res)
        return results

    def instantiate(self, hooks):
        """Select the requested hook implementations

        Parameters
        ----------
        hooks: list of str
            Requested hooks
        """
        hookfuncs = []
        for hookname in hooks:
            try:
                hookfunc = get_entrypoint(f"{self._namespace}.{self.name}", hookname)
            except ValueError:
                msg = f"Implementation {hookname!r} not found for {self.name} hook"
                raise ConfigurationError(msg)
            hookfuncs.append((hookname, hookfunc))
        self._impl = hookfuncs


[docs]@Hook.declare def at_startup(action, site, args): """Startup hook Parameters ---------- action: {"submit", "monitor", "kill"} Action that was requested site: :py:class:`troika.sites.base.Site` Selected site args: :py:class:`argparse.Namespace`-like Command-line arguments Returns ------- bool If True, interrupt the action """
[docs]@Hook.declare def pre_submit(site, script, output, dryrun): """Pre-submit hook Parameters ---------- site: :py:class:`troika.sites.base.Site` Selected site script: path-like Path to the script to be submitted output: path-like Path to the job output file dryrun: bool If True, do not do anything, only print actions """
[docs]@Hook.declare def post_kill(site, script, output, jid, cancel_status, dryrun): """Post-kill hook Parameters ---------- site: :py:class:`troika.sites.base.Site` Selected site script: path-like Path to the script file of the job being killed output: path-like or None Path to the job output file jid: str Job ID of the job being killed cancel_status: str CANCELLED, KILLED or TERMINATED dryrun: bool If True, do not do anything, only print actions """
[docs]@Hook.declare def at_exit(action, site, args, sts, logfile): """Exit hook Parameters ---------- action: {"submit", "monitor", "kill"} Action that was requested site: :py:class:`troika.sites.base.Site` Selected site args: :py:class:`argparse.Namespace`-like Command-line arguments sts: int Exit status logfile: path-like Path to the log file """