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 .. autofunction:: libvhc.drivers.driver_signature :noindex: Here we illustrate various ways to create a typical driver. In :ref:`plug_driver` 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 .. _factories: Factory functions ----------------- Since the above pattern is very common, we provide the module :mod:`libvhc.factories`, that provides three classes that automatize the creation of a driver. Simple commands: :func:`~libvhc.factories.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 ********************************** .. code-block:: python # 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: :func:`~libvhc.factories.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 ***************************************** .. code-block:: python # 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 :func:`~libvhc.factories.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 ************************** .. code-block:: python # 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)