plesty.bench ============ .. py:module:: plesty.bench .. autoapi-nested-parse:: ``plesty.bench`` — talk to a bench running ``plesty-server`` from another machine. This is the *client* half of plesty-server and the only part published to PyPI (distribution ``plesty-bench``). It depends on ``pyzmq`` and ``click`` and imports nothing from ``plesty.server``: the developer machine that runs the remote field-test loop, or a script on the experiment PC, installs this and nothing of the bench application. The contract is deliberately thin — *the CLI is the API*: the bench agent accepts a ``plesty-server`` argument list, runs it, and returns the exit code and the output. Anything the command line can do is remote, with no second schema to keep in step. .. code-block:: python from plesty.bench import BenchClient with BenchClient("tcp://lab-bench-03:5550", token="…") as bench: print(bench.describe()["result"]["version"]) result = bench.exec(["status", "--json"]) job = bench.exec(["field-test", "pm100d", "--no-wait"]).stdout.strip() bench.follow(job, print) # tails the job log until it ends data = bench.fetch("repos/plesty-pm100d/exp/reports/pm100d-s120c/field-test.json") Or from a shell: ``plesty-bench -b lab-bench-03 -- status``. Submodules ---------- .. toctree:: :maxdepth: 1 /reference/plesty/bench/cli/index /reference/plesty/bench/client/index Attributes ---------- .. autoapisummary:: plesty.bench.DEFAULT_PORT Exceptions ---------- .. autoapisummary:: plesty.bench.BenchClientError Classes ------- .. autoapisummary:: plesty.bench.BenchClient plesty.bench.ExecResult Functions --------- .. autoapisummary:: plesty.bench.parse_address Package Contents ---------------- .. py:data:: DEFAULT_PORT :value: 5550 .. py:class:: BenchClient(address: str | None = None, token: str | None = None, timeout_ms: int = 10000, context: zmq.Context[Any] | None = None) A connection to one bench agent. Connect lazily to *address*. :param address: ``host``, ``host:port`` or ``tcp://host:port``; ``None`` reads ``PLESTY_BENCH``. :param token: Shared secret the agent was started with; ``None`` reads ``PLESTY_BENCH_TOKEN``. :param timeout_ms: Default reply timeout for :meth:`request`. :param context: ZMQ context to use; the global instance by default. .. py:attribute:: address .. py:attribute:: token .. py:attribute:: timeout_ms :value: 10000 .. py:attribute:: _context :value: None .. py:attribute:: _socket :type: zmq.Socket[Any] | None :value: None .. py:method:: __enter__() -> BenchClient Open the socket. .. py:method:: __exit__(*_exc: object) -> None Close the socket. .. py:method:: close() -> None Close the socket (a later request reconnects). .. py:method:: _connect() -> zmq.Socket[Any] .. py:method:: request(message: dict[str, Any], timeout_ms: int | None = None) -> dict[str, Any] Send one request and return the ``result`` of an ``ok`` reply. :raises BenchClientError: On no reply in time, a malformed reply, or an ``error`` reply (its message is the exception text). .. py:method:: describe(timeout_ms: int | None = None) -> dict[str, Any] The agent's self-description (kind, version, home, commands). .. py:method:: ping(timeout_ms: int = 2000) -> bool Whether a bench agent answers at the address. .. py:method:: exec(argv: collections.abc.Sequence[str], timeout_ms: int | None = None) -> ExecResult Run ``plesty-server `` on the bench and return what it produced. The result carries the exit code; it does not raise on a non-zero exit, only when the agent could not run the command at all. .. py:method:: fetch(path: str, timeout_ms: int | None = None) -> bytes Download a file from the bench home (``path`` relative to it). .. py:method:: follow(job_id: str, sink: collections.abc.Callable[[str], None], poll_s: float = 0.5, timeout_s: float | None = None) -> int Stream the log of *job_id* to *sink* until the job ends; returns its exit code. Uses ``jobs log --offset N --json`` on the bench, so nothing is held open between polls and a lost connection just resumes. :raises BenchClientError: When the job is unknown or the bench stops answering. .. py:exception:: BenchClientError Bases: :py:obj:`RuntimeError` The bench did not answer, refused the request, or the command failed to run. Initialize self. See help(type(self)) for accurate signature. .. py:class:: ExecResult What one remote ``plesty-server`` invocation produced. :ivar argv: The arguments that were run. :ivar code: Exit code. :ivar stdout: Captured standard output. :ivar stderr: Captured standard error (progress lines). .. py:attribute:: argv :type: tuple[str, Ellipsis] .. py:attribute:: code :type: int .. py:attribute:: stdout :type: str .. py:attribute:: stderr :type: str .. py:property:: ok :type: bool Whether the command exited 0. .. py:method:: json() -> Any Parse stdout as JSON (for commands run with ``--json``). .. py:function:: parse_address(value: str | None) -> str Turn ``host``, ``host:port`` or ``tcp://host:port`` into a ZMQ endpoint. :param value: The address; ``None`` reads ``PLESTY_BENCH``, else ``localhost``.