API Reference#
Generated from docstrings in the libby package.
Core#
- class libby.libby.Libby(self_id, transport, keys=None, callback=None, *, discover=False, discover_interval_s=5.0, hello_on_start=True)[source]#
Bases:
object- Parameters:
- classmethod zmq(self_id, bind, address_book, keys=None, callback=None, *, discover=False, discover_interval_s=5.0, hello_on_start=True, group_id=None)[source]#
- classmethod rabbitmq(self_id, rabbitmq_url='amqp://localhost', keys=None, callback=None, group_id=None)[source]#
Create a Libby instance using RabbitMQ transport.
- Parameters:
self_id (str) – Unique identifier for this peer
rabbitmq_url (str) – RabbitMQ connection URL (default: “amqp://localhost”)
keys (List[str] | None) – List of RPC keys this peer will serve
callback (Callable[[dict, dict], dict | None] | None) – Default callback for RPC requests
group_id (str | None) – Optional group identifier
- Returns:
Configured Libby instance
- Return type:
Example
>>> libby = Libby.rabbitmq( ... self_id="peer-A", ... rabbitmq_url="amqp://user:pass@localhost:5672/", ... keys=["echo"], ... group_id="hsfei" ... )
- close()#
- Return type:
None
- class libby.daemon.LibbyDaemon[source]#
Bases:
objectBase class for configurable Libby daemons.
The class owns the following:
Libby transport construction
service and topic registration
typed keyword registration
YAML/JSON configuration loading
subsystem configuration selection
logging and lifecycle management
Instrument-specific subclasses should normally only implement
on_startandon_stop, plus any hardware-facing methods.- CONFIG_ATTRIBUTES = frozenset({'address_book', 'bind', 'discovery_enabled', 'discovery_interval_s', 'fail_fast_on_start', 'group_id', 'peer_id', 'rabbitmq_url', 'transport'})#
- classmethod config_attributes()[source]#
Return config keys mapped directly onto daemon attributes.
Subclasses may extend the set:
CONFIG_ATTRIBUTES = ( LibbyDaemon.CONFIG_ATTRIBUTES | {"device_name"} )
- classmethod from_config_file(path, daemon_id=None, *, env_prefix=None)[source]#
Build a daemon from a single-daemon or subsystem config file.
For a subsystem config containing one daemon, the daemon is selected automatically. For multiple daemons,
daemon_idis required.
- on_start(libby)[source]#
Initialize hardware and register keywords.
- Parameters:
libby (Libby)
- Return type:
None
- on_stop(libby=None)[source]#
Release hardware resources.
- Parameters:
libby (Libby | None)
- Return type:
None
- on_hello(libby)[source]#
Run after the initial discovery hello.
- Parameters:
libby (Libby)
- Return type:
None
- register_keyword(keyword)[source]#
Register now, or defer registration until the daemon starts.
- Parameters:
keyword (Keyword)
- Return type:
None
- property keyword_registry#
Return Libby’s typed keyword builder.
This property is intended for use in
on_start, after the Libby instance has been constructed.
Programmatic client for getting and setting keywords on libby daemons.
Client CLI spins up a connection and holds it for its lifetime, so a script can touch many
keywords cheaply. It resolves a qualified <group>.<daemon>.<keyword> to a peer
and key, calls Libby.rpc, and turns the reply into a value or a
LibbyError via libby.response.unwrap().
- class libby.client.WaitResult(satisfied, address, value, elapsed_s, polls)[source]#
Bases:
objectWhat a
Client.wait_for_result()wait observed before it stopped.
- class libby.client.Client(libby)[source]#
Bases:
objectLong-lived, in-process handle for getting and setting libby keywords.
- Parameters:
libby (Libby)
- classmethod rabbitmq(*, self_id='libby-client', rabbitmq_url='amqp://localhost')[source]#
Connect over RabbitMQ.
- classmethod zmq(*, self_id='libby-client', bind='tcp://127.0.0.1:56001', address_book=None)[source]#
Connect over ZMQ, given an address book of peer endpoints.
- classmethod from_config(path=None, *, self_id='libby-client')[source]#
Build a Client from cli_config.yaml, resolving transport as the CLI does.
- set(name, value, *, timeout_s=None)[source]#
Write a keyword and return the value the daemon applied.
- wait_for(expression, timeout=None, *, daemon=None, case=False, poll_s=0.1, rpc_timeout_s=3.0)[source]#
Block until
expressionis true; return whether it became true.expressionis one comparison between a$-prefixed keyword and a literal — seelibby.expressionfor the accepted syntax:client.wait_for('$hsfei.pickoff.positionvalue > 15', timeout=5) client.wait_for('$ismoving == false', 30, daemon='hsfei.pickoff')
- Parameters:
expression (str) – The condition to wait on.
timeout (float | None) – Seconds to wait before giving up.
Nonewaits indefinitely;0evaluates once and returns.daemon (str | None) – Default
<group>.<daemon>, so the expression can name a keyword bare.case (bool) – Compare strings case-sensitively.
poll_s (float) – Seconds between reads.
rpc_timeout_s (float) – Per-read RPC timeout.
- Returns:
True if the expression became true, False if the timeout expired with it still false.
- Raises:
ExpressionError – the expression is malformed, or its two sides cannot be compared at all.
KeywordError – the daemon rejected the read (e.g. unknown or write-only keyword) — a condition waiting cannot resolve.
- Return type:
- wait_for_result(expression, timeout=None, *, daemon=None, case=False, poll_s=0.1, rpc_timeout_s=3.0)[source]#
Like
wait_for(), but report what the wait observed.Same arguments and same exceptions; returns a
WaitResultinstead of a bool, for callers that want to show the value the expression settled on (or timed out against).
Keywords#
Keyword classes for Libby.
- class libby.keyword.Keyword(name, *, getter=None, setter=None, units=None, description='', nullable=False, validator=None, timeout_s=None)[source]#
Bases:
objectNamed value exposed as a libby service.
- Parameters:
- class libby.keyword.BoolKeyword(name, *, getter=None, setter=None, units=None, description='', nullable=False, validator=None, timeout_s=None)[source]#
Bases:
KeywordKeyword whose value is a Python
bool.- Parameters:
- class libby.keyword.IntKeyword(name, *, getter=None, setter=None, units=None, description='', nullable=False, validator=None, timeout_s=None)[source]#
Bases:
KeywordKeyword whose value is a Python
int;boolis rejected.- Parameters:
- class libby.keyword.FloatKeyword(name, *, getter=None, setter=None, units=None, description='', nullable=False, validator=None, timeout_s=None)[source]#
Bases:
KeywordKeyword whose value is a Python
float; acceptsint, rejectsbool.- Parameters:
- class libby.keyword.StringKeyword(name, *, getter=None, setter=None, units=None, description='', nullable=False, validator=None, timeout_s=None)[source]#
Bases:
KeywordKeyword whose value is a Python
str.- Parameters:
- class libby.keyword.TriggerKeyword(name, *, action, description='', timeout_s=None)[source]#
Bases:
KeywordWrite-only action keyword. Any modify fires
action; show returnsfalse.
- libby.keyword.match_pattern(pattern, names)[source]#
Return names matching
pattern, sorted.%is a wildcard; it matches any run of characters excluding..
Per-Libby buffer for building Keyword instances with a typed-method API.
- class libby.keyword_registry.KeywordRegistry[source]#
Bases:
objectCollects Keyword instances built via typed methods.
Build keywords with
bool/int/float/string/trigger; each call appends to an internal buffer. Calldrain()to retrieve and clear the buffer; pass the result toLibby.register_keywords.- bool(name, *, getter=None, setter=None, description='', nullable=False, validator=None, timeout_s=None)[source]#
- int(name, *, getter=None, setter=None, units=None, description='', nullable=False, validator=None, timeout_s=None)[source]#
- float(name, *, getter=None, setter=None, units=None, description='', nullable=False, validator=None, timeout_s=None)[source]#
- string(name, *, getter=None, setter=None, description='', nullable=False, validator=None, timeout_s=None)[source]#
Configuration and naming#
- exception libby.config.ConfigError[source]#
Bases:
ConfigError,ValueErrorRaised when daemon configuration cannot be selected or validated.
Subclasses the public
libby.ConfigErrortoo, so callers can catch one exception regardless of whether it came from daemon subsystem-config loading (here) or client cli_config.yaml loading (libby.config_resolve).
- libby.config.load_config(path, *, optional=False)[source]#
Load a JSON or YAML configuration file.
A missing file raises
FileNotFoundErrorunlessoptional, in which case{}is returned - used by the client’s optional cli_config.yaml.
- libby.config.with_env_overrides(config, prefix='LIBBY_')[source]#
Apply top-level environment-variable overrides.
- libby.config.deep_merge(base, override)[source]#
Return a recursive merge without mutating either input.
- libby.config.is_subsystem_config(config, *, daemon_section='daemons')[source]#
Return whether a config contains a daemon collection.
- libby.config.list_daemons(config, *, daemon_section='daemons')[source]#
List daemon IDs in a subsystem config.
- libby.config.extract_daemon_config(full_config, daemon_id, *, daemon_section='daemons')[source]#
Merge subsystem defaults with one daemon’s overrides.
- class libby.config.DaemonConfigLoader(path, *, daemon_section='daemons')[source]#
Bases:
objectLoad a single-daemon or multi-daemon subsystem config.
- Parameters:
path (PathLike)
daemon_section (str)
Resolve libby connection settings from cli_config.yaml plus overrides.
Shared by the libby CLI and the lib’s Client.from_config so the precedence
rules (override → config file → built-in default) live in one place.
- libby.config_resolve.load_cli_config(path=None)[source]#
Load the client’s cli_config.yaml. Missing file → empty dict.
The client config is optional, so it wraps
libby.config.load_configwithoptional=Trueand re-raises parse/shape failures asConfigError.
- libby.config_resolve.resolve_transport(override, config)[source]#
Pick the transport: override → config → default; validated.
- libby.config_resolve.resolve_rabbitmq_url(override, config)[source]#
Pick the RabbitMQ URL: override → config → default.
- libby.config_resolve.parse_addr_kv(entry)[source]#
Parse a ‘peer_id=tcp://host:port’ address-book entry.
- libby.config_resolve.resolve_address_book(config, extra_addrs=None)[source]#
Merge the config ‘peers’ map with extra ‘peer=addr’ override strings.
Keyword-name parsing, value coercion, and peer/group naming (transport-agnostic).
- libby.naming.parse_keyword(arg, *, allow_pattern=False)[source]#
Parse ‘<group>.<daemon>.<keyword>’ into (group, daemon, keyword).
With
allow_pattern=True,%is allowed in the keyword segment. Group and daemon must always be explicit.
- libby.naming.qualified_peer_id(peer_id, group_id=None)[source]#
Return the lowercased wire identity “<group_id>.<peer_id>”, or bare peer_id.
The single choke point both daemon and client build their identity from, so a short peer_id cannot collide across groups and case never matters.
- libby.naming.peer_id(group, daemon)[source]#
Map an address’s group/daemon segments to the daemon peer id.
daemon is the peer_id, group is its group_id: the same pair qualified_peer_id joins on the daemon side, so client and daemon always agree on the wire identity by construction. See plans/peer_group_naming_design.md.
- libby.naming.coerce_value(value)[source]#
Coerce a modify value string.
Empty / ‘null’ → None; ‘true’/’false’ → bool; parseable as int → int; parseable as float → float; otherwise the original string.
Expressions for wait_for: one keyword compared against one literal.
$hsfei.pickoff.positionvalue > 15
$hsfei.pickoff.ismoving == false
$hsfei.pickoff.status != Ready
Keyword references are prefixed with $, and are either fully qualified
($<group>.<daemon>.<keyword>) or, when a default daemon is supplied, a
bare keyword ($<keyword>).
Values need quoting only when they contain whitespace, start with $, or
look like an operator; one level of quoting is stripped. An unquoted value is
coerced the way the CLI coerces a modify value, so false is a bool and
15 an int, while 'false' is the string.
Boolean operators, arithmetic, and multi-keyword expressions are not supported.
- class libby.expression.Comparison(address, op, operand)[source]#
Bases:
objectOne parsed comparison: a keyword address, an operator, and a literal.
Always keyword-first, so a caller reads one keyword and compares one way;
parse_comparison()flips the operator to make it so.- evaluate(value, *, case=False)[source]#
Compare
value(a keyword’s current value) against the operand.String comparisons are case-insensitive unless
caseis true.A
valueofNoneunder an ordering operator is unsatisfied rather than an error, so a wait started before a daemon has populated a nullable keyword keeps waiting instead of failing.- Raises:
ExpressionError – the two types cannot be ordered at all (e.g.
"ready" > 15), which no amount of waiting will fix.- Parameters:
- Return type:
- libby.expression.parse_comparison(expression, *, daemon=None)[source]#
Parse
expressioninto aComparison.- Parameters:
- Raises:
ExpressionError – the expression is not a single keyword-to-literal comparison, or the default daemon is malformed.
KeywordNameError – the resolved address is not a valid
<group>.<daemon>.<keyword>.
- Return type:
Responses and errors#
Turn a bamboo RPC envelope into a keyword-response dict.
Libby.rpc returns a transport envelope, not the keyword response itself:
{"status": "delivered", "resp": <payload|None>}{"status": "timeout", ...}{"status": "too_large", "mtu": ..., "size": ...}
The keyword <payload> is {"ok": True, "value": ..., "units"?: ...} or
{"ok": False, "error": "..."}. unwrap raises on any failure; a caller
that prefers a dict (e.g. the CLI’s table renderer) wraps it in its own
non-raising adapter.
- libby.response.unwrap(name, envelope)[source]#
Reduce an rpc envelope to its keyword-response dict, or raise.
Raises
LibbyTimeout(not delivered / no response),KeywordError(daemon answeredok=False), orLibbyError(malformed / oversized).
Exceptions raised by libby core and lib code.
Core and library code raises these errors. Entry points (the CLI or a daemon) catch them and decide whether to exit the process or bubble up.
- exception libby.errors.ConfigError[source]#
Bases:
LibbyErrorA config file or connection setting is invalid.
- exception libby.errors.KeywordNameError[source]#
Bases:
LibbyErrorA qualified keyword name is malformed.
- exception libby.errors.ExpressionError[source]#
Bases:
LibbyErrorA
wait_forexpression is malformed, or cannot be evaluated.
- exception libby.errors.LibbyTimeout[source]#
Bases:
LibbyErrorAn RPC request was not delivered, or got no response, within its TTL.
- exception libby.errors.KeywordError(name, error)[source]#
Bases:
LibbyErrorA daemon rejected a keyword get/set (responded with
ok=False).
Transports#
- class libby.zmq_transport.ZmqTransport(bind_router, address_book, my_id, group_id=None)[source]#
Bases:
TransportSimple ROUTER (bind) + per-peer DEALER (connect) transport.
This peer binds a ROUTER at bind_router.
For each remote peer_id in address_book, we lazily create a DEALER and set its ZMQ.IDENTITY to peer_id so the remote can see who sent.
Incoming frames arrive on ROUTER as
[IDENT, PAYLOAD]or[IDENT, b"", PAYLOAD]. We pass IDENT as “peer:<peer_id>” to the Protocol callback.Replies to a request we initiated over one of our own DEALER sockets arrive on that DEALER socket, not on our ROUTER (that’s how ZMQ’s ROUTER/DEALER pattern works: a reply travels back over whichever connection carried the request). Every DEALER we create is registered with the poller and mapped back to its peer_id so those replies are actually read instead of sitting unpolled forever.
- reply_to(peer_id, frame)[source]#
Try to send directly back to the peer using the ROUTER routing-id we observed on the incoming request. Returns True if used, False otherwise.
- class libby.rabbitmq_transport.RabbitMQTransport(peer_id, rabbitmq_url='amqp://localhost', group_id=None)[source]#
Bases:
objectRabbitMQ-based transport for Bamboo Protocol.
Architecture: - Each peer gets a unique queue: “libby.peer.<peer_id>” - Direct exchange “libby.direct” for peer-to-peer messages - Fanout exchange “libby.fanout” for broadcast messages - Each peer binds its queue to both exchanges
No address book needed - RabbitMQ broker handles all routing.
- __init__(peer_id, rabbitmq_url='amqp://localhost', group_id=None)[source]#
Initialize RabbitMQ transport.
- start(ready_timeout_s=5.0)[source]#
Start consuming messages from RabbitMQ.
Blocks (up to
ready_timeout_s) until the receive queue is declared, bound, and consuming, so callers can publish a request immediately after this returns without racing their own reply queue.- Parameters:
ready_timeout_s (float)
- Return type:
None
Shared helper for building transport setup-error messages.
- libby._transport_errors.describe_exception(exc)[source]#
Return a human-readable message for exc.
Some exceptions (e.g. pika’s AMQPConnectionError on a refused connection) have an empty str() and bury the real cause in repr()/args instead, which turns “transport setup failed” into a message with nothing after the colon. Fall back to repr() whenever str() has nothing useful to say.
- Parameters:
exc (BaseException)
- Return type:
CLI#
Libby CLI — show/modify keywords on libby peers, plus raw req/sub.
- libby.cli.libby_cli.cmd_waitfor(namespace)[source]#
Block until a keyword comparison holds, or the timeout expires.