Create and use a drivers

Every driver is defined by a string in the v_driver.txt file. Historically, they are defined as:

group:driver

although such a naming scheme is not necessary.

The drivers can be advertised from multiple packages using the vhc.drivers entry point group. Each entry of the group associate a string, e.g. module:callable, with a callable that must be compatible with

libvhc.drivers.driver_signature(vcheck, path, argv, *args, **kwargs)[source]

Prototype of a function implementing a VHC driver.

Parameters:
vcheck : instance of VCheck

store the recipe name and the check currently executing

path : string

path provided to the vhc executable.

argv : list of strings

remaining of the command line

args : list

positional arguments. Not used when calling from vhc

kwargs : dictionary

keyword arguments. Not used when calling from vhc

Here we illustrate various ways to create a typical driver. In Plug new drivers we will show how to to advertise them.

Anatomy of a driver

The majority of drivers act, on their core, on single fits files and in general have this structure:

  1. Setup: get the file name template for the recipe name, tell the html renderer the expected number of files per ifu, get the configuration and the logger, etc.
  2. Collect the fits files for the required recipe
  3. For each file, execute the proper cure tool
  4. Collect standard output and error and log them into the appropriate places
  5. Communicate to the html renderer the success or failure of the check
  6. Deal smoothly with exceptions: they should be logged and notified to the them renderer, but they should not cause vhc to exit

Factory functions

Since the above pattern is very common, we provide the module libvhc.factories, that provides three classes that automatize the creation of a driver.

Simple commands: command_line_factory()

Simple commands, that don’t require any external information, can be easily setup providing a string with the command to execute.

Example: common:exptime driver

# in common.py
command = "checkheader -E -r {recipe} {fname}"
# ``exptime`` is the callable implementing the driver
exptime = command_line_factory(command)

The variables within {...} are expanded in the body of the factory as the driver is executed. The available variables are

recipe recipe at hand
driver driver at hand
fname name of the file to test
ifuid id of the ifu from the fname header
headkey instruct cure to write in the fits headers the version of VHC and whether the check is successful or not

If by chance your command needs to have a set of curly brackets, you can escape them doubling them {{...}}.

Not so simple commands: command_func_factory()

Sometimes you need to build the string programmatically, e.g. reading information for some configuration file, do some computation, etc. This factory allow to easily do this

Example: common:check_overscan driver

# in common.py
from astropy.io import fits

import libvhc.reference_file_parser as vhcrfp
import libvhc.utils as vhcutils
[...]
def _check_overscan_cmd(vcheck, fname, log, conf):
    """Build the call for the overscan check

    Parameters
    ----------
    vcheck : instance of :class:`~libvhc.VCheck`
        store the recipe name and the check currently
        executing
    fname : string
        name of the file to check
    log : instance of :class:`logging.LoggerAdapter`
        logger
    conf : instance of :class:`configparser.ConfigParser`
        configuration object

    Returns
    -------
    cmd : string
        string of the command to execute
    """
    # get the overscan reference value(s) from configuration file
    header = fits.getheader(fname)
    spectid = vhcutils.get_specid(header)
    channel = vhcutils.get_channel(header)
    amplifier = vhcutils.get_amplifier(header)
    vals = vhcrfp.picker("overscan").get_value(spectid, channel, amplifier)
    mean, stddev, dev_mean, dev_stddev = vals

    # build the checkheader call
    cmd_str = "checkheader -O -r {recipe}"
    # expected mean and standard deviation
    cmd_str += " --ovc_mean {} --ovc_stddev {}".format(mean, stddev)
    # maximum deviation from them
    cmd_str += " --max_ovc_dev_mean {} --max_ovc_dev_rms {}".format(dev_mean,
                                                                    dev_stddev)

    cmd_str += " {fname}"

    return cmd_str

# ``check_overscan`` is the callable implementing the driver
check_overscan = factories.command_func_factory(_check_overscan_cmd)

Non standard drivers function_factory()

In case the standard checks do not suite you, we also provide a lower level factory, which loop through the fits files and call the provided function on each of the files. But you have to implement yourself the communication the success or failure of the checks with the output files and the html renderer.

Example: foobar driver

# in foo.py
import subprocess as sp

import libvhc.loggers as vhclog
import libvhc.utils as vhcutils

def _bar(vcheck, fname, lpath):
    "run ls on all files"
    log = vhclog.getLogger(fname[:lpath-1])  # vhc logger
    ifuid = vhcutils.get_ifuid(fname)
    self.format_dic['ifuid'] = ifuid

    try:
        p = sp.Popen(["ls", "-l", fname], stdout=sp.PIPE, stderr=sp.PIPE)
        stdout, stderr = p.communicate()
        log.info(stdout)  # will got to the ``log.txt`` file
        html_success = "success"
        msg = 'it worked'
        if stderr:  # if there is some error appears on standard error
            # will got to the ``log.txt`` and the ``v_results.txt`` file
            log.error(stderr)
            html_success = "fail"
            msg = 'no, it did not'
    except Exception as e:
        # catch errors?
        [...]

    # tell the html renderer whether the test worked or not
    vhchtml.add_ifu_test(vcheck, ifuid, msg, html_success)
    [...]

bar = factories.function_factory(_bar)