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 Daemon is a facilitator for common mKTL actions taken in a daemon context: loading a configuration file, instantiating mktl.Item instances, and commencing routine operations.

The developer is expected to subclass the Daemon class and implement a setup() method, and/or a setup_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.ArgumentParser instance, though in practice it can be any Python object with specific named attributes of interest to a Daemon subclass; 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.Item to 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 invoke add_item() for any custom mktl.Item subclasses or otherwise execute custom code. When setup() is called the bulk of the Daemon machinery 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 all mktl.Item instances have been created, including any non-custom mktl.Item instances, 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.Item subclasses.

convert_units(value, old, new)

Use the pint module to convert the provided value from old units to new units. The value as a pint.Quantity instance 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.Quantity instance 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.Quantity instance. 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 Configuration instance 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 with mktl.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.