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-downcould be set. If actionroll-downis requested with the mode active, the curtain actuator would not be triggered.For defining actions, a
Controlsubclass must implement an Enum class as follows:class Action(enum.Enum): ACTION_A = 'actiona' ACTION_B = 'actionb'… where
ACTION_AandACTION_Bare constant names to be used in the Python code andactionaandactionbare the corresponding action names to be used on the command line if implementing a CLI utility.Similarily, for defining modes, a
Controlsubclass must implement an Enum class as follows:class Mode(enum.Enum): MODE_X = 'modex' MODE_Y = 'modey'… where
MODE_XandMODE_Yare constant names to be used in the Python code andmodexandmodeyare 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
ControlShellclass 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.Noneif 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
FileDiscoveryfor the discovery algorithm.Truefor discovering a configuration file based on the name of the caller. SeeFileDiscoveryfor the discovery algorithm.Noneif the control does not need an environment configuration.
See
BashConfigfor 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.Noneif 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.Trueif one should be auto-created.Noneif the control does not need this facility.ns¶ (str | True | None) – The message namespace to use when writing messages to the message buffer.
Trueif the namespace shall be derived from the caller name. SeeCallerDiscoveryfor the discovery algorithm. IfmsgBufferis set toTrue, the namespace given innswill 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:
The identifer of the status property, unique to the control. It must contain only ASCII characters, without any spaces.
POSITIONin the example above.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.UPin the example above, suggesting thatPositionis an Enum class defined elsewhere.The human-readable description of the status property.
Position of the roll curtain:in the example above.The human-readable value of the status property.
upin the example above.
- Returns:
An Iterable of status tuples for this control.
- Return type:
- 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:
The human-readable description of an individual status property.
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
- 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
MessageErroror 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
threadExecutorwas set toTruein the constructor.An auto-created process executor is auto-closed. This holds if
processExecutorwas set toTruein 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
ControlShellthat control resources of their own.Messages in the message buffer are written to stdout as follows:
If the
-j|--jsoncommand 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|--jsoncommand line flag is not given, plain text messages are written to stdout.The language of plain text messages are determined by the
lngvalue set in the constructor.If the
-v|--verbosecommand line flag is given, messages with severityVERBOSEandDEBUGare included in the plain text output.Plain text messages are colored according to message severities. To suppress the coloring, set the
isColoringDefaultflag of the message buffer toFalse.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:
4if the message buffer contains at least one message of severityERROR.3if the message buffer contains does not contain any message of severityERRORbut at last one with severityWARNING.0if none of the aforementioned conditions are true.
- static addStandardArgs(parser)
For uniform arguments of executable scripts, amends an argument parser with standard arguments.
-j|--jsonflag: Write JSON output to stdout. Handled by__exit__()-v|--verboseflag: Write more verbose messages to stdout. Handled by__exit__().-o|--oknodoflag: Suppress an error if the requested action’s target state is already reached. Will be passed asisOkNodotooperate().--forceflag: Force the action even if the target state is already reached. Will be passed asisForcedtooperate().--nopromptflag: Do not prompt the user for any input, e.g. confirmations or passwords. Its inversion will be passed asmayPrompttooperate().
- 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
Trueif one should be auto-created and lifecycle-managed.Noneif no thread pool executor is needed.processExecutor¶ (
ctlbase.process.ProcessExecutor| True | None) – A process executor orTrueif one should be auto-created and lifecycle-managed.Noneif 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.
Truefor auto-discovery based on the name of the using Python module.Noneif no environment configuration is needed.msgBuffer¶ (
ctlbase.message.MessageBuffer| True | None) – A message buffer orTrueif one should be auto-created and lifecycle-managed.Noneif 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
enif the requested language is not available.Trueif 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
CleaningUpexception 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
taskMonitorwas set toTruein 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 bynotifyReady().-e|--errors: Maximum number of errors after which the service shall exit (with exit code4). To be handled by classes extendingServiceControlShell.-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__().--printflag: 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 orTrueif one should be auto-created and lifecycle-managed.Noneif 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.