plesty.server.services.agent
The bench agent: runs plesty-server commands for a remote plesty.bench client.
A ZMQ ROUTER on the agent port (5550) speaking the plesty-lib device-server
wire. Four request types — describe, exec, fetch, device —
documented in plesty.bench.client. exec runs the click application
in-process with its output captured, with the agent’s own --home/
-f prepended, so every command the CLI has is remote with nothing to
keep in sync. Each request is handled on a worker thread (a start waits
for its probe; a describe must not wait behind it); replies go back
through the socket thread, since a ZMQ socket belongs to one thread.
device relays one request to a declared device’s own server and hands its
reply back. It is the exception to “the CLI is the API”, and deliberately: a
control panel is a long-lived session of many small round trips, and exec
would run the click application for each one. It is also why the panel is
relayed rather than pointed at the device port — a device server binds
tcp://* and checks no token, so routing through the agent keeps one
authenticated door rather than one per instrument.
Trust model: the bench network, like the device servers — a shared token
(--token / PLESTY_BENCH_TOKEN) checked on every exec/fetch/
device, fetch confined to the bench home, device confined to the
declared devices this bench is running, no agent/gui through exec.
Attributes
Classes
|
|
Serve |
Functions
|
The process's |
|
Capture what this thread prints, and only this thread; yields |
|
Run agent on a daemon thread; returns the thread and its stop event. |
|
|
|
|
|
|
|
Return this thread's socket to address, connecting on first use. |
|
Send one device-server request to address and return its reply envelope. |
|
Close and forget this thread's socket to address. |
|
Module Contents
- plesty.server.services.agent.DEFAULT_BIND = 'tcp://*:5550'
- plesty.server.services.agent.TOKEN_ENV = 'PLESTY_BENCH_TOKEN'
- plesty.server.services.agent.FORBIDDEN
- plesty.server.services.agent.FETCH_LIMIT = 16777216
- plesty.server.services.agent.DEVICE_TIMEOUT_MS = 15000
- class plesty.server.services.agent._ThreadStream(original: Any)
Bases:
io.TextIOBasesys.stdout/sys.stderrthat sends each thread’s writes to its own buffer.execcaptures what a command prints, and requests run on several worker threads at once.contextlib.redirect_stdoutswaps the stream for the whole process, so two commands running together wrote into each other’s capture: a client’sjobs cancelwas answered with the[]of a console’sjobs list --jsonpoll, and aninstallthat was being followed got an emptyjobs log --jsonand died on it (#19). This stands in for the stream once, and a thread that is capturing gets its own buffer; every other thread writes through to the original stream.Initialize self. See help(type(self)) for accurate signature.
- Parameters:
original (Any)
- original
- local
- writable() bool
Return whether object was opened for writing.
If False, write() will raise OSError.
- Return type:
bool
- _target() Any
- Return type:
Any
- write(text: str) int
Write string s to stream.
Return the number of characters written (which is always equal to the length of the string).
- Parameters:
text (str)
- Return type:
int
- flush() None
Flush write buffers, if applicable.
This is not implemented for read-only and non-blocking streams.
- Return type:
None
- isatty() bool
Return whether this is an ‘interactive’ stream.
Return False if it can’t be determined.
- Return type:
bool
- property encoding: str
- Return type:
str
- plesty.server.services.agent._STREAMS_LOCK
- plesty.server.services.agent._thread_stream(name: str) _ThreadStream
The process’s
sys.<name>as a_ThreadStream, installed on first use.- Parameters:
name (str)
- Return type:
- plesty.server.services.agent._captured() Any
Capture what this thread prints, and only this thread; yields
(out, err).- Return type:
Any
- plesty.server.services.agent._SOCKETS
- class plesty.server.services.agent.Agent(home: plesty.server.model.home.Home, fleet_path: str | os.PathLike[str] | None = None, bind: str = DEFAULT_BIND, token: str | None = None, workers: int = 4)
Serve
plesty.benchclients for one bench.Configure the agent.
- Parameters:
home (plesty.server.model.home.Home) – The bench home every command runs against.
fleet_path (str | os.PathLike[str] | None) – Fleet file passed to every command (
-f), orNone.bind (str) – ZMQ endpoint to bind.
token (str | None) – Shared secret;
NonereadsPLESTY_BENCH_TOKEN; empty means no check (a bench on a trusted network).workers (int) – Concurrent requests.
- home
- fleet_path
- bind = 'tcp://*:5550'
- token
- workers = 4
- bound: str | None = None
- served = 0
- device_timeout_ms = 15000
- handle(message: Any) dict[str, Any]
Answer one decoded request (pure; no socket involved).
- Parameters:
message (Any)
- Return type:
dict[str, Any]
- description() dict[str, Any]
What
describereturns.- Return type:
dict[str, Any]
- exec(argv: collections.abc.Sequence[Any]) dict[str, Any]
Run
plesty-server <argv>in-process against this bench; capture everything.- Parameters:
argv (collections.abc.Sequence[Any])
- Return type:
dict[str, Any]
- device(name: str, request: Any) dict[str, Any]
Relay one device-server request to the server of name, and its reply back.
Why a request type of its own, when the rule is “the CLI is the API”: a control panel is a long-lived session of many small round trips — a poll per parameter per second — and
execwould spawn the click application for each one. This carries the device wire itself, so the panel a console opens locally is the same panel driven from a laptop.Why relayed at all, when device servers bind
tcp://*and could be reached directly: they check no token. The agent does, so the bench keeps one authenticated door instead of one per instrument.The reply is passed through as the device server framed it, so an error from the instrument reaches the caller as its own error, not as a relay failure.
- Parameters:
name (str) – The declared device whose server to reach.
request (Any) – The device-server request (
query,write,call,describe,help) to forward verbatim.
- Returns:
The reply envelope to send back to the client.
- Raises:
ValueError – When the name or the request is missing or malformed.
- Return type:
dict[str, Any]
- fetch(path: str) dict[str, Any]
Read a file under the bench home; path is relative to it.
- Parameters:
path (str)
- Return type:
dict[str, Any]
- serve(stop: threading.Event, poll_ms: int = 100, max_replies: int | None = None) int
Bind and answer requests until stop is set; returns the number answered.
- Parameters:
stop (threading.Event) – Set to end the loop.
poll_ms (int) – Socket poll interval.
max_replies (int | None) – Stop once this many replies went out (tests).
- Return type:
int
- plesty.server.services.agent.serve_in_thread(agent: Agent, stop: threading.Event | None = None) tuple[threading.Thread, threading.Event]
Run agent on a daemon thread; returns the thread and its stop event.
- Parameters:
agent (Agent)
stop (threading.Event | None)
- Return type:
tuple[threading.Thread, threading.Event]
- plesty.server.services.agent._ok(result: Any) dict[str, Any]
- Parameters:
result (Any)
- Return type:
dict[str, Any]
- plesty.server.services.agent._error(message: str, kind: str) dict[str, Any]
- Parameters:
message (str)
kind (str)
- Return type:
dict[str, Any]
- plesty.server.services.agent._frame(reply: dict[str, Any]) bytes
- Parameters:
reply (dict[str, Any])
- Return type:
bytes
- plesty.server.services.agent._relay_socket(address: str, timeout_ms: int) Any
Return this thread’s socket to address, connecting on first use.
- Parameters:
address (str) – The device server’s endpoint.
timeout_ms (int) – Receive timeout.
- Returns:
A connected ZMQ DEALER belonging to the calling thread.
- Return type:
Any
- plesty.server.services.agent._relay(address: str, request: dict[str, Any], timeout_ms: int) dict[str, Any]
Send one device-server request to address and return its reply envelope.
The device server’s own
{"status": ...}envelope is returned unchanged, so a device that refused a write says so in its own words. Only the relay’s own failures — a server that stopped answering, a reply that was not JSON — are reported as the agent’s.- Parameters:
address (str) – The device server’s endpoint.
request (dict[str, Any]) – The request to forward.
timeout_ms (int) – How long to wait for the reply.
- Returns:
The reply envelope.
- Return type:
dict[str, Any]
- plesty.server.services.agent._drop_relay_socket(address: str) None
Close and forget this thread’s socket to address.
- Parameters:
address (str)
- Return type:
None
- plesty.server.services.agent._version() str
- Return type:
str