shell module

Base classes for implementing CLI utilities and service daemons.

The following concepts are “core” to this module:

  • control: A control encapsulates the logic for “controlling” some other hardware or software resource. It is implemented by subclassing Control. Examples of such resources include:

    • Actuators in a home automation context, optionally coupled to one or more sensors.

    • Remote file systems to mount and unmount using various credentials from desktop keyrings, TPM2 hardware modules and more.

    • A mediacenter software running on the same Linux machine, e.g. Kodi.

    • Any REST-compliant service accessible on the network.

  • action: A control defines one or more actions it can perform. Supported actions must be listed in an Enum class.

  • mode: A control can optionally support different modes. Each mode can make the control behave differently for a given action. For instance, if controlling a curtain in a home automation context, a mode prevent-down could be set. If action roll-down is requested with the mode active, the curtain actuator would not be triggered.

For defining actions, a Control subclass must implement an Enum class as follows:

class Action(enum.Enum):
    ACTION_A = 'actiona'
    ACTION_B = 'actionb'

… where ACTION_A and ACTION_B are constant names to be used in the Python code and actiona and actionb are the corresponding action names to be used on the command line if implementing a CLI utility.

Similarily, for defining modes, a Control subclass must implement an Enum class as follows:

class Mode(enum.Enum):
    MODE_X = 'modex'
    MODE_Y = 'modey'

… where MODE_X and MODE_Y are constant names to be used in the Python code and modex and modey are the corresponding mode names to be used on the command line if implementing a CLI utility.

The classes ControlShell and ServiceControlShell serve as Python context managers for common resources such as message buffers and process executors. By using them, a control can delegate the lifecycle management of those resources. Furthermore, the shell classes handle output to stdout and/or log files.

class ctlbase.shell.Control(loop, processExecutor=None, envConfig=None, actionType: Enum | None = None, modeType: Enum | None = None, msgBuffer=None, ns=True, jsonIndent=4)

Base class for a control.

It provides a uniform interface for various kinds of controls. Eventually, composite controls for several of these resources can be created, preserving the uniform interface. Furthermore, a set of CLI utilities can be created as a shell for these controls, all sharing common command line arguments, as well as plain text and JSON output. See the ControlShell class for details.

Subclasses must call the super constructor __init__().

__init__(loop, processExecutor=None, envConfig=None, actionType: Enum | None = None, modeType: Enum | None = None, msgBuffer=None, ns=True, jsonIndent=4)
Parameters:
  • processExecutor (ctlbase.process.ProcessExecutor | None) – A process executor for executing (shell) commands in a subprocess. None if the control does not need this facility.

  • envConfig (tuple[str, dict] | str | bool | None) –

    An environment configuration in one of several possible forms:

    • A tuple of an absolute file path and the corresponding parsed dict, if the configuration file has been parsed already.

    • The path or file name of the Bash-style configuration file to be parsed. See FileDiscovery for the discovery algorithm.

    • True for discovering a configuration file based on the name of the caller. See FileDiscovery for the discovery algorithm.

    • None if the control does not need an environment configuration.

    See BashConfig for the supported configuration syntax.

  • actionType (Enum) – The Enum class representing the supported actions of this control.

  • modeType (Enum | None) – The Enum class representing the supported modes of this control. None if the control does not support different modes.

  • msgBuffer (MessageBuffer | True | None) – The message buffer to which all the messages of the control shall be written. True if one should be auto-created. None if the control does not need this facility.

  • ns (str | True | None) – The message namespace to use when writing messages to the message buffer. True if the namespace shall be derived from the caller name. See CallerDiscovery for the discovery algorithm. If msgBuffer is set to True, the namespace given in ns will be set as the default namespace of the auto-created message buffer.

  • jsonIndent (int) – A number of spaces to use as an indentation when creating JSON output.

abstract async getStatusTuplesEn() list

Returns an Iterable of one or more “status tuples”.

One status tuple represents one status property of the control. A property can be anything worth reporting, e.g. the state of a sensor or the mount state of a remote file system.

Consider the following example:

('POSITION', Position.UP, 'Position of the roll curtain:', 'up')

