plesty.server.presenter.bench

The local bench: one object that composes the model and the services.

Bench is what the CLI and the GUI talk to. It owns a Home, a Fleet, and the four services, and answers the operator’s questions — what is declared, what is installed, what is running — and carries out the operator’s intents — install, start, stop, field-test, bring the fleet up. Anything that takes longer than a request/reply (an install, a field test) it starts as a job by re-invoking plesty-server _job … through the detached launcher (Bench.install(), Bench.field_test()); the synchronous twins (install_now(), field_test_now()) are what those jobs run.

Attributes

Say

SERVE_TIMEOUT_MS

SHARE_CHECK_DIR

_RUN_KEYS

DEVICE_ENV_SEPARATOR

_VARIABLE

UNKNOWN_SOURCE

TERMINAL_KIND

LAST_OPERATOR_FILE

WELCOME_SEEN_FILE

DEMO

TRAFFIC_LINES

FIELD_TEST_ELSEWHERE

Exceptions

BenchError

An operation could not be carried out as asked.

Classes

VenvContext

Where a command for one device runs: which interpreter, in which directory.

AgentHandle

The agent this process is serving, and what it takes to stop it.

DeviceView

What the operator sees about one device instance.

SourceOptions

What a declaration form can offer for a package's source.

InstalledModule

One installed version of a module, as Hub ▸ Installed modules… lists it.

Bench

The local bench.

Functions

_setup_entries(→ dict[str, dict[str, Any]])

The devices: of the module's setup.yaml as written; empty when unreadable.

_served_entry(→ dict[str, Any])

A setup entry as the bench serving it declares it: without the run keys.

_device_env(→ dict[str, str])

What an experiment's declaration sets for one device of its setup, by stem.

_expanded(→ str)

text with each ${NAME} replaced by its value in located.

_check_config(→ None)

Refuse a configuration a run would refuse: one that is not a YAML mapping.

_planned_config(→ dict[str, Any] | None)

The configuration a started run froze into its plan; None when it has none.

_run_source(→ str)

What a run's Source cell says: the version that measured it.

_canonical(→ str)

A distribution name as PyPI compares them: case and _/./- alike.

_waiting_runs_first(→ list[dict[str, Any]])

Order runs with the ones not yet started on top, the newest created first.

control_blocked(→ str | None)

Why a device in this state cannot be controlled, or None when it can.

_example_blocks(→ dict[str, str])

Each KEY= line of an example with the comment lines directly above it, keyed by KEY.

_moment(→ datetime.datetime | None)

An ISO timestamp as an aware datetime; None when absent or unreadable.

release_tag(→ bool)

Whether ref names a release (v0.3.1) rather than a branch.

module_spec(→ plesty.server.model.DeviceSpec)

A declaration standing in for a bare module install.

_node_view(→ DeviceView | None)

Rebuild one device of the node host from its list --json entry.

_node_status(→ plesty.server.model.ProcessStatus)

A status from a node's JSON, keeping only the fields this version knows.

_stderr(→ None)

fleet_name(→ str)

What a fleet is called: its file's name without .yaml (fleets/default.yaml).

_same_file(→ bool)

Whether one and other name the same file, however each is spelled.

_now(→ str)

Module Contents

plesty.server.presenter.bench.Say
plesty.server.presenter.bench.SERVE_TIMEOUT_MS = 600000
plesty.server.presenter.bench.SHARE_CHECK_DIR = '.plesty-share-check'
plesty.server.presenter.bench._RUN_KEYS = ('host_name', 'data_dir')
plesty.server.presenter.bench._setup_entries(checkout: pathlib.Path) → dict[str, dict[str, Any]]

The devices: of the module’s setup.yaml as written; empty when unreadable.

Kept whole, for the bench serving a device to declare it by; the run-side reading of the same file is plesty.lib.experiment.Setup.

Parameters:

checkout (pathlib.Path)

Return type:

dict[str, dict[str, Any]]

plesty.server.presenter.bench._served_entry(entry: collections.abc.Mapping[str, Any], located: collections.abc.Mapping[str, str] | None = None) → dict[str, Any]

A setup entry as the bench serving it declares it: without the run keys.

Where the device writes is not added: the bench that starts it gives that from its own share and folder (Bench.device_env()).

