Source code for apstools.plans.command_list

"""
nscan plan
+++++++++++++++++++++++++++++++++++++++

.. autosummary::

   ~CommandFileReadError
   ~command_list_as_table
   ~execute_command_list
   ~get_command_list
   ~parse_Excel_command_file
   ~parse_text_command_file
   ~register_command_handler
   ~run_command_file
   ~summarize_command_file
"""

import logging
import os
import pyRestTable

from bluesky import plan_stubs as bps

from .. import utils

logger = logging.getLogger(__name__)
logger.info(__file__)


[docs]class CommandFileReadError(IOError): """ Exception when reading a command file. .. index:: Bluesky Exception; CommandFileReadError """
[docs]def command_list_as_table(commands, show_raw=False): """ format a command list as a pyRestTable.Table object """ tbl = pyRestTable.Table() tbl.addLabel("line #") tbl.addLabel("action") tbl.addLabel("parameters") if show_raw: # only the developer might use this tbl.addLabel("raw input") for command in commands: action, args, line_number, raw_command = command row = [line_number, action, ", ".join(map(str, args))] if show_raw: row.append(str(raw_command)) tbl.addRow(row) return tbl
[docs]def execute_command_list(filename, commands, md=None): """ plan: execute the command list .. index:: Bluesky Plan; execute_command_list The command list is a tuple described below. * Only recognized commands will be executed. * Unrecognized commands will be reported as comments. See example implementation with APS USAXS instrument: https://github.com/APS-USAXS/ipython-usaxs/blob/5db882c47d935c593968f1e2144d35bec7d0181e/profile_bluesky/startup/50-plans.py#L381-L469 PARAMETERS filename *str* : Name of input text file. Can be relative or absolute path, such as "actions.txt", "../sample.txt", or "/path/to/overnight.txt". commands *[command]* : List of command tuples for use in ``execute_command_list()`` where command *tuple* : (action, OrderedDict, line_number, raw_command) action *str* : names a known action to be handled parameters *list* : List of parameters for the action. The list is empty of there are no values line_number *int* : line number (1-based) from the input text file raw_command *str* or *[str]* : contents from input file, such as: ``SAXS 0 0 0 blank`` SEE ALSO .. autosummary:: ~execute_command_list ~register_command_handler ~run_command_file ~summarize_command_file ~parse_Excel_command_file ~parse_text_command_file *new in apstools release 1.1.7* """ full_filename = os.path.abspath(filename) if len(commands) == 0: yield from bps.null() return text = f"Command file: {filename}\n" text += str(command_list_as_table(commands)) print(text) for command in commands: action, args, i, raw_command = command print(f"file line {i}: {raw_command}") _md = {} _md["full_filename"] = full_filename _md["filename"] = filename _md["line_number"] = i _md["action"] = action # note: args is shorter than parameters, means the same thing here _md["parameters"] = args _md.update(md or {}) # overlay with user-supplied metadata action = action.lower() if action == "tune_optics": # example: yield from tune_optics(md=_md) emsg = ( f"This is an example. action={raw_command}." " Must define your own execute_command_list() function" ) logger.warn(emsg) yield from bps.null() else: print(f"no handling for line {i}: {raw_command}")
[docs]def get_command_list(filename): """ return command list from either text or Excel file SEE ALSO .. autosummary:: ~execute_command_list ~get_command_list ~register_command_handler ~run_command_file ~summarize_command_file ~parse_Excel_command_file ~parse_text_command_file *new in apstools release 1.1.7* """ full_filename = os.path.abspath(filename) if not os.path.exists(full_filename): raise IOError(f"file not found: {filename}") try: commands = parse_Excel_command_file(filename) except (ValueError, utils.ExcelReadError): try: commands = parse_text_command_file(filename) except ValueError as exc: raise CommandFileReadError( f"could not read {filename} as command list file: {exc}" ) return commands
class _HandlerRegistrar: """ Internal use, allows redefinition of execute_command_list(). """ command = None
[docs]def parse_Excel_command_file(filename): """ parse an Excel spreadsheet with commands, return as command list TEXT view of spreadsheet (Excel file line numbers shown):: [1] List of sample scans to be run [2] [3] [4] scan sx sy thickness sample name [5] FlyScan 0 0 0 blank [6] FlyScan 5 2 0 blank PARAMETERS filename *str* : Name of input Excel spreadsheet file. Can be relative or absolute path, such as "actions.xslx", "../sample.xslx", or "/path/to/overnight.xslx". RETURNS list of commands *[command]* : List of command tuples for use in ``execute_command_list()`` RAISES FileNotFoundError if file cannot be found SEE ALSO .. autosummary:: ~get_command_list ~register_command_handler ~run_command_file ~summarize_command_file ~parse_text_command_file *new in apstools release 1.1.7* """ full_filename = os.path.abspath(filename) if not os.path.exists(full_filename): raise FileNotFoundError(full_filename) xl = utils.ExcelDatabaseFileGeneric(full_filename) commands = [] if len(xl.db) > 0: for i, row in enumerate(xl.db.values()): action, *values = list(row.values()) # trim off any None values from end while len(values) > 0: if values[-1] is not None: break values = values[:-1] commands.append((action, values, i + 1, list(row.values()))) return commands
[docs]def parse_text_command_file(filename): """ parse a text file with commands, return as command list * The text file is interpreted line-by-line. * Blank lines are ignored. * A pound sign (#) marks the rest of that line as a comment. * All remaining lines are interpreted as commands with arguments. Example of text file (no line numbers shown):: #List of sample scans to be run # pound sign starts a comment (through end of line) # action value mono_shutter open # action x y width height uslits 0 0 0.4 1.2 # action sx sy thickness sample name FlyScan 0 0 0 blank FlyScan 5 2 0 "empty container" # action sx sy thickness sample name SAXS 0 0 0 blank # action value mono_shutter close PARAMETERS filename *str* : Name of input text file. Can be relative or absolute path, such as "actions.txt", "../sample.txt", or "/path/to/overnight.txt". RETURNS list of commands *[command]* : List of command tuples for use in ``execute_command_list()`` RAISES FileNotFoundError if file cannot be found SEE ALSO .. autosummary:: ~execute_command_list ~get_command_list ~register_command_handler ~run_command_file ~summarize_command_file ~parse_Excel_command_file *new in apstools release 1.1.7* """ full_filename = os.path.abspath(filename) if not os.path.exists(full_filename): raise FileNotFoundError(full_filename) with open(full_filename, "r") as fp: buf = fp.readlines() commands = [] for i, raw_command in enumerate(buf): row = raw_command.strip() if row == "" or row.startswith("#"): continue # comment or blank else: # command line action, *values = utils.split_quoted_line(row) commands.append((action, values, i + 1, raw_command.rstrip())) return commands
[docs]def register_command_handler(handler=None): """ Define the function called to execute the command list PARAMETERS handler *obj* : Reference of the ``execute_command_list`` function to be used from :func:`~apstools.plans.run_command_file()`. If ``None`` or not provided, will reset to :func:`~apstools.plans.execute_command_list()`, which is also the initial setting. SEE ALSO .. autosummary:: ~execute_command_list ~get_command_list ~register_command_handler ~summarize_command_file ~parse_Excel_command_file ~parse_text_command_file *new in apstools release 1.1.7* """ COMMAND_LIST_REGISTRY.command = handler or execute_command_list
[docs]def run_command_file(filename, md=None): """ plan: execute a list of commands from a text or Excel file .. index:: Bluesky Plan; run_command_file * Parse the file into a command list * yield the command list to the RunEngine (or other) SEE ALSO .. autosummary:: ~execute_command_list ~get_command_list ~register_command_handler ~summarize_command_file ~parse_Excel_command_file ~parse_text_command_file *new in apstools release 1.1.7* """ _md = dict(command_file=filename) _md.update(md or {}) commands = get_command_list(filename) yield from COMMAND_LIST_REGISTRY.command(filename, commands, md=_md)
[docs]def summarize_command_file(filename): """ print the command list from a text or Excel file SEE ALSO .. autosummary:: ~execute_command_list ~get_command_list ~run_command_file ~parse_Excel_command_file ~parse_text_command_file *new in apstools release 1.1.7* """ commands = get_command_list(filename) print(f"Command file: {filename}") print(command_list_as_table(commands))
COMMAND_LIST_REGISTRY = _HandlerRegistrar() COMMAND_LIST_REGISTRY.command = execute_command_list # ----------------------------------------------------------------------------- # :author: Pete R. Jemian # :email: jemian@anl.gov # :copyright: (c) 2017-2022, UChicago Argonne, LLC # # Distributed under the terms of the Creative Commons Attribution 4.0 International Public License. # # The full license is in the file LICENSE.txt, distributed with this software. # -----------------------------------------------------------------------------