A status tuple contains the following str elements, in that sequence:

  1. The identifer of the status property, unique to the control. It must contain only ASCII characters, without any spaces. POSITION in the example above.

  2. The “raw” value of the status property. “Raw” means “without any conversion for readability”. Hence, the value can be of any Python type, be it a scalar type like int or str, be it a list or dict or a custom object. Position.UP in the example above, suggesting that Position is an Enum class defined elsewhere.

  3. The human-readable description of the status property. Position of the roll curtain: in the example above.

  4. The human-readable value of the status property. up in the example above.

Returns:

An Iterable of status tuples for this control.

Return type:

Iterable

async getStatusJson(statusTuplesEn=None)

Returns the control status in JSON format.

Parameters:

statusTuplesEn (tuple[str]) – The status tuples to be rendered in JSON format, as returned by getStatusTuplesEn(). Only the first two elements of each status tuple are evaluated.

Returns:

The control status in JSON format.

Rytpe:

str

async getStatusTextEn(statusTuplesEn=None, firstColumnWidth=24)

Returns the control status as a human-readable text, without any control characters, in the language en.

The output contains two text columns:

  1. The human-readable description of an individual status property.

  2. Second column: The corresponding human-readable value.

Parameters:
  • statusTuplesEn (tuple[str]) – The status tuples to be rendered as plain text, as returned by getStatusTuplesEn(). Only the last two elements of each status tuple are evaluated.

  • firstColumnWidth (int) – The number of characters to which the first column should be padded.

Returns:

The control status as a human-readable text.

Rytpe:

str

close()

Closes an auto-created message buffer. This holds if msgBuffer was set to True in the constructor.

class ctlbase.shell.ControlShell(parser, threadExecutor=None, processExecutor=None, envConfigPath=True, msgBuffer=None, lng=True)

A context manager for creating CLI utilities. Creates and lifecyle-manages various resources as described in __init__().

When the context manager exits, the following steps are taken:

  • If the context manager exits due to an exception of type MessageError or ValueError, the corresponding error message is added to the message buffer.

  • Any other exception bubbles up, i.e. is not handled gracefully.

  • An auto-created thread pool executor is auto-closed. This holds if threadExecutor was set to True in the constructor.

  • An auto-created process executor is auto-closed. This holds if processExecutor was set to True in the constructor.

  • The close function or coroutine function of the control set via setControl() is called if one exists.

  • The close function or coroutine function of this context manager is called if one exists. Useful for extensions of ControlShell that control resources of their own.

  • Messages in the message buffer are written to stdout as follows:

    • If the -j | --json command line flag is given, all messages in the message buffer are written to stdout in JSON format. All remaining flags for output handling are ignored at this point. If the -j | --json command line flag is not given, plain text messages are written to stdout.

    • The language of plain text messages are determined by the lng value set in the constructor.

    • If the -v | --verbose command line flag is given, messages with severity VERBOSE and DEBUG are included in the plain text output.

    • Plain text messages are colored according to message severities. To suppress the coloring, set the isColoringDefault flag of the message buffer to False.

    • Any plain text string set in printOnExit() is written to stdout.

  • The message buffer is closed.

  • The context manager’s asyncio event loop is closed.

The process exit code is determined as follows:

  • 4 if the message buffer contains at least one message of severity ERROR.

  • 3 if the message buffer contains does not contain any message of severity ERROR but at last one with severity WARNING.

  • 0 if none of the aforementioned conditions are true.

static addStandardArgs(parser)

For uniform arguments of executable scripts, amends an argument parser with standard arguments.

  • -j | --json flag: Write JSON output to stdout. Handled by __exit__()

  • -v | --verbose flag: Write more verbose messages to stdout. Handled by __exit__().

  • -o | --oknodo flag: Suppress an error if the requested action’s target state is already reached. Will be passed as isOkNodo to operate().

  • --force flag: Force the action even if the target state is already reached. Will be passed as isForced to operate().

  • --noprompt flag: Do not prompt the user for any input, e.g. confirmations or passwords. Its inversion will be passed as mayPrompt to operate().

Parameters:

parser (ArgumentParser) – The argument parser to amend.

__init__(parser, threadExecutor=None, processExecutor=None, envConfigPath=True, msgBuffer=None, lng=True)

