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 nearly all cases the developer will need to create a custom mktl.Daemon subclass to satisfy the operational goals of an individual daemon.

class mktl.Daemon(store, configuration, arguments=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; configuration is the base name of the mKTL configuration file that defines the items for which this daemon is authoritative. arguments 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 arguments argument is not required. This is intended to be a vehicle for custom subclasses to receive key information from command-line arguments, 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.

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.

The Item class, expanded

The bulk of any daemon-specific logic will occur in mktl.Item subclasses. This is where requests get handled, where data gets interpreted, where logic is defined that could span items within and without the boundaries of the containing mktl.Daemon.

Subclass definitions of the mktl.Item.req_refresh(), mktl.Item.req_set(), and mktl.Item.validate() methods are the key methods to override when implementing custom behavior. Any method with a req_ prefix is part of the handling chain for an inbound request, with mktl.Item.req_get() and mktl.Item.req_set() implementing entry points for GET and SET operations, respectively, though if mktl.Item.req_get() leverages mktl.Item.req_refresh() in order to acquire the most recent value.

class mktl.Item(store, key, subscribe=True, authoritative=False, pub=None)

An Item represents a key/value pair, where the key is the name of the Item, and the value is whatever is provided by the authoritative daemon. The principal way for both clients and daemons to get or set the value is via the value() property.

A non-authoritative Item will automatically subscribe() itself to any available updates.

poll(period)

Poll for a new value every period seconds. Polling will be discontinued if period is set to None or zero. The actual acquisition of a new value is accomplished via the req_poll() method, which in turn leans heavily on req_refresh() to do the actual work.

publish(new_value, timestamp=None, repeat=False)

Publish a new value, which is expected to be the Python binary representation of the new value. If timestamp is set it is expected to be a UNIX epoch timestamp; the current time will be used if it is not provided. Newly published values are always cached locally.

Note that, for simple cases, an authoritative daemon can set the value() property to publish a new value instead of calling publish() directly.

req_get(request)

Handle a GET request. A typical subclass should not need to re-implement this method, implementing req_refresh() would normally be sufficient. The request argument is a protocol.message.Request instance, parsed from the on-the-wire request. The value returned from req_get() is identical to the value returned by req_refresh().

req_poll(repeat=False)

Entry point for calls established by poll(); a typical subclass should not need to reimplement this method. The main reason req_poll() exists is to streamline the expected behavior of req_refresh(), allowing it to focus entirely on what it means to acquire a new value; after receiving the refreshed value, req_poll() will additionally publish the new value. A common pattern for custom subclasses involves registering req_poll() as a callback on other items, so that the local value of this item can be refreshed when events occur elsewhere within a daemon.

For convenience, the value returned from req_poll() is identical to the value returned by req_refresh().

req_refresh()

Acquire the most up-to-date value available for this Item and return it to the caller. The return value is a dictionary, with a ‘value’ key containing the Python binary representation of the item value, and a ‘time’ key with a UNIX epoch timestamp representing the last-changed time for that value. Returning None is expected if no new value is available; returning None will not clear the currently known value, that is only done if the returned dictionary contains None as the ‘value’.

Examples:

{'time': 1234.5678, 'value': 54}
{'time': 8765.4321, 'value': None}
req_set(request)

Handle a client-initiated SET request. Any calls to req_set() are expected to block until completion of the request; no return value of significance is expected, though one can be provided and will be returned to the client, even if the client does not use it. Any errors should be indicated by raising an exception.

The request is a protocol.message.Request instance.

validate(value)

A hook for a daemon to validate a new value. The default behavior is a no-op; any checks should raise exceptions if they encounter a problem with the incoming value. The ‘validated’ value should be returned by this method; this allows for the possibility that the incoming value has been translated to a more acceptable format, for example, converting ‘123’ to the bare number 123 for a numeric item type.

markd executable

The markd 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 markd in the Executables documentation for additional details.