API Reference#
Generated from docstrings in the libby package.
Core#
- class libby.libby.BroadcastReply(peer_id, payload)[source]#
Bases:
objectOne peer’s answer to
Libby.broadcast_request().
- class libby.libby.Libby(self_id, transport, keys=None, callback=None, *, discover=False, discover_interval_s=5.0, hello_on_start=True, is_daemon=False)[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, is_daemon=False)[source]#
- classmethod rabbitmq(self_id, rabbitmq_url='amqp://localhost', keys=None, callback=None, group_id=None, *, is_daemon=False)[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
is_daemon (bool)
- 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
- broadcast_request(key, payload, timeout_s=1.0)[source]#
Send
keyto every reachable peer and collect their replies.Nothing on the wire says how many peers will answer, so this always waits the full
timeout_s. Over ZMQ “every reachable peer” is the address book. Our own reply is dropped: on RabbitMQ our queue sits on the fanout exchange like everyone else’s.
- 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.KeyListing(names, writeonly, services)[source]#
Bases:
objectOne peer’s answer to a
keys.listrequest.writeonlynames those ofnamesthat have no value to read, so a caller polling the peer can skip them. It is bare keyword names, matchingmatcheson the wire rather than the qualifiednamesabove.serviceslists the non-keyword keys the peer answers, such askeys.read. It is empty for a peer on a libby old enough not to report the field, which is how a caller decides whether bulk reads are available: an unknown key is dropped without an ACK, so probing for one looks exactly like a dead peer.
- 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.
- listing(pattern, *, timeout_s=3.0)[source]#
List a peer’s matching keywords and the services it serves, in one call.
Use this over
list()when the caller also needs to know whether the peer supports bulk reads; both come from the samekeys.listresponse, so asking costs no extra round trip.- Parameters:
- Return type:
- list(pattern, *, timeout_s=3.0)[source]#
List qualified keyword names matching
<group>.<daemon>.<pattern>.Returns fully qualified names, so the result feeds straight back into
get(),show()orread(). A%in the group or daemon segment spans daemons by broadcast, so that form waits the fulltimeout_s(seepeers()).
- peers(pattern='%.%', *, timeout_s=1.0)[source]#
List the live daemons matching
<group>.<daemon>asgroup.daemonids.Every daemon answers a broadcast
keys.list, so this waits the fulltimeout_srather than returning on the first reply. Over ZMQ only daemons in the address book are asked.
- peer_listings(pattern='%.%', *, timeout_s=1.0)[source]#
Map each live daemon matching
patternto its keyword names.A keyword segment in
patternnarrows the names; without one every keyword is listed. Same timeout semantics aspeers().Clients answer the broadcast too, since every
Libbyserveskeys.list, so a reply counts only if it reports itself as a daemon.
- describe(name, *, timeout_s=3.0)[source]#
Read one keyword’s metadata (type, access, units, timeout_s).
- read(names, *, timeout_s=3.0, chunk_size=100)[source]#
Read many keywords using one request per peer, per chunk.
Unlike
get()andset(), this never raises for a failed read: every requested name maps to its own response dict, so one dead peer or one broken getter costs only its own entries. Names may span peers; each peer is requested separately, in the order given.
- 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.
- class libby.naming.AddressPattern(group, daemon, keyword)[source]#
Bases:
objectA
<group>.<daemon>[.<keyword>]pattern with%allowed in every segment.
- libby.naming.parse_address_pattern(arg)[source]#
Parse
<group>.<daemon>[.<keyword>]with%allowed anywhere.Unlike
parse_keyword()this admits patterns that span daemons, so it is only for callers that resolve them by broadcast (list,peers).- Parameters:
arg (str)
- Return type:
- 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:
Keygrabber#
The InfluxDB sink is omitted here: it imports its client at module scope, and
the docs build does not install the optional influxdb extra.
The sample the keygrabber collects, and the sink contract it writes to.
A Sample carries nothing backend-specific: turning one into measurements,
fields, tags or columns is the sink’s job, so a second backend is a new sink
rather than a change to the collector.
- exception libby.keygrabber.sink.SinkError[source]#
Bases:
LibbyErrorA sink could not be reached, configured, or written to.
- exception libby.keygrabber.sink.SinkWriteError[source]#
Bases:
SinkErrorA batch of samples could not be written and may be worth retrying.
- class libby.keygrabber.sink.Sample(keyword, group, peer, value, units, timestamp)[source]#
Bases:
objectOne keyword’s value, read at one instant.
- Parameters:
- class libby.keygrabber.sink.RetryPolicy(max_batches=64, base_backoff_s=1.0, max_backoff_s=60.0)[source]#
Bases:
objectBounds on how a
RetryingWriterqueues and re-attempts batches.
- class libby.keygrabber.sink.Sink(*args, **kwargs)[source]#
Bases:
ProtocolDestination for collected samples.
Implementations own their own schema.
writereturns how many samples it actually stored, which can be fewer than it was given when a backend cannot represent some of them, and raisesSinkWriteErrorwhen the batch failed and should be retried.
- class libby.keygrabber.sink.RetryingWriter(sink, *, policy=None, clock=<built-in function monotonic>)[source]#
Bases:
objectWraps a sink, holding failed batches in a bounded queue for retry.
Retrying is backend-independent, so it lives here rather than inside any one sink. The queue is bounded and drops its oldest batch when full, so a database that stays down cannot grow the daemon’s memory without limit.
Nothing here starts a thread and nothing sleeps: the caller decides when to call
flush_due(), andclockis injectable so backoff is testable without waiting for it.- Parameters:
sink (Sink)
policy (Optional[RetryPolicy])
clock (Callable[[], float])
- property healthy: bool#
Whether the most recent attempt on the sink succeeded.
Worth asking for, because
write()deliberately does not raise when a batch fails: it queues it and returns 0, which is what makes retrying possible and what stops a caller learning about the failure from an exception.
Parse and validate the keygrabber’s own configuration sections.
A keygrabber config file is an ordinary LibbyDaemon config (peer_id,
group_id, transport, …) plus three sections this module owns:
sink, defaults and collections.
- class libby.keygrabber.config.CollectionConfig(name, group, daemon, keywords, exclude, interval_s, timeout_s, refresh_s)[source]#
Bases:
objectOne configured peer, keyword selection and cadence.
- Parameters:
- class libby.keygrabber.config.KeygrabberConfig(collections, sink, workers, retry)[source]#
Bases:
objectThe keygrabber’s parsed configuration.
- Parameters:
collections (Tuple[CollectionConfig, ...])
workers (int)
retry (RetryPolicy)
- collections: Tuple[CollectionConfig, ...]#
- retry: RetryPolicy#
- libby.keygrabber.config.parse_config(config)[source]#
Validate a daemon config’s keygrabber sections into typed objects.
- Parameters:
- Return type:
- libby.keygrabber.config.build_sink(sink_config)[source]#
Build the configured sink, importing its backend client on demand.
- libby.keygrabber.config.select_keywords(collection, available)[source]#
Return the bare keyword names this collection should read.
availableis the peer’s full keyword list. Includes are applied first, then exclusions, so a default exclusion is dropped even when a wildcard would otherwise have matched it.- Parameters:
collection (CollectionConfig)
- Return type:
One configured peer, resolved against the live peer and read on a cadence.
- class libby.keygrabber.collection.TickResult(samples, read_errors, first_error=None)[source]#
Bases:
objectWhat one read of a collection produced.
- class libby.keygrabber.collection.Collection(config, *, clock=<built-in function monotonic>)[source]#
Bases:
objectTracks what one peer exposes and turns a read of it into samples.
Resolution is refreshed periodically rather than once, so keywords added by a restarted daemon are picked up without restarting the keygrabber.
- Parameters:
config (CollectionConfig)
clock (Callable[[], float])
- invalidate()[source]#
Force the next tick to resolve again, after a config change.
- Return type:
None
- backoff_interval_s()[source]#
Return the interval to use next, stretched while the peer fails.
A peer that is down would otherwise be retried, and logged about, on its configured cadence indefinitely. Backing off keeps a dead daemon from dominating both the logs and the read budget, while the cap keeps a recovered one from waiting long to be noticed.
- Return type:
- resolve(client)[source]#
Ask the peer what it serves and select the configured keywords.
One
keys.listcovers all of it: the peer’s keyword names, which of them cannot be read, and whether it serveskeys.read. Selection is then local, so a collection with several patterns still costs one request.
Decides which collections are due to be read, and which to skip.
Deliberately free of threads, pools and sleeping: it answers “what should run now” and nothing else, so the due-time ordering and the skip rule can be tested against an injected clock rather than by racing real threads.
- class libby.keygrabber.scheduler.Scheduler(*, clock=<built-in function monotonic>)[source]#
Bases:
objectA due-time heap over collections, with at most one tick each in flight.
Every method takes the lock only for in-memory bookkeeping, never across a read or a write, because
reloadcallsreplace()from the transport’s receive thread and blocking that would time out every read already in flight.- Parameters:
clock (Callable[[], float])
- replace(collections)[source]#
Swap the scheduled set, leaving in-flight ticks to finish.
Every new collection is due immediately, so a reload takes effect on the next pass rather than after a full interval.
- Parameters:
collections (Iterable[Collection])
- Return type:
None
- next_delay()[source]#
Return seconds until the next tick, or None when nothing is scheduled.
- Return type:
float | None
- claim_due()[source]#
Claim every collection now due, and count those already running.
Returns the collections the caller should tick, and how many ticks were skipped because their predecessor had not finished. A skipped tick is not retried sooner: it waits for its next interval, so a slow peer settles at a lower rate instead of building a backlog.
- Return type:
Tuple[Tuple[Collection, …], int]
- update(collection)[source]#
Replace one collection and reschedule only it.
Used when a control keyword changes a cadence, so adjusting one collection does not re-seed every other collection’s due time and set the whole fleet reading at once.
- Parameters:
collection (Collection)
- Return type:
None
The keygrabber daemon: schedule reads, collect samples, write them out.
- class libby.keygrabber.daemon.Counters[source]#
Bases:
objectTallies the daemon reports, guarded for cross-thread increments.
The lock is only ever held around an increment, never across a read or a sink write, because these are served on the transport’s receive thread and blocking it would time out every read already in flight.
- class libby.keygrabber.daemon.KeygrabberDaemon[source]#
Bases:
LibbyDaemonPolls keywords from other libby peers and writes them to a sink.
One process serves the whole fleet: cadence lives in this daemon’s config rather than in each hardware daemon, and the database credential lives in one place.
- on_start(libby)[source]#
Parse config, open the sink, and start the reader and writer threads.
- Parameters:
libby (Libby)
- Return type:
None
- on_stop(libby=None)[source]#
Stop the threads and close the sink once the writer has drained it.
The draining happens on the writer thread rather than here, so only one thread is ever inside the sink. This method just waits for it.
- Parameters:
libby (Libby | None)
- Return type:
None
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.
- send(dest, frame)[source]#
Send a frame to a destination.
"peer:<peer_id>"or"<peer_id>"-> direct to that peer"broadcast:*"-> to all known peers
Dropped once stopped: sending lazily creates a DEALER per peer, and after
stop()there is nothing left to close it. bamboo’s discovery announces from its own thread, which itsstop()sets a flag for rather than joining, so a broadcast can arrive a whole announce interval after this transport was torn down.
- 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.
- libby.cli.libby_cli.cmd_completion(namespace)[source]#
Print the shell code that wires TAB completion to the libby executable.