Creates a new control shell with the resources specified in the parameters.

Always creates a new asyncio event loop for the current thread.

Parameters:
  • parser (argparse.ArgumentParser | argparse.Namespace) – The parser or already parsed namespace with the command line arguments to evaluate.

  • threadExecutor (concurrent.futures.ThreadPoolExecutor | True | None) – A thread pool executor or True if one should be auto-created and lifecycle-managed. None if no thread pool executor is needed.

  • processExecutor (ctlbase.process.ProcessExecutor | True | None) – A process executor or True if one should be auto-created and lifecycle-managed. None if no process executor is needed.

  • envConfigPath (str | bool | None) – Absolute path or file name (to be auto-discovered) of an evironment configuration file to be parsed and provided as dict. True for auto-discovery based on the name of the using Python module. None if no environment configuration is needed.

  • msgBuffer (ctlbase.message.MessageBuffer | True | None) – A message buffer or True if one should be auto-created and lifecycle-managed. None if no message buffer is needed. Note that in this case, this context manager cannot automatically write output to stdout.

  • lng (str | True) – A two-letter language code for the messages to be written to stdout. Defaults to en if the requested language is not available. True if the default locale from the environment shall be taken.

threadExecutor

The thread pool executor lifecycle-managed by this context manager.

processExecutor

The process executor lifecycle-managed by this context manager.

envConfigPath

The absolute path of the environment configuration file parsed into envConfigDict.

envConfigDict

The dict holding the parsed environment configuration.

msgBuffer

The message buffer used by this context manager to write output to stdout.

lng

The two-letter language code used by this context manager to write output to stdout.

requestCleanup(signalNumber=None, stackFrame=None)

A Python signal handler injecting a CleaningUp exception into all running code in the current Python process.

The signature is defined in signal.signal.

setControl(control: Control)

Associates the given control with this context manager.

The effects are as follows:

  • The context manager’s message buffer is propagated to the control. If the context manager does not have a message buffer, the opposite takes place: The control’s message buffer is propagated to the context manager.

  • If the context manager’s message buffer does not yet have a default namespace, the namespace of the control is set as its default namespace.

  • When the context manager exits, the close coroutine of the control is called. This step is skipped if the control does not have a close coroutine.

printOnExit(outputStr)

In addition to this context manager’s message buffer, set a text that shall be printed to stdout when this context manager exits.

class ctlbase.shell.ServiceControlShell(parser, taskMonitor=None, processExecutor=None, envConfigPath=None, msgBuffer=None, lng=None, **kwargs)

A context manager for creating services, i.e. background daemons.

It adds the following features to the base class ControlShell:

  • Auto-creation and lifecycle management for a task monitor. It can be used to schedule service tasks in the background, i.e. in the context manager’s asyncio event loop.

  • Support for command line arguments common to services.

When the context manager exits, the following steps are taken in addition to base class ControlShell:

  • An auto-created task monitor is auto-closed. This holds if taskMonitor was set to True in the constructor.

static addStandardArgs(parser)

For uniform arguments of executable scripts, amends an argument parser with standard arguments.

  • --delay: Delay in milliseconds after which the systemd shall be notified ready. Handled by notifyReady().

  • -e | --errors: Maximum number of errors after which the service shall exit (with exit code 4). To be handled by classes extending ServiceControlShell.

  • -l | --log: Path to the service’s log file. Handled by __enter__().

  • --tswidth: Number of characters the timestamps in the log file should be padded to. Handled by __enter__().

  • --print flag: Print messages to stdout in addition to the log file. Handled by __enter__().

Parameters:

parser (ArgumentParser) – The argument parser to amend.

__init__(parser, taskMonitor=None, processExecutor=None, envConfigPath=None, msgBuffer=None, lng=None, **kwargs)

Arguments with the same name have the same meaning as with __init__().

Parameters:

taskMonitor (ctlbase.process.TaskMonitor | True | None) – A task monitor or True if one should be auto-created and lifecycle-managed. None if no task monitor is needed.

taskMonitor

The task manager lifecycle-managed by this context manager.

async notifyReady()

Sends a systemd ready notification.

Useful if the service is wrapped into a systemd service unit.