Daemon interface
A daemon is the source of authority for one or more mKTL items; when a client gets or sets an item value, that request is handled by the daemon. Exactly how that request gets handled is entirely up to the daemon code; there are no restrictions imposed by the mKTL protocol itself, it is up to the daemon to resolve any consistency issues or conflicting requests.
The default behavior of the mktl.Store is to act as a caching
key/value store: values come in, values go out, they may or may not persist
across restarts depending on the configuration. This behavior is rarely
sufficient for an application; a typical daemon will implement custom code
behind the getting and setting of individual items. The Python interface
described here expects the user to create custom subclasses of
mktl.Daemon, which in turn may instantiate custom subclasses of
mktl.Item. Background threads are used widely, polling and callbacks
in particular are two areas where dedicated per-item background threads will
invoke any/all registered methods; the author of any custom code should not
assume that calls arriving via these mechanisms will be serialized or
thread-safe to any meaningful degree.
The Daemon class
The mktl.Daemon class is the entry point and overall organization
for the daemon; while it provides a common storage location for daemon-wide
functions, such as handling requests and publishing broadcasts, from the
perspective of the developer the mktl.Store is a container for
the mktl.Item instances that define the actual behavior of the
system. An incoming set request, for example, is not handled by the
any higher level construct, it is passed to the appropriate mktl.Item
instance for proper handling.
The mktl.Daemon also defines the initialization sequence, if any,
for the daemon: which steps to take upon startup, which
mktl.Item subclasses to invoke for specific items, whether other
initialization steps must occur such as defining a common communication point
to access a hardware controller, or whether the initial state of a set of
items needs to be manipulated before proceeding with routine operations.
In most cases the developer will need to create a custom mktl.Daemon
subclass to satisfy the operational goals of an application. Likewise, in most
cases the developer will need to create custom mktl.Item subclasses
to implement application-specific behavior, with special attention paid to the
specific methods to override, and
additional methods useful in a daemon context.
- class mktl.Daemon(store, alias, override=False, options=None)
The mKTL
Daemonis a facilitator for common mKTL actions taken in a daemon context: loading a configuration file, instantiatingmktl.Iteminstances, and commencing routine operations.The developer is expected to subclass the
Daemonclass and implement asetup()method, and/or asetup_final()method. This subclass is the gateway between mKTL functionality and the domain-specific custom code appropriate for the developer’s application.The store argument is the name of the store that this daemon is providing items for; alias is the unique name of this mKTL daemon, and is used to locate the configuration file that defines the items for which this daemon is authoritative.
options is expected to be an
argparse.ArgumentParserinstance, though in practice it can be any Python object with specific named attributes of interest to aDaemonsubclass; the options argument is not required. This is intended to be a vehicle for custom subclasses to receive key information from command-line options, such as the location of an auxiliary configuration file containing information about a hardware controller.- add_item(item_class, key, **kwargs)
Add an
mktl.Itemto this daemon instance; this is the entry point for establishing an authoritative item, one that will handle inbound get/set request and the like. The kwargs will be passed directly to the item_class when it is called to be instantiated.
- cleanup(*args, **kwargs)
Subclasses should override the
cleanup()method to perform any/all actions prior to shutting down the daemon, such as publishing a “shutting down” message, or cleanly terminating a connection with a hardware device. This method is guaranteed to only be invoked a single time. The default implementation of this method takes no actions.
- setup()
Subclasses should override the
setup()method to invokeadd_item()for any custommktl.Itemsubclasses or otherwise execute custom code. Whensetup()is called the bulk of theDaemonmachinery is in place, but cached values have not been loaded, nor has the presence of this daemon been announced. The default implementation of this method takes no actions.
- setup_final()
Subclasses should override the
setup_final()method to execute any/all code that should occur after allmktl.Iteminstances have been created, including any non-custommktl.Iteminstances, but before this daemon announces its availability on the local network. The default implementation of this method takes no actions.
- stop()
Request that this daemon stop execution.
Configuration management
Largely for internal use, there are some aspects of the
mktl.config.Configuration class that are directly relevant for use
within a daemon. A reference to the appropriate Configuration instance can
be accesssed as an attribute of the mktl.Store, or via the
mktl.config.get() method; mktl.config.Configuration instances
are singletons.
- class mktl.config.Configuration(store, alias=None)
A convenience class to represent mKTL configuration data. To first order an instance acts like a dictionary, returning the configuration for a single key, or the full configuration block for a single UUID or unique alias.
In addition to acting as a repository for the description of all items, the Configuration instance also provides translation routines for some item values; the behavior of these translations is fully driven by the configuration, and does not depend on custom
mktl.Itemsubclasses.- convert_units(value, old, new)
Use the
pintmodule to convert the provided value from old units to new units. The value as apint.Quantityinstance will be returned if new is set to None.
- from_format(key, value)
Translate the provided value according to the configuration of the item identified by the supplied key. For example, if the item is enumerated, this method will enable one-way mapping from string values to integers; for example, ‘Off’ to 0, ‘On’ to 1, etc.
This is the inverse of
to_format().
- from_quantity(key, quantity)
Translate the provided
pint.Quantityinstance to the unformatted representation appropriate for the item identified by the supplied key. This is only relevant for numeric types that have defined units; a TypeError exception will be raised for items that do not have units.
- keys(authoritative=False)
Return an iterable sequence of keys for the items represented in this configuration. If authoritative is set to True, only return the keys for locally authoritative items; any keys with a leading underscore (built-in items) will be omitted from the reported authoritative set.
- to_format(key, value)
Translate the provided value according to the configuration of the item identified by the supplied key. For example, if the item is enumerated, this method will enable one-way mapping from integer values to representative strings; for example, 0 to ‘Off’, 1 to ‘On’, etc.
This is the inverse of
from_format().
- to_quantity(key, value, units=None)
Translate the provided value according to the configuraton of the item identified by the supplied key to a
pint.Quantityinstance. This is only relevant for numeric types that have defined units; a TypeError exception will be raised for items that do not have units. The returned quantity will be translated to the specified units, if any, otherwise the default ‘unformatted’ units are used.
- update(block, save=True)
Update the locally cached configuration to include any/all contents in the provided block. A configuration block is a Python dictionary in the on-disk client format, minimally including the keys ‘store’, ‘uuid’, and ‘items’.
- uuids(authoritative=False)
Return an iterable sequence of UUIDs represented in this configuration. If authoritative is set to True, only return the authoritative UUID.
In addition to the class itself there are an assortment of helper methods, two of which are potentially relevant in a daemon context:
- mktl.config.authoritative(store, alias, items)
Declare an authoritative configuration block for use by a local authoritative daemon. This is the expected entry point for a daemon that generates or otherwise provides its own JSON configuration via custom routines.
- mktl.config.get(store, alias=None)
Retrieve the locally cached
Configurationinstance for the specified store. A KeyError exception is raised if there are no locally cached configuration blocks for that store. A typical client will only interact withmktl.get(), which in turn calls this method.
mkd executable
The mkd executable provides a command-line interface to invoke a
persistent daemon executing a mktl.Daemon subclass as described above.
It is the natural starting point for any persistent daemon implementing
the scheme described in this document, since that is the precise purpose
it is written for.
Refer to the section covering mkd in the Executables documentation for additional details.