A ${VARIABLE} in its args is the variable’s value, as a bench’s fields in the same file are (#43): a device that follows the runs — --replay-run-root ${LOCAL_DATA}/pol-pl — is told the folder this machine’s .env names, not one written into the file for every machine.

Parameters:
  • entry (collections.abc.Mapping[str, Any]) – The device’s setup.yaml entry.

  • located (collections.abc.Mapping[str, str] | None) – The variables, as the experiment’s environment has them.

Raises:

BenchError – When an argument names a variable that is not set.

Return type:

dict[str, Any]

plesty.server.presenter.bench.DEVICE_ENV_SEPARATOR = '__'
plesty.server.presenter.bench._device_env(env: collections.abc.Mapping[str, str], stem: str) → dict[str, str]

What an experiment’s declaration sets for one device of its setup, by stem.

<STEM>__<NAME> in the declaration’s env is NAME for the device whose stem is STEM (spec → SPEC, as in SPEC_ADDRESS), when this experiment serves it — and only then (#43). The welcome’s demo used to write its spectrometer’s folders into that device’s own .env, which every later experiment serving a spec inherited.

Parameters:
  • env (collections.abc.Mapping[str, str])

  • stem (str)

Return type:

dict[str, str]

plesty.server.presenter.bench._VARIABLE
plesty.server.presenter.bench._expanded(text: str, located: collections.abc.Mapping[str, str]) → str

text with each ${NAME} replaced by its value in located.

Parameters:
  • text (str)

  • located (collections.abc.Mapping[str, str])

Return type:

str

plesty.server.presenter.bench._check_config(name: str, text: str) → None

Refuse a configuration a run would refuse: one that is not a YAML mapping.

Raises:

BenchError – Naming what is wrong with it.

Parameters:
  • name (str)

  • text (str)

Return type:

None

plesty.server.presenter.bench._planned_config(run_dir: pathlib.Path) → dict[str, Any] | None

The configuration a started run froze into its plan; None when it has none.

Parameters:

run_dir (pathlib.Path)

Return type:

dict[str, Any] | None

plesty.server.presenter.bench.UNKNOWN_SOURCE = 'unknown — the run recorded no version'
plesty.server.presenter.bench._run_source(status: dict[str, Any], package: str, installed: str) → str

What a run’s Source cell says: the version that measured it.

Parameters:
  • status (dict[str, Any]) – The run’s status, with the provenance its start journaled.

  • package (str) – The experiment’s distribution.

  • installed (str) – The current install’s line (Bench.experiment_source()).

Returns:

<package>==<version> from the run’s own provenance once it has started; the current install marked as what it will run on while it waits; UNKNOWN_SOURCE for a started run that recorded none.

Return type:

str

plesty.server.presenter.bench._canonical(name: str) → str

A distribution name as PyPI compares them: case and _/./- alike.

Parameters:

name (str)

Return type:

str

plesty.server.presenter.bench._waiting_runs_first(rows: list[dict[str, Any]]) → list[dict[str, Any]]

Order runs with the ones not yet started on top, the newest created first.

A created run has no start time, so by name alone it lands wherever its name happens to sort. Put at the bottom, under every measurement, the run just created was out of sight, and creating it read as having failed — an operator duplicated a run three times and saw none of them (#31). The run just created is the one the next Run is meant for, so it leads.

Parameters:

rows (list[dict[str, Any]]) – The runs, newest first by name.

Returns:

The same rows, reordered.

Return type:

list[dict[str, Any]]

plesty.server.presenter.bench.control_blocked(status: plesty.server.model.ProcessStatus) → str | None

Why a device in this state cannot be controlled, or None when it can.

Pure, and taking the status rather than a name, so the window can decide whether to enable Control… from the state it already painted — a check that probed a server on every table refresh would be a round trip per device per tick, on the GUI thread.

Controls need a server that answers: the panel reads the device’s schema over the wire, and a stopped server has nothing to read it from.

Parameters:

status (plesty.server.model.ProcessStatus) – The server’s observed state.

Returns:

The reason a tooltip shows, or None.

Return type:

str | None

exception plesty.server.presenter.bench.BenchError

Bases: RuntimeError

An operation could not be carried out as asked.

Initialize self. See help(type(self)) for accurate signature.

plesty.server.presenter.bench.TERMINAL_KIND = 'cmd'
plesty.server.presenter.bench.LAST_OPERATOR_FILE = 'last-operator.txt'
plesty.server.presenter.bench.WELCOME_SEEN_FILE = 'welcome-seen'
plesty.server.presenter.bench.DEMO: dict[str, str]
plesty.server.presenter.bench.TRAFFIC_LINES = 2000
class plesty.server.presenter.bench.VenvContext

Where a command for one device runs: which interpreter, in which directory.

Variables:
  • name – The device instance.

  • python – The interpreter of the environment serving it.

  • cwd – The directory a command runs in — the checkout for a git install, the device’s run directory for a release.

  • installed – The installation this describes.

name: str
python: pathlib.Path
cwd: pathlib.Path
installed: plesty.server.model.InstalledVersion
property scripts: pathlib.Path

The environment’s executables — plesty, pytest, python.

Return type:

pathlib.Path

property label: str

One line naming what commands here will run against.

Return type:

str

path_env() → dict[str, str]

PATH with this environment first, so its console scripts win.

Return type:

dict[str, str]

class plesty.server.presenter.bench.AgentHandle

The agent this process is serving, and what it takes to stop it.

Variables:
  • service – The agent answering requests.

  • thread – The daemon thread it serves on.

  • stop – Set to end the serve loop at its next poll.

  • settings – What it was started with — a later edit to agent.yaml does not move a bound socket.

service: plesty.server.services.Agent
thread: threading.Thread
stop: threading.Event
settings: plesty.server.model.AgentSettings
class plesty.server.presenter.bench.DeviceView

What the operator sees about one device instance.

Variables:
  • spec – The fleet declaration.

  • installed – The installed version that serves it, or None.

  • status – The server’s observed state.

  • field_test_blocked – Why a field test cannot run, or None when it can.

  • control_blocked – Why its controls cannot be opened, or None.

  • info – The package-level record (repository, project page, latest release).

  • host – The node this device runs on, empty for this bench’s own. What the Host column shows, and what decides whether an action is carried out here or relayed.

  • fleet – The fleet of this bench that declares it; empty for a node’s.

spec: plesty.server.model.DeviceSpec
installed: plesty.server.model.InstalledVersion | None
status: plesty.server.model.ProcessStatus
field_test_blocked: str | None = None
control_blocked: str | None = None
info: plesty.server.model.PackageInfo | None = None
host: str = ''
fleet: str = ''
property name: str

The instance name.

Return type:

str

property remote: bool

Whether this device runs on a node rather than on this bench.

Return type:

bool

property source: str

the requirement plus the commit for a checkout.

Type:

One line

Return type:

str

class plesty.server.presenter.bench.SourceOptions

What a declaration form can offer for a package’s source.

Variables:
  • versions – Release versions known for the package: every release on PyPI newest first once fetched, then what this bench installed and the catalogue’s.

  • refs – Git refs known for it (installed checkouts, the repository’s tags once fetched, the usual branches). A tag whose release is in versions is left to the release source.

  • git_urls – Repository URLs known for it.

versions: tuple[str, Ellipsis] = ()
refs: tuple[str, Ellipsis] = ()
git_urls: tuple[str, Ellipsis] = ()
class plesty.server.presenter.bench.InstalledModule

One installed version of a module, as Hub ▸ Installed modules… lists it.

Variables:
  • installed – The inventory record.

  • kind – device or experiment — what the module says it is, else what this bench declares it as; "" when neither says.

  • used_by – The declarations this version serves. While there is one, it is not uninstalled.

installed: plesty.server.model.InstalledVersion
kind: str = ''
used_by: tuple[str, Ellipsis] = ()
class plesty.server.presenter.bench.Bench(home: plesty.server.model.Home, fleet: plesty.server.model.Fleet | None = None, manager: plesty.server.model.DeviceManager | None = None, installer: plesty.server.services.Installer | None = None, jobs: plesty.server.services.JobRunner | None = None, supervisor: plesty.server.services.Supervisor | None = None, say: Say | None = None, connect_node: Any | None = None)

The local bench.

Compose a bench.

Parameters:
home
fleet
manager
installer
jobs
supervisor
_say: Say
_agent: AgentHandle | None = None
_nodes: plesty.server.services.Nodes | None = None
_connect_node = None
view: frozenset[str] | None = None
node_view: frozenset[str] | None = None
_fleet_cache: dict[pathlib.Path, tuple[int, plesty.server.model.Fleet]]
_current_stamp: int | None
_run_roots: dict[str, tuple[tuple[str, Ellipsis], str]]
_published: dict[str, tuple[tuple[str, Ellipsis], tuple[str, Ellipsis]]]
_released_jobs: set[str]
classmethod open(home: str | os.PathLike[str] | None = None, fleet_path: str | os.PathLike[str] | None = None, say: Say | None = None) → Bench

Build a bench from the environment: the home, and the fleet file it finds.

Parameters:
  • home (str | os.PathLike[str] | None) – Explicit home directory (--home); else the default.

  • fleet_path (str | os.PathLike[str] | None) – Explicit fleet file (-f); else ./fleet.yaml, then <home>/fleet.yaml, then the fleet used last, then <home>/fleets/default.yaml — created empty if missing.

  • say (Say | None) – Progress sink.

Return type:

Bench

say(line: str) → None

Emit a progress line.

Parameters:

line (str)

Return type:

None

say_to(say: Say) → None

Send progress lines to say from now on.

The console does this at start-up. Without it these lines go to stderr, which a windowed process on Windows discards outright — so “port 5551 is in use — moving to 5553” was written to nowhere while the fleet file was quietly edited.

Parameters:

say (Say)

Return type:

None

spec(name: str) → plesty.server.model.DeviceSpec

The declaration of name, in the current fleet or any other of this bench.

Raises:

BenchError – When no fleet declares it.

Parameters:

name (str)

Return type:

plesty.server.model.DeviceSpec

fleet_files() → list[pathlib.Path]

Every fleet this bench has: <home>/fleets/*.yaml, and the current file.

Return type:

list[pathlib.Path]

fleets() → dict[str, plesty.server.model.Fleet]

Every fleet this bench has, by name, each as its file says now.

Read from disk each time — a file only when it changed since — so what the agent declares shows in the console without a restart (#18). A file that does not read is said and left out.

Return type:

dict[str, plesty.server.model.Fleet]

fleet_of(name: str) → str | None

The fleet that declares device name, or None.

Parameters:

name (str)

Return type:

str | None

shown_fleets() → dict[str, plesty.server.model.Fleet]

The fleets the console shows: every one, or those in view.

Return type:

dict[str, plesty.server.model.Fleet]

new_fleet(name: str) → pathlib.Path

Create the empty fleet name beside this bench’s others; returns its file.

The current fleet stays current: a client creates a fleet to declare into it (declare --fleet NAME), not to move the bench onto it.

Raises:

BenchError – When the name is unusable or the fleet exists.

Parameters:

name (str)

Return type:

pathlib.Path

fleet_rows() → list[dict[str, Any]]

Every fleet: name, path, devices (a count) and current.

Return type:

list[dict[str, Any]]

fleet_file(fleet: str | None) → pathlib.Path

The file of the fleet called fleet, or of the current one.

Raises:

BenchError – When this bench has no fleet of that name.

Parameters:

fleet (str | None)

Return type:

pathlib.Path

_edit_fleet(path: pathlib.Path, change: collections.abc.Callable[[plesty.server.model.Fleet], plesty.server.model.Fleet]) → plesty.server.model.Fleet

Re-read the fleet at path, make change, write it back; returns the result.

Never written from what was read earlier: the agent and the console are two writers of the same file, and a write from a stale copy undid what the other had just declared (#18).

Raises:

BenchError – When path is an experiment’s setup, which the bench reads and never writes (#48).

Parameters:
Return type:

plesty.server.model.Fleet

_check_free(name: str, fleet_path: pathlib.Path) → None

Refuse name when another fleet of this bench already declares it.

A name keys the run directory, the log and the process record, so two fleets declaring it would share all three.

Parameters:
  • name (str)

  • fleet_path (pathlib.Path)

Return type:

None

declare(spec: plesty.server.model.DeviceSpec, save: bool = True, fleet: str | None = None) → plesty.server.model.DeviceSpec

Add or replace spec, allocating a port when it names none.

Parameters:
  • spec (plesty.server.model.DeviceSpec) – The declaration.

  • save (bool) – Write the fleet file back (creating one when there is none).

  • fleet (str | None) – The fleet to declare it in; the one that already declares it, else the current one, by default.

Raises:

BenchError – When another fleet declares the name, or fleet is not one.

Return type:

plesty.server.model.DeviceSpec

forget(name: str, save: bool = True) → None

Remove name from the fleet that declares it (no error when absent).

Parameters:
  • name (str)

  • save (bool)

Return type:

None

_save_fleet() → pathlib.Path

Write the current fleet back, to <home>/fleets/default.yaml when it has no file.

A declaration that is not on disk is invisible to the jobs the console starts — they are separate processes that read the fleet file. So a bench that declares a device gets a fleet file, whether or not anyone asked for one.

Return type:

pathlib.Path

remove(name: str) → bool

Drop name from the fleet, stopping its server first; True when one was stopped.

The environment stays installed (another instance may use it); the .env stays too, in case the device is declared again.

Parameters:

name (str)

Return type:

bool

rename(old: str, new: str) → plesty.server.model.DeviceSpec

Declare old under new, moving with it what the name keys on disk.

A device name is not a label: it names the working directory the server runs in — which holds its .env and the logs plesty-lib writes there — and its own server log. Removing the device and declaring it again is what an operator does today to fix a typo, and it loses both. So the rename moves them.

The server has to be stopped first: its process record, the port it bound and the directory it has open are all keyed by the name it was started under.

Parameters:
  • old (str) – The declared name.

  • new (str) – The name to give it; unchanged when equal to old.

Returns:

The renamed declaration.

Raises:

BenchError – When old is not declared, its server still runs, the new name is unusable, or something already holds it.

Return type:

plesty.server.model.DeviceSpec

set_autostart(name: str, flag: bool) → plesty.server.model.DeviceSpec

Include name in (or drop it from) the working fleet; persists to the fleet file.

Parameters:
  • name (str)

  • flag (bool)

Return type:

plesty.server.model.DeviceSpec

property working: list[plesty.server.model.DeviceSpec]

the declared devices marked autostart.

Type:

The working fleet

Return type:

list[plesty.server.model.DeviceSpec]

save_fleet_as(path: str | os.PathLike[str], names: collections.abc.Sequence[str] | None = None) → pathlib.Path

Write the working fleet (or names) to path as a new fleet file; returns it.

The file is portable: it carries the declarations, not this bench’s home. Every device written is marked autostart — it is the fleet of that file. The declared experiments go with it: an experiment has no autostart to set and is part of what this bench is set up to do.

Parameters:
  • path (str | os.PathLike[str])

  • names (collections.abc.Sequence[str] | None)

Return type:

pathlib.Path

create_fleet(path: str | os.PathLike[str]) → plesty.server.model.Fleet

Write an empty fleet file at path and make it the current one.

Raises:

BenchError – When path already exists.

Parameters:

path (str | os.PathLike[str])

Return type:

plesty.server.model.Fleet

load_fleet(path: str | os.PathLike[str]) → plesty.server.model.Fleet

Replace the declarations with the fleet file at path (the home stays).

Nothing is stopped: the console loads the remembered fleet at start-up and the servers it finds running are the ones it is there to supervise. Swapping one fleet for another is open_fleet().

Parameters:

path (str | os.PathLike[str])

Return type:

plesty.server.model.Fleet

open_fleet(path: str | os.PathLike[str], create: bool = False) → plesty.server.model.Fleet

Stop what this bench has running, then adopt the fleet file at path.

Replacing the declarations alone left the servers of the old fleet running with no way to reach them: the console is filled from the fleet, so they dropped out of the table while their PIDs, launch records and TCP ports lived on — and a device of the same name in the newly opened fleet then could not bind. Everything with a launch record is stopped, the working fleet and hand-started devices alike; running() names them beforehand, for the operator to confirm.

The new file is read before anything is stopped, so a path that is not a fleet costs nothing: the running servers are still there to go back to.

Parameters:
  • path (str | os.PathLike[str]) – The fleet file to adopt.

  • create (bool) – Write an empty fleet file at path first (New fleet) instead of reading an existing one.

Raises:
  • BenchError – When create is set and path already exists.

  • FleetError – When path is not a readable fleet document.

Return type:

plesty.server.model.Fleet

remember_fleet() → None

Note the current fleet file as the one to reopen next time (console start-up).

Return type:

None

fleet_text() → str

The current fleet as YAML text (what the fleet-file editor shows).

Return type:

str

replace_fleet_text(text: str) → plesty.server.model.Fleet

Validate text as a fleet file, write it to the fleet path and adopt it.

Raises:

FleetError – When the text is not a valid fleet document.

Parameters:

text (str)

Return type:

plesty.server.model.Fleet

env_file(name: str) → pathlib.Path

The device’s .env file: <home>/run/<name>/.env.

Parameters:

name (str)

Return type:

pathlib.Path

read_env_text(name: str) → str

The .env text of name (empty when there is none).

Parameters:

name (str)

Return type:

str

write_env_text(name: str, text: str) → pathlib.Path

Write the .env of name; returns the path.

Parameters:
  • name (str)

  • text (str)

Return type:

pathlib.Path

process_env(spec: plesty.server.model.DeviceSpec) → dict[str, str]

The environment a server or field test of spec runs with.

The bench’s own environment, then device_env().

Parameters:

spec (plesty.server.model.DeviceSpec)

Return type:

dict[str, str]

device_env(spec: plesty.server.model.DeviceSpec) → dict[str, str]

What the bench sets for spec on top of its own environment.

Where it writes — the bench data path as DEVICE_DATA_ROOT and its data_dir under it as DEVICE_DATA_PATH — then the device’s .env file, then the fleet’s env: (explicit in the declaration, so it wins). A run serving the device overrides the folder the same way.

Parameters:

spec (plesty.server.model.DeviceSpec)

Return type:

dict[str, str]

venv_context(name: str) → VenvContext

The interpreter and directory a command for name would run in.

Raises:

BenchError – When nothing is installed for name.

Parameters:

name (str)

Return type:

VenvContext

run_in_venv(name: str, line: str) → plesty.server.model.Job

Run the command line against the environment installed for name.

The point is the interpreter. plesty check, plesty init and plesty field-test have to run against the venv that serves the device, which until now meant finding that path by hand in a system terminal and leaving the console to use it. The venv’s script directory goes first on PATH, so plesty, python and pytest are that environment’s.

It is not a shell: the line is split into an argument list and handed to the process, so there is no pipe, no redirection and no glob. What it buys is the right interpreter, not a second way to compose shell.

Raises:

BenchError – When nothing is installed for name, the line is empty, or it cannot be split into arguments.

Parameters:
  • name (str)

  • line (str)

Return type:

plesty.server.model.Job

experiments() → list[plesty.server.model.ExperimentSpec]

Every declared experiment, in file order.

Return type:

list[plesty.server.model.ExperimentSpec]

experiment(name: str) → plesty.server.model.ExperimentSpec

The declaration of experiment name.

Raises:

BenchError – When it is not declared.

Parameters:

name (str)

Return type:

plesty.server.model.ExperimentSpec

declare_experiment(spec: plesty.server.model.ExperimentSpec, save: bool = True) → plesty.server.model.ExperimentSpec

Add or replace an experiment declaration; returns it.

Parameters:
Raises:

BenchError – When a device, or another fleet, already holds that name.

Return type:

plesty.server.model.ExperimentSpec

forget_experiment(name: str, save: bool = True) → None

Drop experiment name from the fleet, leaving its runs on disk.

The runs are the measurement and outlive the declaration: removing them is purge()’s business and is asked for separately.

Parameters:
  • name (str) – The declared name.

  • save (bool) – Write the fleet file, when there is one.

Return type:

None

experiment_context(name: str) → VenvContext

The interpreter and checkout a run of name would use.

Raises:

BenchError – When nothing is installed, or the module was installed from a release — an experiment’s configuration lives in its repository, so a release install has nothing to run against.

Parameters:

name (str)

Return type:

VenvContext

experiment_env(name: str) → dict[str, str]

The environment a run of name would get.

The bench’s own, then the checkout’s .env, then what the fleet declares — so pointing one experiment at a different stage is an edit to the declaration and not to the module’s deployment file.

Parameters:

name (str)

Return type:

dict[str, str]

unset_reasons(name: str, missing: collections.abc.Sequence[str]) → list[str]

Why the bench could not give a run each of missing — what to set so it can.

Where this machine sees the shared disk comes from the bench without an address in the module’s setup, and each device’s address from the bench serving it, so a missing one is a setting missing there — a bench’s data, a device the setup does not list — or a variable the checkout’s .env does not set, and the message names that.

Parameters:
  • name (str)

  • missing (collections.abc.Sequence[str])

Return type:

list[str]

local_endpoint() → str

Where this bench’s own agent listens — the bench a device’s local host is.

Return type:

str

static _setup(checkout: pathlib.Path) → Any

The module’s setup.yaml (a plesty.lib.experiment.Setup).

Empty when it is missing or broken: a broken setup is reported where it is edited, not here, and a run still starts with the addresses and paths its .env gives it.

Parameters:

checkout (pathlib.Path)

Return type:

Any

experiment_manifest(name: str) → plesty.server.model.ExperimentManifest

Ask the module of name what it takes.

Read from the module every time rather than cached in the fleet: a declaration carrying a copy of the contract would be a second thing to keep in step, and an updated module would leave it stale.

Raises:

BenchError – When nothing is installed, or the module publishes no manifest.

Parameters:

name (str)

Return type:

plesty.server.model.ExperimentManifest

experiment_devices(name: str, config: str) → list[str] | None

The devices of name’s setup a run of config uses; None for all.

Asked of a module whose manifest says it chooses (plesty-lib#62): one module may measure in place with a few devices or navigate with more on another bench, and serving the others would start instruments no step touches.

Raises:

BenchError – When the module could not say.

Parameters:
  • name (str)

  • config (str)

Return type:

list[str] | None

experiment_config_file(name: str, config: str | None = None) → pathlib.Path

The configuration file a run of name would read — or config, one of its others.

Parameters:
  • name (str) – The declared experiment.

  • config (str | None) – A configuration file relative to the checkout (config/demo.yaml); the declared one when None.

Raises:

BenchError – When config names a file outside the checkout.

Return type:

pathlib.Path

experiment_source(name: str) → str

One line naming what a run of name would execute.

The same line the device table carries, for the same reason: a run is only reproducible if what produced it can be named, and a declaration alone does not say which version answered. Reads the install rather than the declaration, so a checkout shows the commit it is on.

Parameters:

name (str) – The declared experiment.

Returns:

plesty-pol-pl==0.1.0 (abc1234), or the requirement marked (not installed) when nothing is installed for it yet.

Return type:

str

experiment_configs(name: str) → list[str]

The configuration files name’s checkout offers, declared one first.

A module ships its config/ in the repository rather than in the wheel, so what a run may be given is whatever is in the checkout — a measured configuration and a demo one beside it, say. Reading the directory is the only way to know: nothing declares the alternatives, and a bench that offered only the declared file would hide them.

Parameters:

name (str) – The declared experiment.

Returns:

Paths relative to the checkout (config/default.yaml). The declared configuration leads and is always present, even when the checkout has yet to be made, so the caller always has something to offer.

Return type:

list[str]

experiment_config_copy(name: str, config: str | None = None) → pathlib.Path

Where this bench keeps its own copy of a configuration file of name.

Configuring an experiment never writes the module’s files — they change only through its repository — so what the operator saves goes here, under the experiment’s folder in the bench home (<home>/run/<name>/config/demo.yaml), and new runs start from it.

Raises:

BenchError – When config names a file outside the checkout.

Parameters:
  • name (str)

  • config (str | None)

Return type:

pathlib.Path

experiment_config_path(name: str, config: str | None = None) → pathlib.Path

The file new runs of name take config from: the bench’s copy, else the module’s.

Parameters:
  • name (str)

  • config (str | None)

Return type:

pathlib.Path

read_experiment_config(name: str, config: str | None = None) → str

The configuration’s text — the declared one, or config; empty when absent.

The bench’s own copy when it has saved one, else the module’s file.

Parameters:
  • name (str)

  • config (str | None)

Return type:

str

write_experiment_config(name: str, text: str, config: str | None = None) → pathlib.Path

Save the configuration of name — or config, one of its others; returns where.

Written to the bench’s copy (experiment_config_copy()), never to the module’s file, which is left as its repository has it.

Raises:

BenchError – When the text is not a YAML mapping, or cannot be written. A run refuses a configuration it cannot parse, and refusing it here costs nothing started.

Parameters:
  • name (str)

  • text (str)

  • config (str | None)

Return type:

pathlib.Path

read_run_config(name: str, run_id: str) → dict[str, Any]

What the run run_id of name is configured with, and whether it may change.

A run created here has its own copy (RUN_CONFIG_FILE), and that is what it is started with. A run that has started was planned from its configuration, which its plan keeps; one created before runs had copies still names the module’s file.

Returns:

text, the configuration; config, the module’s file it came from; path, where it is read; frozen, whether the run has started and the configuration can no longer change.

Raises:

BenchError – When there is no such run.

Parameters:
  • name (str)

  • run_id (str)

Return type:

dict[str, Any]

write_run_config(name: str, run_id: str, text: str, config: str) → pathlib.Path

Configure the run run_id of name alone, before it has started.

The run’s own copy is replaced; the module’s files, and every other run, are left as they are.

Parameters:
  • name (str) – The declared experiment.

  • run_id (str) – The run.

  • text (str) – The configuration it is to be run with.

  • config (str) – The module’s file it was taken from (config/demo.yaml).

Returns:

The run’s configuration file.

Raises:

BenchError – When there is no such run, it has started, or the text is not a YAML mapping.

Return type:

pathlib.Path

experiment_setup_file(name: str) → pathlib.Path

The setup.yaml the module of name ships at its root — which may be missing.

Raises:

BenchError – When nothing is installed to look in.

Parameters:

name (str)

Return type:

pathlib.Path

experiment_dotenv(name: str) → dict[str, str]

The values the checkout’s .env holds — where its setup’s variables are set.

Raises:

BenchError – When nothing is installed to look in.

Parameters:

name (str)

Return type:

dict[str, str]

write_experiment_setup(name: str, text: str | None, values: collections.abc.Mapping[str, str] | None = None) → pathlib.Path

Replace the module’s setup.yaml with text, and set values in its .env.

The setup names the variables, the .env holds their values: a bench edited by value is saved as both (plesty.lib.experiment.setup.split_benches()).

Parameters:
  • name (str) – The experiment.

  • text (str | None) – The setup’s new text; None leaves it as it is.

  • values (collections.abc.Mapping[str, str] | None) – Variable → value for .env; the rest of it is kept.

Raises:

BenchError – When the text is not a setup — refused here rather than when a run reads it — or a file cannot be written.

Return type:

pathlib.Path

experiment_run_root(name: str) → pathlib.Path

Where the runs of name land: its root on the shared disk, else this bench’s home.

With the module’s setup saying where this machine sees the shared data disk (PLESTY_DATA), runs go to the root the module declares there — pol-pl → /mnt/plesty/pol-pl — the folder every bench sees, so a device on any of them can write into a run. Without one, or for a module that cannot say, they stay under this bench’s home.

Parameters:

name (str)

Return type:

pathlib.Path

_declared_run_root(name: str) → str | None

The run root the module of name declares; None when it cannot be read.

Parameters:

name (str)

Return type:

str | None

experiment_runs(name: str) → list[pathlib.Path]

The run directories of name, newest first.

Parameters:

name (str)

Return type:

list[pathlib.Path]

experiment_run(name: str, run_id: str | None = None) → pathlib.Path | None

The run directory run_id names, or the newest run of name.

Parameters:
  • name (str)

  • run_id (str | None)

Return type:

pathlib.Path | None

experiment_status(name: str, run_id: str | None = None) → dict[str, Any]

What a run of name is doing, read from its run directory.

Nothing is asked of the run: the journal, the plan and the progress file are this bench’s own files, so a refresh costs a directory read and cannot disturb a measurement.

Returns:

The journal’s status, with run (the run id, or None when there is none yet), total steps, step (what the step in flight last reported) and asked (what the run is currently being asked for).

Parameters:
  • name (str)

  • run_id (str | None)

Return type:

dict[str, Any]

serve_experiment_devices(name: str, devices: collections.abc.Sequence[str] | None = None) → tuple[dict[str, str], list[str]]

Have each device of name’s setup running on its bench; the run’s addresses.

The module’s setup.yaml says which bench each device runs on (host_name); that bench declares it when it lacks it, starts it on a port of its own choosing, and says the port. This bench serves its own devices itself and asks another bench through its agent — the link Server ▸ Benches drives it by — so a run is only ever told an address a server is listening on. A device whose <DEVICE>_ADDRESS is set by hand, in the checkout’s .env or the declaration, is left alone: whoever set it said where the device is.

Parameters:
  • name (str) – The declared experiment.

  • devices (collections.abc.Sequence[str] | None) – Only these devices of the setup — the ones the run’s configuration uses (experiment_devices()); None for all.

Returns:

<DEVICE>_ADDRESS for every device served, and the devices this call started — the run’s to stop when it ends (release_experiment_devices()); one already running was someone else’s and is left to them.

Raises:

BenchError – When a bench could not be asked, or could not serve a device.

Return type:

tuple[dict[str, str], list[str]]

release_experiment_devices(name: str, devices: collections.abc.Sequence[str]) → list[str]

Stop devices of name’s setup, each on its bench: a run’s, once it has ended.

The devices a run started (serve_experiment_devices()) are its own, and are stopped however it ended — completed, stopped, aborted or killed. A device that is not running any more is passed over, and one that cannot be stopped is reported, not raised: the run is over either way.

Parameters:
  • name (str) – The declared experiment.

  • devices (collections.abc.Sequence[str]) – The devices to stop, as its setup names them.

Returns:

The devices stopped.

Return type:

list[str]

A link to the agent of bench, a bench of an experiment’s setup.

Parameters:

bench (Any)

Return type:

plesty.server.services.NodeLink

serve(name: str, entry: collections.abc.Mapping[str, Any]) → tuple[plesty.server.model.ProcessStatus, bool]

Have name running on this bench, declared from a setup.yaml entry.

An entry naming no port leaves this bench to pick one (declare()). The experiment’s setup holds its devices’ settings, so a device this bench already declares is declared again from the entry when the two differ — keeping the port this bench gave it, unless the entry names one — and a server running with the old settings is restarted. A name declared for another package is refused rather than started under a name the experiment means differently.

Parameters:
  • name (str) – The device, as the experiment’s setup names it.

  • entry (collections.abc.Mapping[str, Any]) – Its entry, as _served_entry() makes it.

Returns:

The server’s status, and whether this call started it.

Raises:

BenchError – When the name is declared for another package, or the server did not come up.

Return type:

tuple[plesty.server.model.ProcessStatus, bool]

_serve_on(bench: Any, device: str, entry: collections.abc.Mapping[str, Any]) → tuple[int, bool]

Have the bench bench of a setup serve device; its port, and whether it started it.

Raises:

BenchError – When the bench did not answer, refused, or said no port.

Parameters:
  • bench (Any)

  • device (str)

  • entry (collections.abc.Mapping[str, Any])

Return type:

tuple[int, bool]

check_shared_disk(name: str, devices: collections.abc.Sequence[str] | None = None) → list[str]

Prove every bench of name’s setup sees the one shared disk this bench sees.

Each bench says where it sees the disk (its data) and devices write there by paths relative to it, so a bench pointed at another folder writes data no one reads. This bench writes a probe under its own data; every other bench, through its agent, reads it under its own and writes a reply, which this bench reads back — so each bench is shown to see the same folder, and to be able to write to it. The files are removed afterwards.

Parameters:
  • name (str) – The declared experiment.

  • devices (collections.abc.Sequence[str] | None) – Only the benches serving these devices — the ones the run’s configuration uses; None for every bench.

Returns:

The benches checked.

Raises:

BenchError – When a bench does not see the disk this bench does, or could not be asked.

Return type:

list[str]

next_run_id(name: str, when: Any = None) → str

Propose a name for the next run of name: <experiment>-<date>-<idx>.

The date makes a run findable by when it was measured and the index orders the runs within a day, which is how an operator refers to them out loud. It is only a proposal — whoever creates the run can call it whatever they like.

Parameters:
  • name (str) – The declared experiment.

  • when (Any) – The day to number within; today by default.

Returns:

A run id no run of this experiment is using.

Return type:

str

_taken_run_names(name: str) → set[str]

The run names name cannot give a run: the ones on disk, and the ones runs have had.

A deleted run leaves its job and its log behind, and handing its name to another run makes that log look like the new run’s — which is exactly how a fresh run comes to show a finished one’s output.

Parameters:

name (str)

Return type:

set[str]

rename_experiment_run(name: str, run_id: str, new_name: str) → dict[str, Any]

Give a run of name that has not started another name (#27).

A typo, or a name chosen before the sample was settled, would otherwise cost deleting the run and configuring it again.

Parameters:
  • name (str) – The declared experiment.

  • run_id (str) – The run’s name now.

  • new_name (str) – What to call it.

Returns:

A row for the run table, under the new name.

Raises:

BenchError – If there is no such run, it has started, or the new name is unusable or taken.

Return type:

dict[str, Any]

create_experiment_run(name: str, run_id: str | None = None, config: str | None = None, copy_of: str | None = None, text: str | None = None, operator: str = '', note: str = '') → dict[str, Any]

Name a run of name and record what it will be run with, without starting it.

Creating and starting are separate on purpose: a measurement takes hours and touches the rig, so there is value in seeing the name, the configuration and the plan sitting in the table before anything moves. The run gets its own copy of its configuration (RUN_CONFIG_FILE), so configuring it later changes it alone.

Parameters:
  • name (str) – The declared experiment.

  • run_id (str | None) – What to call it; the proposal of next_run_id() by default.

  • config (str | None) – Configuration file for this run, relative to the checkout; the declared one by default.

  • copy_of (str | None) – The run this one repeats, when it is a duplicate. Recorded so the table can keep the copy beside its original instead of sorting it to the top, where the two are read apart.

  • text (str | None) – The run’s configuration; the text of config by default.

  • operator (str) – Who measures it (#40), kept with the run and proposed for the next one on this bench.

  • note (str) – Why — the run’s first note, in its notes.txt.

Returns:

A row for the run table.

Raises:

BenchError – If the name is unusable or already taken.

Return type:

dict[str, Any]

last_operator() → str

Who created the last run on this bench; "" before the first (#40).

Return type:

str

run_notes(name: str, run_id: str) → list[dict[str, str]]

The notes of one run, oldest first; none when it has none (#40).

Parameters:
  • name (str)

  • run_id (str)

Return type:

list[dict[str, str]]

add_run_note(name: str, run_id: str, text: str, by: str) → list[dict[str, str]]

Add a note to a run, open or ended; returns its notes, oldest first (#40).

Raises:

BenchError – When there is no such run, or the note is empty.

Parameters:
  • name (str)

  • run_id (str)

  • text (str)

  • by (str)

Return type:

list[dict[str, str]]

duplicate_experiment_run(name: str, run_id: str, into: str | None = None) → dict

Create a run that repeats run_id — same experiment, same configuration.

Repeating a measurement is the ordinary thing to want: the same dot again tomorrow, the same sweep after realigning. The configuration is taken from the run itself — its own copy, or what it froze into its plan — not from the module’s file, which may have been edited since.

Parameters:
  • name (str) – The declared experiment.

  • run_id (str) – The run to repeat.

  • into (str | None) – What to call the new run; the next proposal by default.

Returns:

A row for the run table.

Raises:

BenchError – If the run is not there, or the new name is taken.

Return type:

dict

delete_experiment_run(name: str, run_id: str) → str

Delete one run of name — its directory and everything in it.

This throws away a measurement, so it refuses a run that is still going: what is on disk then is half a run whose process is still writing to it.

Parameters:
  • name (str) – The declared experiment.

  • run_id (str) – The run to delete.

Returns:

The run id deleted.

Raises:

BenchError – If there is no such run, it is still going, or the directory cannot be removed.

Return type:

str

experiment_rows(name: str) → list[dict[str, Any]]

Every run of name, newest first, as the run table shows them.

An experiment that has never run still gets one row. It is declared and ready, which is a state worth seeing — a table that listed only runs would make a newly added experiment invisible until someone started it.

Parameters:

name (str) – The experiment.

Returns:

One {"name", "status"} per run, or a single not-started row.

Return type:

list[dict[str, Any]]

release_lost_run_devices(name: str) → list[str]

Stop the devices a run of name started, when its job died before it could.

The job that runs a module stops the devices the run started once the module ends; killed itself, it stops nothing, and they stayed up with no run to use them. The newest ended run job of name says which they were. A device is stopped only while the server running now is the one that run started — up before the job ended — so one started since, by anyone, is left alone. Once per job: the refresh asks again.

Returns:

The devices stopped.

Parameters:

name (str)

Return type:

list[str]

read_run(run_dir: str | pathlib.Path) → dict[str, Any]

Read a run directory that is not one of this bench’s own.

A run carries everything needed to read it — the plan it was given, the journal of what happened, the records, and the versions that produced it — so a run measured on another bench, or restored from a backup, is openable here without declaring the experiment it came from.

Parameters:

run_dir (str | pathlib.Path) – The directory to read.

Returns:

A row for the run table, its name taken from the run’s own provenance where it records one.

Raises:

ExperimentError – If the directory is not a run.

Return type:

dict[str, Any]

experiment_records(name: str, run_id: str | None = None) → list[dict[str, Any]]

The committed step records of a run of name, oldest of the tail first.

Parameters:
  • name (str)

  • run_id (str | None)

Return type:

list[dict[str, Any]]

experiment_jobs(name: str) → list[plesty.server.model.Job]

Every run job recorded for name, newest first.

Parameters:

name (str)

Return type:

list[plesty.server.model.Job]

running_experiment(name: str) → plesty.server.model.Job | None

The run job of name that is still going, if one is.

Parameters:

name (str)

Return type:

plesty.server.model.Job | None

start_experiment(name: str, resume: str | None = None, start: str | None = None) → plesty.server.model.Job

Start a run of name, or continue resume; returns the job.

The run is a detached job like any other, so closing the console does not stop the measurement. Before it starts, the module’s required environment is checked against what the run would actually get: a missing address is worth refusing here rather than aborting a plan part-way through.

Parameters:
  • name (str) – The declared experiment.

  • resume (str | None) – Run id to continue, or None for a new run.

  • start (str | None) – Run id created beforehand to start under that name, so the run an operator named is the directory the data lands in. Needs a module whose plesty-lib understands --run-id.

Raises:

BenchError – When one is already running, the resumed run is not there, or the environment the module requires is incomplete.

Return type:

plesty.server.model.Job

ask_experiment(name: str, run_id: str | None = None, **changes: Any) → dict[str, Any]

Ask a run of name for something, and return what it now asks for.

Raises:

BenchError – When there is no run to ask.

Parameters:
  • name (str)

  • run_id (str | None)

  • changes (Any)

Return type:

dict[str, Any]

pause_experiment(name: str, run_id: str | None = None) → dict[str, Any]

Ask a run to hold before its next step.

The rig stays connected and the instruments keep their positions — that is the difference from stopping and resuming, which releases the rig and pays a reconnect and a preflight to come back.

Parameters:
  • name (str)

  • run_id (str | None)

Return type:

dict[str, Any]

resume_experiment(name: str, run_id: str | None = None) → dict[str, Any]

Let a held run go on.

Parameters:
  • name (str)

  • run_id (str | None)

Return type:

dict[str, Any]

stop_experiment(name: str, run_id: str | None = None, reason: str = '') → dict[str, Any]

Ask a run to finish the step in flight and then stop.

Nothing is lost and nothing is redone: the step commits and the journal records where to resume from. On a long exposure this takes as long as the exposure — abort_experiment() is the one that does not wait.

Parameters:
  • name (str)

  • run_id (str | None)

  • reason (str)

Return type:

dict[str, Any]

abort_experiment(name: str, run_id: str | None = None, reason: str = '') → dict[str, Any]

Ask a run to stop without finishing the step in flight.

The instruments are released at once; the interrupted step never committed, so resuming runs it again.

Parameters:
  • name (str)

  • run_id (str | None)

  • reason (str)

Return type:

dict[str, Any]

report_experiment(name: str, text: str, run_id: str | None = None) → dict[str, Any]

Record something on a running measurement, where it happened.

Raises:

BenchError – When there is no run, or the text is empty.

Parameters:
  • name (str)

  • text (str)

  • run_id (str | None)

Return type:

dict[str, Any]

kill_experiment(name: str) → bool

Kill the run job of name outright; True when one was running.

The last resort, for a run that has stopped reading its control file. It leaves no run_canceled in the journal, so the resume point is the last step that committed and the one in flight is lost. Killing the job also kills the step that stops the run’s devices when it ends, so they are stopped here instead.

Parameters:

name (str)

Return type:

bool

experiment_site_packages(name: str) → list[pathlib.Path]

Where the environment of name’s module keeps its packages — to load its viewer from.

An experiment is installed in an environment of its own, and its viewer (plesty-lib’s viewer contract, plesty.lib.monitor.load_viz()) imports the module and what it depends on. Those directories, put behind this console’s own on its import path, make the viewer importable here without installing anything: plesty is a namespace package, so the module’s part of it joins the console’s.

Raises:

BenchError – When nothing is installed, or the environment is not of this console’s Python — its compiled packages would not load.

Parameters:

name (str)

Return type:

list[pathlib.Path]

experiment_viewer(name: str, run_id: str | None = None) → plesty.server.model.Job

Open the module’s own viewer on a run of name; returns the job.

The panels are built from the experiment’s dependencies, which this console’s environment does not have — so the viewer runs in the module’s environment, as its own process, rather than inside the console.

Raises:

BenchError – When the module ships no viewer, or there is no run.

Parameters:
  • name (str)

  • run_id (str | None)

Return type:

plesty.server.model.Job

agent_settings() → plesty.server.model.AgentSettings

How this bench’s agent binds and whom it answers (<home>/agent.yaml).

Return type:

plesty.server.model.AgentSettings

save_agent_settings(settings: plesty.server.model.AgentSettings) → plesty.server.model.AgentSettings

Write settings for the next start; returns them.

A running agent is not re-bound underneath its clients — the endpoint and the token are what they connected with. The console says so and the operator restarts it.

A share or bench folder that changed is checked first: the share is mounted and this bench can write in its folder, created when missing. One left as it was is not, so the token can still be changed while the shared disk is away.

Raises:

FleetError – When the settings would not bind, or the new share and folder are not a place this bench can write.

Parameters:

settings (plesty.server.model.AgentSettings)

Return type:

plesty.server.model.AgentSettings

property agent_running: bool

Whether this process is serving the agent right now.

Return type:

bool

agent_state() → str

One line for the status bar: where the agent listens, and whether it checks a token.

Never the token itself — the value belongs on the clipboard and in the client’s environment, not in a window anyone can read over a shoulder.

Return type:

str

start_agent(settings: plesty.server.model.AgentSettings | None = None) → plesty.server.model.AgentSettings

Serve plesty-bench clients from this process; returns the settings used.

The console is the long-running process a lab PC already has open, so the agent is served on a thread of it — the same way fleet run --agent does — rather than by a second process to supervise.

Raises:
  • BenchError – When the agent is already running here.

  • FleetError – When the settings would not bind.

Parameters:

settings (plesty.server.model.AgentSettings | None)

Return type:

plesty.server.model.AgentSettings

stop_agent() → bool

Stop the agent served here; False when none was.

Clients in the middle of a command are not waited for: the socket is closed on the next poll, and an exec that was running finishes against a bench that no longer answers. Stopping is the operator saying the bench is no longer offered.

Return type:

bool

property is_master: bool

Whether this bench drives others (role: master in agent.yaml).

Return type:

bool

peers() → list[plesty.server.model.Peer]

The nodes this bench declares, empty when it is not a master.

Read on demand rather than at open(), so a list that cannot be parsed is a message on a console that opened rather than a console that did not.

Raises:

FleetError – When peers.yaml is malformed.

Return type:

list[plesty.server.model.Peer]

save_peers(peers: list[plesty.server.model.Peer]) → list[plesty.server.model.Peer]

Write the node list and reconnect what changed; returns it.

Raises:

FleetError – When a peer is incomplete or two share a name.

Parameters:

peers (list[plesty.server.model.Peer])

Return type:

list[plesty.server.model.Peer]

property nodes: plesty.server.services.Nodes

The links to the declared nodes, built on first use.

Return type:

plesty.server.services.Nodes

refresh_nodes() → plesty.server.services.Nodes

Make the links match what peers.yaml declares; returns them.

Raises:

FleetError – When peers.yaml is malformed.

Return type:

plesty.server.services.Nodes

The link to the node host, against the list as it is declared now.

Every act on a node goes through here rather than through whatever the last refresh happened to build: a bench added in the dialog is acted on without waiting for the table’s next tick, and one removed there is not acted on at all.

Raises:
Parameters:

host (str)

Return type:

plesty.server.services.NodeLink

node_device_handle(host: str, name: str) → Any

A control handle on a device of the node host.

The agent relays to the device’s own server, so this is what a control panel drives — the same object plesty-bench control builds.

Raises:

NodeError – When no such node is declared.

Parameters:
  • host (str)

  • name (str)

Return type:

Any

catalogue(index_url: str | None = None) → plesty.server.services.Catalogue

The hub catalogue (the fleet’s index_url when it names one).

The instance is kept, and with it the entries it has fetched: one request then answers every filter keystroke. It is rebuilt only when the index it is bound to is no longer the wanted one — which opening another fleet can change. Asking the hub again for the same index is Catalogue.entries() with refresh, not a new instance.

Parameters:

index_url (str | None)

Return type:

plesty.server.services.Catalogue

install_entry(entry: plesty.server.model.CatalogueEntry, version: str | None = None, ref: str | None = None, repository: bool = False) → plesty.server.model.Job

Install a catalogue module onto this bench as a job; nothing is declared.

The catalogue is a market place: it puts modules on the bench, and the fleet then declares device instances of what is on it. Which is why this touches no fleet file and needs none.

Parameters:
  • entry (plesty.server.model.CatalogueEntry) – The catalogue module.

  • version (str | None) – Release to install; None takes the newest release.

  • ref (str | None) – Ref for a repository install; None takes the entry’s latest tag, and no tag means the default branch.

  • repository (bool) – Install the repository instead of a release.

Raises:

BenchError – When the entry is not an installable device.

Return type:

plesty.server.model.Job

module_release(package: str) → str | None

The newest release of package on PyPI, or None — one network call.

What the console asks before it offers a source: a release install needs nothing but uv, a repository install needs git.

Parameters:

package (str)

Return type:

str | None

module_releases(package: str) → tuple[str, Ellipsis]

Every release of package on PyPI, newest first — one network call.

Parameters:

package (str)

Return type:

tuple[str, Ellipsis]

module_tags(url: str) → tuple[str, Ellipsis]

The release tags of the repository at url, newest first — one git ls-remote.

None without git on this bench: git is installed for an install that needs it, not to fill a drop-down.

Parameters:

url (str)

Return type:

tuple[str, Ellipsis]

source_options(package: str, fetch: bool = False) → SourceOptions

Versions, refs and repository URLs the bench knows for package.

Reads the inventory and the fleet; the catalogue only when it has already been fetched — unless fetch is set, which asks the hub, PyPI for every release and the repository for its tags (network calls: callers on a GUI thread make them on a worker).

Parameters:
  • package (str)

  • fetch (bool)

Return type:

SourceOptions

seed_env(name: str, installed: plesty.server.model.InstalledVersion | None = None) → pathlib.Path | None

Bring the device’s .env in line with the module’s .env.example.

On the first install the example is copied verbatim — comments and all — as the .env (an empty or missing file counts as “first”). On a later install of another version or ref, keys the new example has and the .env lacks are appended together with the example’s own comment lines above them; the operator’s values are never touched. A checkout’s own .env.example is used; a PyPI install (no repository on disk) gets the generic example shipped with plesty-server.

Returns:

The written path, or None when nothing had to change.

Parameters:
Return type:

pathlib.Path | None

env_example(name: str, installed: plesty.server.model.InstalledVersion | None = None) → str

The .env.example text that applies to device name.

A checkout’s own file first; for a release install (no repository on disk) the file is fetched from the module’s repository as the catalogue names it — the wheel does not carry it; and the generic example shipped with plesty-server when neither is reachable.

Parameters:
Return type:

str

_remote_env_example(package: str) → str | None

.env.example from the module’s repository (GitLab raw URL), or None.

The repository comes from the package record; the catalogue is asked only when the bench has never learned it.

Parameters:

package (str)

Return type:

str | None

_startable_port(spec: plesty.server.model.DeviceSpec) → plesty.server.model.DeviceSpec

spec with a port nothing else is holding; declares the choice.

A port the bench allocated is not a promise it can keep against another process on the machine — a second bench allocates from the same range, and a server left over from an earlier start holds what it holds. When it is taken the device cannot serve a client there whatever we do, so it moves and the fleet file records where it went.

A port the declaration names — --tcp-port in args, or an env value the device itself reads — is a different thing: that number is passed to the device, an operator chose it, and moving it silently would start the server somewhere its own arguments deny. That clash is reported instead.

A device of an experiment’s setup gets its port for this bench only, in memory: the setup is the experiment’s file, and written back it lost its comments and gained this bench’s ports (#48).

Parameters:

spec (plesty.server.model.DeviceSpec)

Return type:

plesty.server.model.DeviceSpec

_claim_port(spec: plesty.server.model.DeviceSpec) → plesty.server.model.DeviceSpec

Declare spec on its port — in its fleet file, or in memory for a setup.

Parameters:

spec (plesty.server.model.DeviceSpec)

Return type:

plesty.server.model.DeviceSpec

_with_port(spec: plesty.server.model.DeviceSpec) → plesty.server.model.DeviceSpec

Give spec a port of its own unless it asked for one.

Parameters:

spec (plesty.server.model.DeviceSpec)

Return type:

plesty.server.model.DeviceSpec

installed(name: str) → plesty.server.model.InstalledVersion | None

The installed version serving name, or None.

Parameters:

name (str)

Return type:

plesty.server.model.InstalledVersion | None

require_installed(name: str) → plesty.server.model.InstalledVersion

Like installed() but raises BenchError when absent.

Parameters:

name (str)

Return type:

plesty.server.model.InstalledVersion

status(name: str, probe: bool = True) → plesty.server.model.ProcessStatus

The server state of name.

Parameters:
  • name (str)

  • probe (bool)

Return type:

plesty.server.model.ProcessStatus

device(name: str, probe: bool = True) → DeviceView

Declaration, installation and state of name in one view.

Parameters:
  • name (str)

  • probe (bool)

Return type:

DeviceView

devices(probe: bool = True, nodes: bool = False) → list[DeviceView]

Every device the shown fleets declare (view), in fleet order.

Parameters:
  • probe (bool) – Ask each local server for its state rather than reporting the process record alone.

  • nodes (bool) – Append the devices of the shown nodes (node_view) — what the console asks for and the CLI does not, since a command line is aimed at one bench and says which with -b.

Return type:

list[DeviceView]

node_devices(hosts: collections.abc.Collection[str] | None = None) → list[DeviceView]

Every device of every declared node, one round trip per node.

A node that does not answer contributes no rows and a line through say() — its devices are not gone, they are out of reach, and the distinction belongs in the status bar rather than in a table that quietly shortens.

Parameters:

hosts (collections.abc.Collection[str] | None) – Only these nodes, by name; None asks every one. A node left out is not asked at all.

Return type:

list[DeviceView]

log(name: str, lines: int = 50) → str

The tail of the server log of name.

Parameters:
  • name (str)

  • lines (int)

Return type:

str

clear_log(name: str) → int

Empty the server log of name; returns the bytes discarded.

A device server’s log is append-only for the life of the bench, so a module worked on for a week is read through a month of older runs. This is what gives an operator a clean slate before reproducing something.

The Traffic tab is read back out of this same log, so clearing it clears the traffic history with it — they are one file, and a caller that only wanted one of them would be surprised by the other.

Parameters:

name (str) – The device whose log to clear.

Returns:

How many bytes were dropped.

Return type:

int

traffic(name: str, lines: int = TRAFFIC_LINES) → list[plesty.server.model.TrafficRecord]

What the server of name served, newest last.

Read from the same log tail the Log tab shows, so it costs one file read and nothing on the wire. Records are sparse in it — one per request among the server’s ordinary lines — so this reads a longer tail than the log view does to fill a useful pane.

An empty list means one of two things the caller cannot tell apart here: a server that has served nothing, or one on a plesty-lib too old to write records at all. The view says so.

Parameters:
  • name (str)

  • lines (int)

Return type:

list[plesty.server.model.TrafficRecord]

reports(name: str) → list[plesty.server.model.FieldTestReport]

Published field-test reports of name (git installs only).

Parameters:

name (str)

Return type:

list[plesty.server.model.FieldTestReport]

install_now(name: str, force: bool = False, job_id: str | None = None) → plesty.server.model.InstalledVersion

Install what name declares, in the foreground; returns the record.

Parameters:
  • name (str)

  • force (bool)

  • job_id (str | None)

Return type:

plesty.server.model.InstalledVersion

update_now(name: str, ref: str | None = None, job_id: str | None = None) → plesty.server.model.InstalledVersion

Update the environment of name in the foreground.

A git checkout moves to ref (default: its recorded ref) and re-syncs; a PyPI install is reinstalled. The declaration follows a new ref.

Parameters:
  • name (str)

  • ref (str | None)

  • job_id (str | None)

Return type:

plesty.server.model.InstalledVersion

install(name: str, force: bool = False) → plesty.server.model.Job

Install what the device name declares, as a job; returns it at once.

Parameters:
  • name (str)

  • force (bool)

Return type:

plesty.server.model.Job

install_module(package: str, version: str | None = None, git: str | None = None, ref: str | None = None, force: bool = False) → plesty.server.model.Job

Install a module onto the bench as a job — no device, no fleet.

The environment is keyed by the package, never by a device name, so installing one is a market-place act: it stocks the bench, and a declaration later picks from what is in stock.

Parameters:
  • package (str) – Distribution name (plesty-pm100d).

  • version (str | None) – Release to install; None takes the newest.

  • git (str | None) – Repository to install from instead of a release.

  • ref (str | None) – Branch, tag or commit for git.

  • force (bool) – Reinstall even when it is already on the bench.

Return type:

plesty.server.model.Job

welcome_due() → bool

Whether the welcome opens by itself: the first start of a bench that holds nothing.

Once only — welcome_seen() records it — and never on a bench that already has a module, a device or an experiment: that bench was set up.

Return type:

bool

welcome_seen() → None

Record that the welcome was offered, so it does not open by itself again.

Return type:

None

install_demo() → plesty.server.model.Job

Install the demo experiment from its repository, as a job (DEMO).

Only the experiment: its setup.yaml names the demo devices — the stage in its simulator and the spectrometer replaying measured spectra — and the run installs and starts them itself, on this bench, as it does for any experiment.

Return type:

plesty.server.model.Job

set_up_demo(operator: str) → dict[str, Any]

Declare the demo experiment, unless it is, and create its first run.

Returns:

The run’s row, as create_experiment_run() gives it.

Parameters:

operator (str)

Return type:

dict[str, Any]

local_module(folder: str | os.PathLike[str]) → plesty.server.services.LocalModule

What the repository at folder is, read from its pyproject.toml.

Raises:

BenchError – When it is not a device or experiment module.

Parameters:

folder (str | os.PathLike[str])

Return type:

plesty.server.services.LocalModule

local_branches(folder: str | os.PathLike[str]) → tuple[list[str], str]

The branches of the repository at folder, and the one it is on.

Parameters:

folder (str | os.PathLike[str])

Return type:

tuple[list[str], str]

load_module(folder: str | os.PathLike[str], ref: str | None = None) → plesty.server.model.Job

Load the repository at folder onto the bench, where it lives, as a job.

What it is is read first, so a folder that is not a module is refused here rather than in a job log. The job checks out ref when it names another branch, makes the folder’s .venv only when there is none, then records it (load_module_now()).

Parameters:
  • folder (str | os.PathLike[str])

  • ref (str | None)

Return type:

plesty.server.model.Job

load_module_now(folder: str | os.PathLike[str], ref: str | None = None) → plesty.server.model.InstalledVersion

Load the repository at folder in the foreground; returns the record.

See plesty.server.services.Installer.load_local(): the folder stays where it is, and <home>/packages/<package>/ records its path, what the module is, its branch, version and commit.

Parameters:
  • folder (str | os.PathLike[str])

  • ref (str | None)

Return type:

plesty.server.model.InstalledVersion

loaded_modules() → list[plesty.server.model.InstalledVersion]

Every local repository loaded onto this bench, newest first.

Return type:

list[plesty.server.model.InstalledVersion]

installed_experiments(loaded: bool = False) → tuple[str, Ellipsis]

The packages installed from PyPI or git that may be experiments.

A package the module itself calls a device ([tool.plesty] module_type), or one this bench declares as a device, is not: a run installs its devices here too, and they are not experiments to create a run of. One whose kind is unknown is offered — the module says what it is only once it is asked.

Parameters:

loaded (bool) – Also offer a package that is only loaded from a folder on this machine. Create run offers those as themselves; Add experiment… has no other list to find them in.

Return type:

tuple[str, Ellipsis]

installed_devices() → tuple[str, Ellipsis]

The installed packages that may be devices: what Add device… offers.

The mirror of installed_experiments(). A package the module itself calls an experiment, or one this bench declares as an experiment, is not offered; one whose kind is unknown is.

Return type:

tuple[str, Ellipsis]

install_module_now(package: str, version: str | None = None, git: str | None = None, ref: str | None = None, force: bool = False, job_id: str | None = None) → plesty.server.model.InstalledVersion

Install a module in the foreground; returns the record.

Parameters:
  • package (str)

  • version (str | None)

  • git (str | None)

  • ref (str | None)

  • force (bool)

  • job_id (str | None)

Return type:

plesty.server.model.InstalledVersion

learn_repository(package: str) → str

Make sure the bench knows where package lives; returns the URL or "".

The release→tag fallback can only reach a repository the package record names, and a bench that has never seen the module names none — which is exactly the first install of a young module, the case the fallback exists for. The hub is what knows: one lookup, best effort, remembered so it is not asked twice. A bench that cannot reach it simply keeps the release failure it would have had anyway.

Parameters:

package (str)

Return type:

str

update(name: str, ref: str | None = None) → plesty.server.model.Job

Update name as a job; returns it at once.

Parameters:
  • name (str)

  • ref (str | None)

Return type:

plesty.server.model.Job

uninstall(name: str) → bool

Stop name if it runs and remove its environment; True when one was removed.

Parameters:

name (str)

Return type:

bool

installed_modules() → list[InstalledModule]

Every installed version of every module, by name, with what declares it.

A module installed from the Hub and never declared is listed too: it is on the bench, and this is where it is seen and removed.

Return type:

list[InstalledModule]

uninstall_module(package: str, slot: str) → bool

Remove one installed version of package; True when an environment was removed.

A loaded folder is only forgotten: it is the developer’s repository, not the bench’s.

Raises:

BenchError – When it is not installed, or a declaration uses it — removing it would leave that declaration with nothing to run.

Parameters:
  • package (str)

  • slot (str)

Return type:

bool

start(name: str, wait: bool = True, timeout: float | None = None) → plesty.server.model.ProcessStatus

Start the server of name (installing first when nothing serves it).

Raises:

BenchError – When it is not installed and cannot be, or the launch fails.

Parameters:
  • name (str)

  • wait (bool)

  • timeout (float | None)

Return type:

plesty.server.model.ProcessStatus

stop(name: str, timeout: float | None = None) → bool

Stop the server of name; True when a process was stopped.

A stop that killed the server before its shutdown ran is reported through say: the device was never closed and may still hold the instrument, which is what refuses the next start. Nothing observable survives the stop — the record and the exit code are both cleared — so this is the only moment it can be said.

Parameters:
  • name (str)

  • timeout (float | None)

Return type:

bool

restart(name: str, wait: bool = True) → plesty.server.model.ProcessStatus

Stop (if running) and start the server of name.

Parameters:
  • name (str)

  • wait (bool)

Return type:

plesty.server.model.ProcessStatus

node_status(host: str, name: str) → plesty.server.model.ProcessStatus

The state of name on the node host.

Raises:

NodeError – When the node did not answer, or knows no such device.

Parameters:
  • host (str)

  • name (str)

Return type:

plesty.server.model.ProcessStatus

node_start(host: str, name: str) → plesty.server.model.ProcessStatus

Start name on the node host; returns its state afterwards.

Raises:

NodeError – When the node did not answer or refused.

Parameters:
  • host (str)

  • name (str)

Return type:

plesty.server.model.ProcessStatus

node_stop(host: str, name: str) → plesty.server.model.ProcessStatus

Stop name on the node host; returns its state afterwards.

Raises:

NodeError – When the node did not answer or refused.

Parameters:
  • host (str)

  • name (str)

Return type:

plesty.server.model.ProcessStatus

node_restart(host: str, name: str) → plesty.server.model.ProcessStatus

Restart name on the node host; returns its state afterwards.

Raises:

NodeError – When the node did not answer or refused.

Parameters:
  • host (str)

  • name (str)

Return type:

plesty.server.model.ProcessStatus

node_install(host: str, name: str, force: bool = False) → str

Install name on the node host; returns the job id it started.

Started and left to run: the install happens in the node’s own home and is followed there. This console shows no Jobs tab for a device it does not host, so what comes back is the identifier to ask about, and the state the next refresh reports.

Raises:

NodeError – When the node did not answer or refused.

Parameters:
  • host (str)

  • name (str)

  • force (bool)

Return type:

str

node_update(host: str, name: str, ref: str | None = None) → str

Update name on the node host; returns the job id it started.

Raises:

NodeError – When the node did not answer or refused.

Parameters:
  • host (str)

  • name (str)

  • ref (str | None)

Return type:

str

fleet_on_nodes(verb: str) → None

Run fleet up or fleet down on every declared node.

A master brings a rig up, and the rig is every bench the console shows — bringing up only the devices that happen to be on this machine is half a rig. Each node is asked for its own working fleet rather than told which devices to start: what is in it is that bench’s declaration, and the checkbox that sets it is on that bench’s own table.

A node that refuses costs a line and not the others: one bench being unreachable must not leave the rest of the rig down.

Parameters:

verb (str) – up or down.

Return type:

None

up(names: collections.abc.Sequence[str] | None = None, install: bool = True, wait: bool = True, nodes: bool = False) → list[plesty.server.model.ProcessStatus]

Install (unless install is off) and start the autostart devices, or names.

A device that is already running is left alone. Returns one status per device, in fleet order.

Parameters:
  • names (collections.abc.Sequence[str] | None)

  • install (bool)

  • wait (bool)

  • nodes (bool)

Return type:

list[plesty.server.model.ProcessStatus]

down(names: collections.abc.Sequence[str] | None = None, nodes: bool = False) → list[str]

Stop everything running (or names); returns the names stopped.

fleet up starts the working fleet, so down starts there — but a device declared autostart: false and started by hand is running all the same, and stopping only the working fleet left it up and called the bench down. Anything with a launch record is included.

Parameters:
  • names (collections.abc.Sequence[str] | None)

  • nodes (bool)

Return type:

list[str]

stoppable() → list[str]

Every name down() would reach, running or not.

Names that only have a launch record are included: stopping needs the record, not the declaration.

Return type:

list[str]

running() → list[str]

The names of the device servers alive right now, in fleet order.

What down() would actually stop — the working fleet and anything started by hand — so a caller can name them before stopping them.

Return type:

list[str]

orphans() → list[str]

Servers alive on this bench that none of its fleets declares.

A fleet opened in place of the one that started them — a stale default after a crash — drops them out of the table while they keep their ports and the instruments’ sessions. Named, they can be stopped (#24).

Return type:

list[str]

run(stop: threading.Event, names: collections.abc.Sequence[str] | None = None, interval: float = 5.0, max_sweeps: int | None = None, leave_running: bool = False, max_backoff: float = 60.0) → int

Bring the fleet up and keep it up until stop is set (headless bench mode).

Every interval seconds each device is inspected; one found exited is started again, after a backoff that doubles per consecutive crash (capped at max_backoff) and resets once it has answered a probe. Devices stopped on purpose (no record) are left alone: the loop keeps up what it started, it does not fight the operator.

Parameters:
  • stop (threading.Event) – Set to end the loop.

  • names (collections.abc.Sequence[str] | None) – Devices to keep up; default: the autostart ones.

  • interval (float) – Seconds between sweeps.

  • max_sweeps (int | None) – Stop after this many sweeps (tests).

  • leave_running (bool) – Do not bring the servers down when the loop ends.

  • max_backoff (float) – Longest wait before a restart.

Returns:

The number of restarts performed.

Return type:

int

field_test_now(name: str, run: plesty.server.model.FieldTestRun, say: Say | None = None) → int

Run the field test of name in the foreground; returns the script’s exit code.

Host tier: the server is stopped for the run and relaunched afterwards exactly as it was (unless run.keep_server). Client tier: the server is started when it is not up, and the address defaults to it.

Raises:

BenchError – When name is not a git install with that tier’s script.

Parameters:
Return type:

int

field_test_options(name: str, tier: str = 'host') → dict[str, list[str]]

What the device’s field-test script accepts, from its --help.

Returns:

{"options": [...], "gates": [...]} — the long options and the gate choices (the standard gates when the script does not list them); both empty when the script cannot be asked.

Parameters:
  • name (str)

  • tier (str)

Return type:

dict[str, list[str]]

control_availability(name: str) → str | None

Why the controls of name cannot be opened, or None when they can.

Parameters:

name (str) – The instance name.

Returns:

The reason, or None.

Return type:

str | None

Return a handle on the running server of name.

The link is not connected: it opens on its first call, on whichever thread makes it, which is how a panel keeps its socket to itself.

Parameters:

name (str) – The instance name.

Returns:

The handle a DevicePanel drives.

Raises:

BenchError – When the device is not in a state to be controlled; the message is control_availability()’s reason.

Return type:

plesty.server.services.DeviceLink

field_test_availability(name: str, tier: str = 'host') → str | None

Why the field test of name cannot run, or None when it can.

The reason is what a tooltip shows: not installed, a release install (no repository on disk), or a checkout without the tier’s script.

Parameters:
  • name (str)

  • tier (str)

Return type:

str | None

field_test(name: str, run: plesty.server.model.FieldTestRun) → plesty.server.model.Job

Run the field test of name as a job; returns it at once.

Parameters:
Return type:

plesty.server.model.Job

_field_test_command(run: plesty.server.model.FieldTestRun, spec: plesty.server.model.DeviceSpec, python: pathlib.Path, checkout: pathlib.Path) → list[str]
Parameters:
Return type:

list[str]

static _script_help(python: pathlib.Path, script: pathlib.Path) → str

The --help text of a field-test script ("" when it cannot be asked).

Parameters:
  • python (pathlib.Path)

  • script (pathlib.Path)

Return type:

str

classmethod _script_options(python: pathlib.Path, script: pathlib.Path) → set[str] | None

The options a field-test script accepts, read from its --help.

Scaffolds differ between SDK releases (--address now, --port/ --mock before); asking the script is cheaper than guessing wrong. None when it cannot be asked (the current scaffold is assumed).

Parameters:
  • python (pathlib.Path)

  • script (pathlib.Path)

Return type:

set[str] | None

job(job_id: str) → plesty.server.model.Job | None

The job job_id, up to date, or None.

Parameters:

job_id (str)

Return type:

plesty.server.model.Job | None

job_list(name: str | None = None, kind: str | None = None) → list[plesty.server.model.Job]

Jobs on record, newest first.

Parameters:
  • name (str | None)

  • kind (str | None)

Return type:

list[plesty.server.model.Job]

job_log(job_id: str, offset: int = 0) → tuple[str, int]

Job log from byte offset; returns text and the next offset.

Parameters:
  • job_id (str)

  • offset (int)

Return type:

tuple[str, int]

job_log_end(job_id: str) → tuple[str, int, int]

The end of a job’s log: text, the byte it starts at, the byte it ends at.

Parameters:

job_id (str)

Return type:

tuple[str, int, int]

job_log_before(job_id: str, end: int) → tuple[str, int]

The part of a job’s log before byte end: text and the byte it starts at.

Parameters:
  • job_id (str)

  • end (int)

Return type:

tuple[str, int]

run_kept_log(name: str, run_id: str) → pathlib.Path | None

The log run run_id of name keeps in its own directory, if any (#33).

What the console shows when this bench has no job for the run: one measured before an uninstall emptied the home, or on another bench.

Parameters:
  • name (str)

  • run_id (str)

Return type:

pathlib.Path | None

log_from(path: str | pathlib.Path, offset: int = 0) → tuple[str, int]

The log at path from byte offset: text and the next offset.

Parameters:
  • path (str | pathlib.Path)

  • offset (int)

Return type:

tuple[str, int]

log_end(path: str | pathlib.Path) → tuple[str, int, int]

The end of the log at path: text, the byte it starts at, the byte it ends at.

Parameters:

path (str | pathlib.Path)

Return type:

tuple[str, int, int]

log_before(path: str | pathlib.Path, end: int) → tuple[str, int]

The part of the log at path before byte end: text and the byte it starts at.

Parameters:
  • path (str | pathlib.Path)

  • end (int)

Return type:

tuple[str, int]

cancel(job_id: str) → bool

Cancel a running job.

Parameters:

job_id (str)

Return type:

bool

_job(name: str, kind: str, argv: list[str], detail: dict[str, object] | None = None, fleet_needed: bool = True) → plesty.server.model.Job

Start plesty-server _job <argv> against this bench as a job.

Raises:

BenchError – When the job reads declarations this bench has not written anywhere — a job is another process, and it opens the fleet file.

Parameters:
  • name (str)

  • kind (str)

  • argv (list[str])

  • detail (dict[str, object] | None)

  • fleet_needed (bool)

Return type:

plesty.server.model.Job

_job_command(*argv: str) → list[str]

plesty-server _job <argv> on this bench: this program, this home and fleet file.

Parameters:

argv (str)

Return type:

list[str]

plesty.server.presenter.bench._example_blocks(template: str) → dict[str, str]

Each KEY= line of an example with the comment lines directly above it, keyed by KEY.

Parameters:

template (str)

Return type:

dict[str, str]

plesty.server.presenter.bench._moment(stamp: str | None) → datetime.datetime | None

An ISO timestamp as an aware datetime; None when absent or unreadable.

Parameters:

stamp (str | None)

Return type:

datetime.datetime | None

plesty.server.presenter.bench.release_tag(ref: str) → bool

Whether ref names a release (v0.3.1) rather than a branch.

Parameters:

ref (str) – A git ref as the inventory or the catalogue reports it.

Return type:

bool

plesty.server.presenter.bench.module_spec(package: str, version: str | None = None, git: str | None = None, ref: str | None = None, path: str | None = None) → plesty.server.model.DeviceSpec

A declaration standing in for a bare module install.

The installer works from a DeviceSpec because a device install is the common case; an environment is keyed by (package, version), (package, ref) or a local folder either way, so the name is only along for the ride and the package’s own is the honest one.

Parameters:
  • package (str)

  • version (str | None)

  • git (str | None)

  • ref (str | None)

  • path (str | None)

Return type:

plesty.server.model.DeviceSpec

plesty.server.presenter.bench.FIELD_TEST_ELSEWHERE = 'it runs on {host} — field-test it from that bench'
plesty.server.presenter.bench._node_view(host: str, entry: dict[str, Any]) → DeviceView | None

Rebuild one device of the node host from its list --json entry.

The node answers in the same document the local list --json prints, so a row is the same DeviceView the local devices make — carrying which bench it came from, and nothing else different.

Returns:

The view, or None when the entry is not one this version reads — a node on an older or newer plesty-server costs its row and not the table.

Parameters:
  • host (str)

  • entry (dict[str, Any])

Return type:

DeviceView | None

plesty.server.presenter.bench._node_status(name: str, raw: dict[str, Any]) → plesty.server.model.ProcessStatus

A status from a node’s JSON, keeping only the fields this version knows.

Parameters:
  • name (str)

  • raw (dict[str, Any])

Return type:

plesty.server.model.ProcessStatus

plesty.server.presenter.bench._stderr(line: str) → None
Parameters:

line (str)

Return type:

None

plesty.server.presenter.bench.fleet_name(path: pathlib.Path) → str

What a fleet is called: its file’s name without .yaml (fleets/default.yaml).

Parameters:

path (pathlib.Path)

Return type:

str

plesty.server.presenter.bench._same_file(one: pathlib.Path, other: pathlib.Path) → bool

Whether one and other name the same file, however each is spelled.

Parameters:
  • one (pathlib.Path)

  • other (pathlib.Path)

Return type:

bool

plesty.server.presenter.bench._now() → str
Return type:

str