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, instantiatingmktl.Item
instances, and commencing routine operations.The developer is expected to subclass the
Daemon
class 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; 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 aDaemon
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 invokeadd_item()
for any custommktl.Item
subclasses or otherwise execute custom code. Whensetup()
is called the bulk of theDaemon
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 allmktl.Item
instances have been created, including any non-custommktl.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 onreq_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 callingpublish()
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 aprotocol.message.Request
instance, parsed from the on-the-wire request. The value returned fromreq_get()
is identical to the value returned byreq_refresh()
.
- req_poll(repeat=False)
Entry point for calls established by
poll()
; a typical subclass should not need to reimplement this method. The main reasonreq_poll()
exists is to streamline the expected behavior ofreq_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 registeringreq_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 byreq_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.