plesty.server.model

Model layer: fleet configuration, device catalogue, installer and supervisor state.

Pure Python — no Qt imports. Everything the presenter needs to know about the bench lives here and is testable without a display.

Submodules

Attributes

DATA_PATH_VAR

DATA_ROOT_VAR

DATA_SHARE_VAR

DEFAULT_DATA_DIR

DEFAULT_WORKERS

ROLE_MASTER

ROLE_NODE

ROLES

DEFAULT_INDEX_URL

INSTALLABLE_CATEGORIES

REQUIRED_STANDARD

DEFAULT_CONFIG

SOURCE_GIT

SOURCE_LOCAL

SOURCE_PYPI

ENV_FILENAME

JOB_KIND

REPORT_BASENAME

STANDARD_GATES

TIER_ARTIFACTS

TIER_SCRIPTS

FLEET_FILENAME

FLEET_VERSION

HOME_ENV

CANCELLED_EXIT

LOG_CHUNK

LOST_EXIT

PEERS_FILENAME

PEERS_VERSION

AGENT_PORT

DEFAULT_TCP_PORT

DEVICE_PORT_BASE

EXITED

RUNNING

STARTING

STATES

STOPPED

MARKER

Exceptions

ManifestError

Raised when a module's manifest cannot be read or makes no sense.

DeviceManagerError

An installed-version record is missing or inconsistent.

FleetError

A fleet file or device declaration is malformed.

FieldTestError

A field test cannot be set up as asked.

JobError

A job record does not exist or cannot be read.

Classes

AgentSettings

How the bench agent binds, whom it answers, and whether it drives others.

CatalogueEntry

One module of the hub catalogue.

DeviceSpec

One device server as the fleet declares it.

ExperimentManifest

What a module answered when asked what it takes.

ExperimentSpec

One experiment as the fleet declares it.

Parameter

One configuration parameter the module declares.

DeviceManager

The installed-module inventory under a Home.

InstalledPackage

All installed versions of one device module.

InstalledVersion

One version of one device module present on the bench.

PackageInfo

What the bench knows about a module as a project — independent of any install.

FieldTestReport

What a field test published — the summary a bench needs, not the whole document.

FieldTestRun

One field-test request.

GateResult

One gate of a published report.

Fleet

A parsed fleet file.

Home

Layout of the plesty-server home directory.

Job

One unit of long-running work.

JobStore

Job records and logs under a Home.

Peer

One bench this master drives.

ProcessRecord

What the supervisor persists about a started server.

ProcessStatus

The observed state of one device server.

TrafficRecord

One served request, as the server reported it.

Functions

check_data_location(→ tuple[str, str])

Return share and folder, having made sure the bench can write in the folder.

load_agent_settings(→ AgentSettings)

The agent settings of home, defaults when it has none.

save_agent_settings(→ pathlib.Path)

Write settings to home; returns the path.

settings_file(→ pathlib.Path)

Where home keeps the agent settings.

parse_catalogue(→ list[CatalogueEntry])

Every entry of a /api/modules/ document (a mapping with modules or a bare list).

check_name(→ None)

Raise FleetError unless name is usable as a file name on any bench.

module_for(→ str)

The import path served by python -m for a PLESTY distribution.

parse_env(→ dict[str, str])

Parse dotenv text into a mapping (later keys win).

read_env(→ dict[str, str])

Parse the file at path; empty when it does not exist.

render_env(→ str)

Render a mapping as dotenv text (one KEY=VALUE per line, quoted when needed).

field_test_script(→ pathlib.Path | None)

The tier's script inside checkout, or None when the module has none.

find_reports(→ list[FieldTestReport])

Every published field-test report under <checkout>/reports/, newest first.

gates_in_help(→ tuple[str, Ellipsis])

The gate names a script's --help offers for --gates, in its order; empty if none.

options_in_help(→ set[str])

The long options an argparse --help text mentions (--address, --mock …).

find_fleet(→ pathlib.Path | None)

Locate the fleet file a command should consult.

load_fleet(→ Fleet)

Read and validate a fleet file.

save_fleet(→ pathlib.Path)

Write a fleet file.

default_home(→ pathlib.Path)

The home directory the environment or the packaged config designates.

tail_file(→ str)

The last lines lines of path, read from at most its last limit bytes.

load_peers(→ list[Peer])

The nodes home declares, empty when it declares none.

parse_peers(→ list[Peer])

Every peer of a peers.yaml document, in the order it declares them.

peers_file(→ pathlib.Path)

Where home keeps the node list.

save_peers(→ pathlib.Path)

Write peers to home; returns the path.

allocate_port(→ int)

The lowest free port from base up, skipping taken and the agent's.

declares_port(→ bool)

Whether args or env name the port themselves.

port_from(→ int)

Resolve the TCP port a server binds from its args and environment.

port_is_free(→ bool)

Whether port can actually be bound right now.

port_listening(→ bool)

Whether something accepts TCP connections on port right now.

parse_traffic(→ list[TrafficRecord])

Every traffic record in text, oldest first.

Package Contents

plesty.server.model.DATA_PATH_VAR = 'DEVICE_DATA_PATH'
plesty.server.model.DATA_ROOT_VAR = 'DEVICE_DATA_ROOT'
plesty.server.model.DATA_SHARE_VAR = 'PLESTY_DATA'
plesty.server.model.DEFAULT_DATA_DIR = 'tmp'
plesty.server.model.DEFAULT_WORKERS = 4
plesty.server.model.ROLE_MASTER = 'master'
plesty.server.model.ROLE_NODE = 'node'
plesty.server.model.ROLES
class plesty.server.model.AgentSettings

How the bench agent binds, whom it answers, and whether it drives others.

Variables:
  • bind – ZMQ endpoint to bind.

  • token – Shared secret every exec/fetch must carry. Empty is legal and means no check — a bench on a network already trusted.

  • workers – Requests served concurrently.

  • role – master to drive other benches, node to serve only this one. One master to a group and no chaining, so the benches of a lab form a star: see is_master.

  • data_share – The Plesty share — the lab’s shared disk — as this machine mounts it (Z:/xyz/plesty, /Volumes/plesty). Empty until set.

  • data_folder – This bench’s folder in the share, relative to it (platform-a, any depth); empty for the share itself.

  • check_updates – Whether the console asks PyPI for a newer bench when it starts, at most once a day (#39).

bind: str = 'tcp://*:5550'
token: str = ''
workers: int = 4
role: str = 'node'
data_share: str = ''
data_folder: str = ''
check_updates: bool = True
property data_root: str

its folder in the share, where every device it starts writes.

Empty while no share is set.

Type:

The bench data path

Return type:

str

property guarded: bool

Whether a token is checked (never the token itself).

Return type:

bool

property is_master: bool

Whether this bench declares nodes and drives their devices.

A configuration convention rather than a permission: the agent checks one shared secret and knows no caller identity, so a node’s token drives that node whoever holds it, and nothing here stops a second bench from declaring itself master of the same nodes. What the role decides is what this console does — a node keeps no peer list and offers nothing remote.

Return type:

bool

validate() → AgentSettings

Return self, having refused settings the agent could not bind.

Raises:

FleetError – When the endpoint is empty or the worker count is not a positive number.

Return type:

AgentSettings

to_mapping() → dict[str, Any]

The document written to agent.yaml.

Return type:

dict[str, Any]

classmethod from_mapping(raw: Any) → AgentSettings

Read a document, falling back to the defaults key by key.

A settings file nobody can parse must not stop the console from opening: what is unreadable is replaced by its default rather than raised, since the operator can see and fix every value in the dialog.

Parameters:

raw (Any)

Return type:

AgentSettings

with_new_token() → AgentSettings

These settings with a freshly generated token.

Return type:

AgentSettings

device_data_env(name: str, data_dir: str | None, fallback: pathlib.Path | None = None) → dict[str, str]

Where a device the bench starts may write, and where it writes by default.

Parameters:
  • name (str) – The device, whose own folder under tmp is its default.

  • data_dir (str | None) – Its folder under the bench data path, as its declaration names it; None for tmp/<name>.

  • fallback (pathlib.Path | None) – Where it writes while the bench has no share — a folder of the bench’s own (#43). A device that must have a data path otherwise refused to start on a bench alone.

Returns:

DEVICE_DATA_ROOT, DEVICE_DATA_PATH and PLESTY_DATA — the share, which the device reports its folders relative to. Without a share, the first two under fallback and no PLESTY_DATA; nothing when there is no fallback either.

Return type:

dict[str, str]

run_data_env() → dict[str, str]

PLESTY_DATA for a run the bench starts: where it reads its devices’ files.

Return type:

dict[str, str]

plesty.server.model.check_data_location(share: str, folder: str) → tuple[str, str]

Return share and folder, having made sure the bench can write in the folder.

An empty share is legal and means unset. Otherwise the share is an absolute path to an existing folder — a relative one would follow whatever directory a server happens to start in — and the bench’s folder is a path inside it, created when missing.

Returns:

The share, and the folder as a/b (empty for the share itself).

Raises:

FleetError – Naming why the bench cannot write there.

Parameters:
  • share (str)

  • folder (str)

Return type:

tuple[str, str]

plesty.server.model.load_agent_settings(home: plesty.server.model.home.Home) → AgentSettings

The agent settings of home, defaults when it has none.

Parameters:

home (plesty.server.model.home.Home)

Return type:

AgentSettings

plesty.server.model.save_agent_settings(home: plesty.server.model.home.Home, settings: AgentSettings) → pathlib.Path

Write settings to home; returns the path.

The file holds a shared secret, so it is made readable by its owner alone. Windows ignores the mode — there the bench home’s own ACL is the guard — which is why the token is a bench-network secret and not a credential.

Raises:

FleetError – When the settings would not bind (see AgentSettings.validate()).

Parameters:
Return type:

pathlib.Path

plesty.server.model.settings_file(home: plesty.server.model.home.Home) → pathlib.Path

Where home keeps the agent settings.

Parameters:

home (plesty.server.model.home.Home)

Return type:

pathlib.Path

plesty.server.model.DEFAULT_INDEX_URL = 'https://hub.plesty.net/api/modules/'
plesty.server.model.INSTALLABLE_CATEGORIES = ('devices', 'experiments')
plesty.server.model.REQUIRED_STANDARD = 'quantum'
class plesty.server.model.CatalogueEntry

One module of the hub catalogue.

Variables:
  • name – Distribution name (plesty-pm100d).

  • slug – Project slug.

  • namespace – GitLab path (plesty/hub/devices/thorlab/pm100d).

  • description – One line.

  • category – devices, analyzers or experiments.

  • vendor – Vendor group for devices (thorlab), else empty.

  • repo_url – Clone URL — what a git install uses.

  • web_url – Project page.

  • version – Latest release version, or empty.

  • latest_tag – Latest tag (v0.2.1), or empty.

  • standard – Compliance standard the module pins (quantum), or empty.

  • checked – Whether a plesty check report is published.

  • gates_ok – Whether that report passed.

  • gates_passed – Gates passed in it.

  • gates_failed – Gates failed in it.

  • field_tests – Number of published field-test reports.

  • sdk_version – The plesty-sdk that report was made with, or empty.

name: str
slug: str = ''
namespace: str = ''
description: str = ''
category: str = ''
vendor: str = ''
repo_url: str = ''
web_url: str = ''
version: str = ''
latest_tag: str = ''
standard: str = ''
checked: bool = False
gates_ok: bool = False
gates_passed: int = 0
gates_failed: int = 0
field_tests: int = 0
sdk_version: str = ''
property publishes_manifest: bool

Whether the module is shown to answer --manifest; true of any non-experiment.

Shown by its published check: passed, on a plesty-sdk that has the Experiment Manifest gate (MANIFEST_SDK).

Return type:

bool

property installable: bool

a device or experiment at the standard, with a repository.

The standard is the gate, not the category: what a bench installs it also runs, and a module that has not met the standard has not shown that it can be. A module the hub lists without one is not offered here until its registry entry says it reached it. An experiment must also publish its manifest (publishes_manifest): one that does not installs, and then cannot be configured.

Type:

Whether the bench offers it

Return type:

bool

property ref: str | None

the latest tag, else the default branch.

Type:

The git ref a fresh install checks out

Return type:

str | None

property quality: str

ok, failing, unchecked.

Type:

One word for listings

Return type:

str

matches(query: str) → bool

Case-insensitive substring match on name, slug, vendor and description.

Parameters:

query (str)

Return type:

bool

classmethod from_registry(raw: collections.abc.Mapping[str, Any]) → CatalogueEntry

Build from one element of the registry’s modules list.

Parameters:

raw (collections.abc.Mapping[str, Any])

Return type:

CatalogueEntry

plesty.server.model.parse_catalogue(document: Any) → list[CatalogueEntry]

Every entry of a /api/modules/ document (a mapping with modules or a bare list).

Parameters:

document (Any)

Return type:

list[CatalogueEntry]

class plesty.server.model.DeviceSpec

One device server as the fleet declares it.

Variables:
  • name – Instance name — the venv, log and state key; unique per bench.

  • package – PyPI distribution of the device module.

  • version – Pinned release, or None for the newest one at install time.

  • git – Repository URL to install from instead of PyPI, or None.

  • ref – Branch, tag or commit for a git source; None = v<version> when a version is given, else the default branch.

  • module – Import path served with python -m; derived from package.

  • args – Arguments appended to python -m <module>.

  • env – Environment set for the server process on top of the bench’s own.

  • port – TCP port the server binds; probed for liveness.

  • autostart – Whether fleet up/fleet run start it.

  • host_name – The bench the server runs on, as an experiment’s setup.yaml names it; the run’s <DEVICE>_ADDRESS is built from that bench and the port (plesty.lib.experiment.setup).

  • path – A local repository to run instead of an install — the folder Hub ▸ Load from… loaded. Only a declaration naming it runs it.

  • data_dir – Where the device writes, relative to its bench’s data path (Server ▸ Configure…) — pol-pl for a spectrometer saving frames there. Started by the bench, the device is given that folder as DEVICE_DATA_PATH; None gives it tmp/<name>. Never absolute and never above the data path.

name: str
package: str
version: str | None = None
git: str | None = None
ref: str | None = None
module: str = ''
args: tuple[str, Ellipsis] = ()
env: collections.abc.Mapping[str, str]
port: int = 0
autostart: bool = True
host_name: str | None = None
path: str | None = None
data_dir: str | None = None
__post_init__() → None

Fill the derived fields and validate the declaration.

Return type:

None

property port_is_explicit: bool

Whether this device asked for its port rather than taking the default.

True once the bench has allocated one and written it down, so the port a client connected to yesterday is the port it finds today.

Return type:

bool

property launch_args: tuple[str, Ellipsis]

The arguments python -m <module> is started with.

port is what the supervisor probes; unless the args already name a port, it is also handed to the server as --tcp-port, so what is probed and what is bound cannot drift. An environment value (*_TCP_PORT — often 5555 straight from a module’s .env.example) is deliberately not trusted here: an explicit flag beats the environment in every hub module, and the bench owns the ports.

Return type:

tuple[str, Ellipsis]

property git_ref: str | None

The ref a git install checks out (ref, else v<version>, else None).

Return type:

str | None

property requirement: str

The pip requirement string.

plesty-pm100d==0.2.1 for a PyPI source; plesty-pm100d @ git+<url>@v0.2.1 for a git source.

Return type:

str

classmethod from_mapping(name: str, raw: collections.abc.Mapping[str, Any]) → DeviceSpec

Build a spec from one devices: entry of a fleet file.

Parameters:
  • name (str) – The mapping key.

  • raw (collections.abc.Mapping[str, Any]) – The entry’s fields.

Raises:

FleetError – On unknown keys or wrong value types.

Return type:

DeviceSpec

to_mapping() → dict[str, Any]

The fleet-file form of this spec (derived defaults omitted).

Return type:

dict[str, Any]

plesty.server.model.check_name(name: str) → None

Raise FleetError unless name is usable as a file name on any bench.

The name of a device is not only a label: it names its virtual environment, its log, its run directory and its state file. A name that a Windows bench cannot open (com3) or that reaches out of its directory (../x) would declare fine on a developer machine and fail on the instrument.

Parameters:

name (str) – The key the fleet file gives the device.

Raises:

FleetError – When the name cannot be a file name everywhere.

Return type:

None

plesty.server.model.module_for(package: str) → str

The import path served by python -m for a PLESTY distribution.

Parameters:

package (str) – PyPI distribution name, e.g. plesty-pm100d.

Returns:

plesty.pm100d — the platform’s flat namespace convention.

Return type:

str

plesty.server.model.DEFAULT_CONFIG = 'config/default.yaml'
class plesty.server.model.ExperimentManifest

What a module answered when asked what it takes.

Variables:
  • module – Import path the manifest names.

  • title – Human title of the experiment.

  • config – The configuration file the module uses by default.

  • run_root – Where the module puts runs when nothing overrides it.

  • viz – Import path of its viewer, or None when it ships none.

  • parameters – Every declared configuration parameter, in file order.

  • environment – The variables the run reads, each with whether it is required and what it is for.

  • chooses_devices – Whether the module names the devices of its setup.yaml a configuration uses (--devices); without it every device is served (plesty-lib#62).

module: str
title: str = ''
config: str = 'config/default.yaml'
run_root: str = 'runs'
viz: str | None = None
parameters: tuple[Parameter, Ellipsis] = ()
environment: tuple[tuple[str, str, bool], Ellipsis] = ()
chooses_devices: bool = False
property groups: list[str]

The parameter groups, in the order they were declared.

Return type:

list[str]

in_group(group: str) → list[Parameter]

The parameters of group, in declaration order.

Parameters:

group (str)

Return type:

list[Parameter]

property required_env: list[str]

The environment variables a run cannot do without.

Return type:

list[str]

missing_env(values: collections.abc.Mapping[str, str]) → list[str]

The required variables values does not set, or sets empty.

Parameters:

values (collections.abc.Mapping[str, str]) – The environment the run would get.

Returns:

The missing variable names, in declaration order. A run can be refused on these before anything is started, rather than aborting part-way through a plan.

Return type:

list[str]

classmethod parse(text: str) → ExperimentManifest

Read a manifest from what --manifest printed.

Parameters:

text (str) – The command’s standard output.

Returns:

The manifest.

Raises:

ManifestError – If the output is not the document it should be.

Return type:

ExperimentManifest

class plesty.server.model.ExperimentSpec

One experiment as the fleet declares it.

Variables:
  • name – Instance name — the key its runs and jobs are recorded under; unique per bench.

  • package – PyPI distribution of the experiment module.

  • version – Pinned release, or None for the newest at install time.

  • git – Repository URL to install from; an experiment is normally installed this way, since its configuration is in the checkout.

  • ref – Branch, tag or commit for a git source; None = v<version> when a version is given, else the default branch.

  • path – A local repository to run instead of an install — the folder Hub ▸ Load from… loaded. Only a declaration naming it runs it.

  • module – Import path run with python -m; derived from package.

  • config – Configuration file, relative to the checkout.

  • args – Arguments appended to python -m <module>, beyond the ones the console supplies itself (--config, --run-root, --resume).

  • env – Environment set for the run on top of the bench’s own.

name: str
package: str
version: str | None = None
git: str | None = None
ref: str | None = None
path: str | None = None
module: str = ''
config: str = 'config/default.yaml'
args: tuple[str, Ellipsis] = ()
env: collections.abc.Mapping[str, str]
__post_init__() → None

Fill the derived fields and validate the declaration.

Raises:

FleetError – If the name is unusable as a directory key, or no package is named.

Return type:

None

property git_ref: str | None

The ref a git install checks out, or None for the default branch.

Return type:

str | None

property requirement: str

The pip requirement a release install resolves.

Return type:

str

command(python: str, run_root: str, resume: str | None = None, config: str | None = None) → list[str]

Build the command that runs this experiment.

The console supplies the three arguments it is entitled to decide — which configuration, where the run goes, and whether this continues an earlier one — and the declaration’s own arguments follow, so a module option always wins over nothing and never over the operator.

Parameters:
  • python (str) – Interpreter of the module’s environment.

  • run_root (str) – Directory the run’s checkpoint directory goes in.

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

  • config (str | None) – The configuration file, when the run has its own; the declared one by default. Passed once: a second --config would win only by coming last.

Returns:

The command line.

Return type:

list[str]

manifest_command(python: str) → list[str]

Build the command that asks the module what it takes.

Parameters:

python (str) – Interpreter of the module’s environment.

Returns:

The command line; it connects to nothing and needs no configuration.

Return type:

list[str]

devices_command(python: str, config: str) → list[str]

Build the command that asks which devices a run of config uses.

Parameters:
  • python (str) – Interpreter of the module’s environment.

  • config (str) – The configuration file the run will read.

Returns:

The command line; it connects to nothing.

Return type:

list[str]

classmethod from_mapping(name: str, raw: collections.abc.Mapping[str, Any]) → ExperimentSpec

Build a spec from one experiments: entry.

Parameters:
  • name (str) – The instance name the entry is keyed by.

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

Raises:

FleetError – On unknown keys or a malformed value.

Return type:

ExperimentSpec

to_mapping() → dict[str, Any]

The declaration’s YAML form, derived defaults left out.

Return type:

dict[str, Any]

exception plesty.server.model.ManifestError

Bases: RuntimeError

Raised when a module’s manifest cannot be read or makes no sense.

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

class plesty.server.model.Parameter

One configuration parameter the module declares.

The fields are the device vocabulary — the console renders an experiment’s settings with the same code that renders a device’s panel, so nothing here is new and nothing here may drift from it.

Variables:
  • key – How the configuration addresses it, group.parameter for a grouped one.

  • group – The group it belongs to.

  • name – Its name within the group.

  • dtype – Declared type name; yaml for a value that is a document rather than a scalar.

  • unit – Physical unit, or "" for a count, or None.

  • description – What it means.

  • default – What the module uses when the configuration omits it.

  • minimum – Lower bound, or None.

  • maximum – Upper bound, or None.

  • options – Allowed values, which make it a choice.

key: str
group: str
name: str
dtype: str = 'str'
unit: str | None = None
description: str = ''
default: Any = None
minimum: float | None = None
maximum: float | None = None
options: tuple[Any, Ellipsis] | None = None
property is_document: bool

Whether its value is a document rather than a scalar.

Return type:

bool

plesty.server.model.SOURCE_GIT = 'git'
plesty.server.model.SOURCE_LOCAL = 'local'
plesty.server.model.SOURCE_PYPI = 'pypi'
class plesty.server.model.DeviceManager(home: plesty.server.model.home.Home)

The installed-module inventory under a Home.

Bind to a home.

Parameters:

home (plesty.server.model.home.Home) – The bench home.

home
record(package: str, version: str, source: str = SOURCE_PYPI, git: str | None = None, ref: str | None = None, commit: str | None = None, python: str = '', job_id: str | None = None, path: str | os.PathLike[str] | None = None) → InstalledVersion

Write down that package version is installed; returns the record.

Parameters:
  • package (str) – Distribution name.

  • version (str) – Installed release.

  • source (str) – SOURCE_PYPI or SOURCE_GIT.

  • git (str | None) – Repository URL for a git source.

  • ref (str | None) – Requested ref for a git source.

  • commit (str | None) – Checked-out commit for a git source.

  • python (str) – Interpreter version of the environment.

  • job_id (str | None) – The install job.

  • path (str | os.PathLike[str] | None) – Environment location when not the default package dir.

Return type:

InstalledVersion

save(installed: InstalledVersion) → pathlib.Path

Write installed to its record file; returns the path.

Parameters:

installed (InstalledVersion)

Return type:

pathlib.Path

get(package: str, slot: str) → InstalledVersion | None

The record of package at slot (a version, or git-<ref>), or None.

Parameters:
  • package (str)

  • slot (str)

Return type:

InstalledVersion | None

require(package: str, slot: str) → InstalledVersion

Like get() but raises DeviceManagerError when absent.

Parameters:
  • package (str)

  • slot (str)

Return type:

InstalledVersion

package(package: str) → InstalledPackage

Every installed version of package (empty when none).

Parameters:

package (str)

Return type:

InstalledPackage

list() → list[InstalledPackage]

Every package with at least one installed version, by name.

Return type:

list[InstalledPackage]

info(package: str) → PackageInfo

The package-level record of package (empty fields when nothing is known).

Parameters:

package (str)

Return type:

PackageInfo

remember(package: str, **fields: str) → PackageInfo

Merge non-empty fields into the package-level record; returns it.

Parameters:
  • package (str)

  • fields (str)

Return type:

PackageInfo

env_dir(installed: InstalledVersion) → pathlib.Path

The environment directory of installed (its path, else the package dir).

Parameters:

installed (InstalledVersion)

Return type:

pathlib.Path

resolve(spec: plesty.server.model.device.DeviceSpec) → InstalledVersion | None

The installed version that serves spec, or None when one must be installed.

The most recent install among those that InstalledVersion.satisfies() the spec wins — for an unpinned PyPI spec that is the newest install, for a git branch the last pull.

Parameters:

spec (plesty.server.model.device.DeviceSpec)

Return type:

InstalledVersion | None

forget(package: str, slot: str, remove_env: bool = False) → bool

Drop the record of package at slot; True when it existed.

Parameters:
  • package (str) – Distribution name.

  • slot (str) – Version (release) or git-<ref> (checkout).

  • remove_env (bool) – Also delete a release’s environment directory.

Return type:

bool

exception plesty.server.model.DeviceManagerError

Bases: ValueError

An installed-version record is missing or inconsistent.

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

class plesty.server.model.InstalledPackage

All installed versions of one device module.

Variables:
  • package – Distribution name.

  • versions – Records keyed by slot (version, or git-<ref>), oldest install first.

package: str
versions: collections.abc.Mapping[str, InstalledVersion]
__iter__() → collections.abc.Iterator[InstalledVersion]

Iterate over the versions, oldest install first.

Return type:

collections.abc.Iterator[InstalledVersion]

__len__() → int

Number of installed versions.

Return type:

int

__contains__(version: object) → bool

Whether version (a slot, or a release version) is installed.

Parameters:

version (object)

Return type:

bool

property latest: InstalledVersion | None

The most recently installed version, or None when empty.

Return type:

InstalledVersion | None

class plesty.server.model.InstalledVersion

One version of one device module present on the bench.

Variables:
  • package – PyPI distribution name, e.g. plesty-pm100d.

  • version – The installed release as reported by the environment.

  • source – SOURCE_PYPI, SOURCE_GIT or SOURCE_LOCAL.

  • git – Repository URL for a git source, else None.

  • ref – Branch, tag or commit that was requested for a git source.

  • commit – The commit actually checked out for a git source.

  • installed_utc – ISO timestamp of the install.

  • python – Version of the interpreter in the environment.

  • job_id – The install job that produced it, when known.

  • path – Where the environment lives when it is not the default <home>/packages/<package>/<version>/ — a git checkout, or a local repository, whose .venv is the environment.

package: str
version: str
source: str = 'pypi'
git: str | None = None
ref: str | None = None
commit: str | None = None
installed_utc: str = ''
python: str = ''
job_id: str | None = None
path: str = ''
__post_init__() → None

Validate the record.

Return type:

None

property is_checkout: bool

Whether the environment is a .venv inside a repository — git or local.

Return type:

bool

property slot: str

the version for a release, git-<ref> for a checkout.

A checkout moves — its version changes with every pull — so it is kept by the ref it tracks, and two checkouts at the same commit (two refs) never share a record.

Type:

What identifies the environment within the package

Return type:

str

property key: tuple[str, str]

(package, slot) — what identifies the environment.

Return type:

tuple[str, str]

satisfies(spec: plesty.server.model.device.DeviceSpec) → bool

Whether this install is what spec asks for.

A PyPI spec is satisfied by the pinned version (or any PyPI version when unpinned); a git spec by an install from the same repository at the same ref (or v<version>), a commit hash matching by prefix. A local repository satisfies exactly the specs that name its folder (path), and nothing else: loading one never changes what an existing declaration runs.

Parameters:

spec (plesty.server.model.device.DeviceSpec)

Return type:

bool

to_dict() → dict[str, Any]

JSON form.

Return type:

dict[str, Any]

classmethod from_dict(data: collections.abc.Mapping[str, Any]) → InstalledVersion

Rebuild a record from to_dict() output (extra keys ignored).

Parameters:

data (collections.abc.Mapping[str, Any])

Return type:

InstalledVersion

class plesty.server.model.PackageInfo

What the bench knows about a module as a project — independent of any install.

Learned from the catalogue (adopt) or from a git install, kept in <home>/packages/<package>/package.json and never asked again: the repository a module comes from, its project page, its latest release.

Variables:
  • package – Distribution name.

  • repo_url – Clone URL of the module’s repository.

  • web_url – Project page (GitLab), "" when unknown.

  • latest_tag – Latest release tag seen (v0.2.1), "" when none.

  • version – Latest release version seen, "" when none.

  • vendor – Hub vendor group, "" when unknown.

  • description – One line, "" when unknown.

  • module_type – What the module is — device, experiment, … — as its [tool.plesty] module_type says; "" when unknown.

package: str
repo_url: str = ''
web_url: str = ''
latest_tag: str = ''
version: str = ''
vendor: str = ''
description: str = ''
module_type: str = ''
to_dict() → dict[str, Any]

JSON form.

Return type:

dict[str, Any]

classmethod from_dict(data: collections.abc.Mapping[str, Any]) → PackageInfo

Rebuild from to_dict() output (extra keys ignored).

Parameters:

data (collections.abc.Mapping[str, Any])

Return type:

PackageInfo

merged(**fields: str) → PackageInfo

A copy with the non-empty fields replacing the current values.

Parameters:

fields (str)

Return type:

PackageInfo

plesty.server.model.ENV_FILENAME = '.env'
plesty.server.model.parse_env(text: str) → dict[str, str]

Parse dotenv text into a mapping (later keys win).

Parameters:

text (str)

Return type:

dict[str, str]

plesty.server.model.read_env(path: str | os.PathLike[str]) → dict[str, str]

Parse the file at path; empty when it does not exist.

Parameters:

path (str | os.PathLike[str])

Return type:

dict[str, str]

plesty.server.model.render_env(values: collections.abc.Mapping[str, str]) → str

Render a mapping as dotenv text (one KEY=VALUE per line, quoted when needed).

Parameters:

values (collections.abc.Mapping[str, str])

Return type:

str

exception plesty.server.model.FleetError

Bases: ValueError

A fleet file or device declaration is malformed.

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

plesty.server.model.JOB_KIND = 'field-test'
plesty.server.model.REPORT_BASENAME = 'reports/field-test-run'
plesty.server.model.STANDARD_GATES: tuple[str, Ellipsis] = ('discovery', 'connect_lifecycle', 'param_roundtrip', 'param_constraints', 'functions',...
plesty.server.model.TIER_ARTIFACTS: collections.abc.Mapping[str, str]
plesty.server.model.TIER_SCRIPTS: collections.abc.Mapping[str, str]
exception plesty.server.model.FieldTestError

Bases: plesty.server.model.errors.FleetError

A field test cannot be set up as asked.

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

class plesty.server.model.FieldTestReport

What a field test published — the summary a bench needs, not the whole document.

Variables:
  • path – The field-test*.json file.

  • tier – host or client, from the file name.

  • instrument – The reports/<instrument>/ directory name.

  • module – Module name from the document.

  • version – Module version the run tested.

  • ok – Overall outcome.

  • passed – Gates passed.

  • failed – Gates failed.

  • skipped – Gates skipped.

  • gates – Per-gate results, in run order.

  • findings – Count of findings per severity (error/warning/info).

  • modified_utc – ISO timestamp of the file — when the run ended.

path: pathlib.Path
tier: str
instrument: str
module: str
version: str
ok: bool
passed: int
failed: int
skipped: int
gates: tuple[GateResult, Ellipsis] = ()
findings: collections.abc.Mapping[str, int]
modified_utc: str = ''
property summary: str

pm100d 0.2.1 [host] ok 9/9 style.

Type:

One line

Return type:

str

classmethod from_path(path: pathlib.Path) → FieldTestReport

Read a published report.

Raises:

FieldTestError – When the file is not a field-test document.

Parameters:

path (pathlib.Path)

Return type:

FieldTestReport

class plesty.server.model.FieldTestRun

One field-test request.

Variables:
  • tier – host (instrument, server must be stopped) or client (running server).

  • address – --address — host tier: instrument address or mock; client tier: tcp://…; None lets the script use .env.

  • gates – Run only these gates (None = all).

  • repetitions – --repetitions for the timing gates (None = script default).

  • keep_going – Carry on after a failed gate.

  • keep_server – Host tier: leave a running server alone (only right for mock or another instrument).

  • extra – Further arguments passed to the script unchanged.

tier: str = 'host'
address: str | None = None
gates: tuple[str, Ellipsis] | None = None
repetitions: int | None = None
keep_going: bool = False
keep_server: bool = False
extra: tuple[str, Ellipsis] = ()
__post_init__() → None

Validate the request.

Return type:

None

property script: str

The script this request runs, relative to the checkout.

Return type:

str

property artifact: str

The published file name this tier writes.

Return type:

str

property needs_instrument: bool

Whether the run opens the instrument itself (host tier, not mock).

Return type:

bool

argv(report_base: str | None = REPORT_BASENAME, supports: collections.abc.Collection[str] | None = None) → list[str]

The script’s arguments for this request.

Parameters:
  • report_base (str | None) – --report for the host tier’s internal .md/.jsonl pair; None leaves it to the script.

  • supports (collections.abc.Collection[str] | None) – The option names the script accepts (from its --help), or None to assume the current scaffold. Older scaffolds take --port PORT and --mock where the current one takes --address; the request is spelled in whichever the script understands, and options it lacks are left out.

Return type:

list[str]

command(python: pathlib.Path, checkout: pathlib.Path, supports: collections.abc.Collection[str] | None = None) → list[str]

The full command line: python running the tier’s script in checkout.

Parameters:
  • python (pathlib.Path) – The checkout’s interpreter.

  • checkout (pathlib.Path) – The module checkout.

  • supports (collections.abc.Collection[str] | None) – The script’s option names (see argv()).

Raises:

FieldTestError – When the checkout has no script for this tier.

Return type:

list[str]

to_dict() → dict[str, Any]

Record form (stored on the job’s detail).

Return type:

dict[str, Any]

classmethod from_dict(data: collections.abc.Mapping[str, Any]) → FieldTestRun

Rebuild a request from to_dict() output.

Parameters:

data (collections.abc.Mapping[str, Any])

Return type:

FieldTestRun

class plesty.server.model.GateResult

One gate of a published report.

name: str
status: str
duration_s: float
reason: str
plesty.server.model.field_test_script(checkout: pathlib.Path, tier: str = 'host') → pathlib.Path | None

The tier’s script inside checkout, or None when the module has none.

Raises:

FieldTestError – On an unknown tier.

Parameters:
  • checkout (pathlib.Path)

  • tier (str)

Return type:

pathlib.Path | None

plesty.server.model.find_reports(checkout: pathlib.Path, since_utc: str | None = None) → list[FieldTestReport]

Every published field-test report under <checkout>/reports/, newest first.

Parameters:
  • checkout (pathlib.Path) – The module checkout.

  • since_utc (str | None) – Only reports modified at or after this ISO timestamp — what a run that started then produced.

Return type:

list[FieldTestReport]

plesty.server.model.gates_in_help(text: str) → tuple[str, Ellipsis]

The gate names a script’s --help offers for --gates, in its order; empty if none.

Parameters:

text (str)

Return type:

tuple[str, Ellipsis]

plesty.server.model.options_in_help(text: str) → set[str]

The long options an argparse --help text mentions (--address, --mock …).

Parameters:

text (str)

Return type:

set[str]

plesty.server.model.FLEET_FILENAME = 'fleet.yaml'
plesty.server.model.FLEET_VERSION = 1
class plesty.server.model.Fleet

A parsed fleet file.

Variables:
  • devices – Specs keyed by device name, in file order.

  • experiments – Experiment specs keyed by name, in file order.

  • home – home: from the file, or None.

  • index_url – index_url: from the file, or None.

  • path – Where it was read from, or None for an in-memory fleet.

  • benches – benches: as a setup file writes it, carried through unread; see plesty.server.model.setup.parse_setup().

devices: collections.abc.Mapping[str, plesty.server.model.device.DeviceSpec]
experiments: collections.abc.Mapping[str, plesty.server.model.experiment.ExperimentSpec]
home: pathlib.Path | None = None
index_url: str | None = None
path: pathlib.Path | None = None
benches: collections.abc.Mapping[str, Any]
__getitem__(name: str) → plesty.server.model.device.DeviceSpec

The spec of device name; KeyError when undeclared.

Parameters:

name (str)

Return type:

plesty.server.model.device.DeviceSpec

__contains__(name: object) → bool

Whether name is a declared device.

Parameters:

name (object)

Return type:

bool

__iter__() → Any

Iterate over the specs in file order.

Return type:

Any

__len__() → int

Number of declared devices.

Return type:

int

property autostart: list[plesty.server.model.device.DeviceSpec]

The specs fleet up starts.

Return type:

list[plesty.server.model.device.DeviceSpec]

property is_setup: bool

Whether this is an experiment’s setup rather than one of a bench’s fleets.

A setup names its benches:; a bench’s fleet never does. It is committed with the experiment, so the bench only reads it: written back, it lost every comment and gained the ports this bench chose (#48).

Return type:

bool

classmethod from_mapping(raw: collections.abc.Mapping[str, Any], path: pathlib.Path | None = None) → Fleet

Build a fleet from a parsed YAML document.

Parameters:
  • raw (collections.abc.Mapping[str, Any]) – The document.

  • path (pathlib.Path | None) – Where it came from, for error messages and path.

Raises:

FleetError – On a wrong schema version or malformed entries.

Return type:

Fleet

to_mapping() → dict[str, Any]

The YAML document form of this fleet.

Return type:

dict[str, Any]

_copy(**changes: Any) → Fleet

A copy of this fleet with changes applied.

Every mutator goes through here rather than rebuilding the fleet positionally, so a field added between two others cannot silently shift what the next argument means.

Parameters:

changes (Any)

Return type:

Fleet

with_device(spec: plesty.server.model.device.DeviceSpec) → Fleet

A copy with spec added or replaced under its name.

Parameters:

spec (plesty.server.model.device.DeviceSpec)

Return type:

Fleet

without_device(name: str) → Fleet

A copy without device name (no error when absent).

Parameters:

name (str)

Return type:

Fleet

with_experiment(spec: plesty.server.model.experiment.ExperimentSpec) → Fleet

A copy with experiment spec added or replaced under its name.

Raises:

FleetError – If a device already holds that name — a run directory, a job key and a log are keyed by it.

Parameters:

spec (plesty.server.model.experiment.ExperimentSpec)

Return type:

Fleet

without_experiment(name: str) → Fleet

A copy without experiment name (no error when absent).

Parameters:

name (str)

Return type:

Fleet

renamed(old: str, new: str) → Fleet

A copy with device old declared under new, where it already sits.

Rebuilding the mapping rather than dropping and re-adding is the whole point: the fleet file is written in key order, so a device the operator renames stays on the line it was on instead of moving to the bottom.

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

  • new (str) – The name to give it; must be free.

Raises:

FleetError – When old is not declared, new already is, or new is not a name every bench can turn into a file name.

Return type:

Fleet

plesty.server.model.find_fleet(explicit: str | os.PathLike[str] | None, home: pathlib.Path) → pathlib.Path | None

Locate the fleet file a command should consult.

Parameters:
  • explicit (str | os.PathLike[str] | None) – A path the user gave (-f); returned as-is when set.

  • home (pathlib.Path) – The bench home, whose fleet.yaml is the last resort after the working directory’s.

Returns:

The first existing candidate, or None when no fleet file exists.

Return type:

pathlib.Path | None

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

Read and validate a fleet file.

Parameters:

path (str | os.PathLike[str]) – The YAML file.

Raises:

FleetError – When the file is missing or malformed.

Return type:

Fleet

plesty.server.model.save_fleet(fleet: Fleet, path: str | os.PathLike[str]) → pathlib.Path

Write a fleet file.

Parameters:
  • fleet (Fleet) – The fleet to serialize.

  • path (str | os.PathLike[str]) – Destination file; parents are created.

Returns:

The written path.

Return type:

pathlib.Path

plesty.server.model.HOME_ENV = 'PLESTY_SERVER_HOME'
class plesty.server.model.Home

Layout of the plesty-server home directory.

Variables:

root – The directory everything lives under.

root: pathlib.Path
classmethod resolve(root: str | os.PathLike[str] | None = None) → Home

Build a home from an explicit path or the environment default.

Parameters:

root (str | os.PathLike[str] | None) – Directory to use; None selects default_home().

Return type:

Home

property venvs: pathlib.Path

One virtual environment per installed device, keyed by device name.

Return type:

pathlib.Path

property logs: pathlib.Path

One append-only log per device server.

Return type:

pathlib.Path

property state: pathlib.Path

One JSON record per running server (pid, port, start time).

Return type:

pathlib.Path

property run: pathlib.Path

Working directories the servers run in; plesty-lib writes logs/ there.

Return type:

pathlib.Path

property repos: pathlib.Path

Git checkouts of device modules installed from a repository.

Return type:

pathlib.Path

property jobs: pathlib.Path

One JSON record per long-running job (install, update, field test, command).

Return type:

pathlib.Path

property fleets: pathlib.Path

Where the bench keeps its fleets, one file each (<home>/fleets/<name>.yaml).

Return type:

pathlib.Path

property default_fleet: pathlib.Path

The fleet a new bench starts with, empty (<home>/fleets/default.yaml).

Return type:

pathlib.Path

fleet_file(name: str) → pathlib.Path

The file of the fleet called name.

Parameters:

name (str)

Return type:

pathlib.Path

property data: pathlib.Path

Where the devices write while the bench has no Plesty share (#43).

Return type:

pathlib.Path

property demo: pathlib.Path

Where the welcome’s demo keeps its runs, and its spectrometer replays them (#43).

Return type:

pathlib.Path

property tools: pathlib.Path

Tools the bench installed for itself (the portable git on Windows).

Return type:

pathlib.Path

property packages: pathlib.Path

one environment per (package, version), side by side.

Type:

Installed device modules

Return type:

pathlib.Path

package_dir(package: str, version: str) → pathlib.Path

Environment of package at version.

Parameters:
  • package (str)

  • version (str)

Return type:

pathlib.Path

package_record(package: str, version: str) → pathlib.Path

Record file of package at version (beside its environment).

Parameters:
  • package (str)

  • version (str)

Return type:

pathlib.Path

property last_fleet: pathlib.Path

Pointer file naming the fleet the console used last (<home>/last-fleet).

Return type:

pathlib.Path

package_info(package: str) → pathlib.Path

The package-level record of package: repository, project page, latest release.

Parameters:

package (str)

Return type:

pathlib.Path

checkout(package: str, ref: str | None) → pathlib.Path

Checkout directory of package at ref (git sources only).

Refs may contain / (feat/x); the directory name flattens them.

Parameters:
  • package (str)

  • ref (str | None)

Return type:

pathlib.Path

job_file(job_id: str) → pathlib.Path

Record file of job job_id.

Parameters:

job_id (str)

Return type:

pathlib.Path

job_log(job_id: str) → pathlib.Path

Log file of job job_id.

Parameters:

job_id (str)

Return type:

pathlib.Path

venv(name: str) → pathlib.Path

Venv directory of the device name.

Parameters:

name (str)

Return type:

pathlib.Path

log(name: str) → pathlib.Path

The current log file of the device name.

Each server start, and each Clear log, opens a new file named with its time (new_log()); logs/<name>.current names the one in use. Without a pointer — a log written before there were any — it is the single logs/<name>.log every start used to append to.

Parameters:

name (str)

Return type:

pathlib.Path

new_log(name: str) → pathlib.Path

Start a new log file for name, make it the current one, and return it.

Named <name>-<YYYYMMDD-HHMMSS>.log. Older files stay: a clear never truncates a file a running server writes, which is what left an 11.7 MB log no reader could get through (#23).

Parameters:

name (str)

Return type:

pathlib.Path

state_file(name: str) → pathlib.Path

Process-state file of the device name.

Parameters:

name (str)

Return type:

pathlib.Path

run_dir(name: str) → pathlib.Path

Working directory of the device name’s server process.

Parameters:

name (str)

Return type:

pathlib.Path

property runs: pathlib.Path

Where experiment runs land, one directory per declared experiment.

A measurement’s data is not a device’s working directory and does not belong under run/: it outlives the process that wrote it, it is what the operator came for, and a purge that clears working directories must not reach it.

Return type:

pathlib.Path

run_root(name: str) → pathlib.Path

Root the runs of experiment name go under.

One root per declared experiment, so plesty.lib.experiment.Run and latest_run read it exactly as they read a checkout’s runs/.

Parameters:

name (str)

Return type:

pathlib.Path

ensure() → Home

Create the directory tree; returns self for chaining.

Return type:

Home

plesty.server.model.default_home() → pathlib.Path

The home directory the environment or the packaged config designates.

Returns:

$PLESTY_SERVER_HOME when set, else running.home from config.yaml with ~ expanded.

Return type:

pathlib.Path

plesty.server.model.CANCELLED_EXIT = -9
plesty.server.model.LOG_CHUNK = 65536
plesty.server.model.LOST_EXIT = -1
class plesty.server.model.Job

One unit of long-running work.

Variables:
  • id – <name>-<kind>-<timestamp>, unique per bench.

  • name – Device instance the job belongs to.

  • kind – install, update, field-test, command …

  • command – The command line.

  • cwd – Working directory.

  • pid – Process id while it runs.

  • started_utc – ISO timestamp of the launch.

  • finished_utc – ISO timestamp of the end, or None while running.

  • returncode – Exit code once finished (None while running; LOST_EXIT when the process vanished without one).

  • log – Path of the log file.

  • detail – Kind-specific data (e.g. the reports a field test wrote).

id: str
name: str
kind: str
command: list[str]
cwd: str
pid: int | None
started_utc: str
finished_utc: str | None = None
returncode: int | None = None
log: str = ''
detail: dict[str, Any]
property running: bool

Whether the job has not finished yet.

Return type:

bool

property state: str

running, done (exit 0), failed (any other exit).

Return type:

str

to_dict() → dict[str, Any]

JSON form, with the derived state included.

Return type:

dict[str, Any]

classmethod from_dict(data: collections.abc.Mapping[str, Any]) → Job

Rebuild a record from to_dict() output (extra keys ignored).

Parameters:

data (collections.abc.Mapping[str, Any])

Return type:

Job

exception plesty.server.model.JobError

Bases: RuntimeError

A job record does not exist or cannot be read.

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

class plesty.server.model.JobStore(home: plesty.server.model.home.Home)

Job records and logs under a Home.

Bind to a home.

Parameters:

home (plesty.server.model.home.Home) – The bench home.

home
create(name: str, kind: str, command: list[str], cwd: str | os.PathLike[str], pid: int | None = None, detail: collections.abc.Mapping[str, Any] | None = None) → Job

Open a new record and return it (state running).

Parameters:
  • name (str) – Device instance the job belongs to.

  • kind (str) – Job kind, part of the id.

  • command (list[str]) – The command line.

  • cwd (str | os.PathLike[str]) – Working directory.

  • pid (int | None) – Process id when already known.

  • detail (collections.abc.Mapping[str, Any] | None) – Kind-specific data stored on the record.

Return type:

Job

save(job: Job) → pathlib.Path

Write job to its record file; returns the path.

Parameters:

job (Job)

Return type:

pathlib.Path

get(job_id: str) → Job | None

The record of job_id, or None when unknown or unreadable.

Parameters:

job_id (str)

Return type:

Job | None

require(job_id: str) → Job

Like get() but raises JobError when unknown.

Parameters:

job_id (str)

Return type:

Job

list(name: str | None = None, kind: str | None = None) → list[Job]

Jobs on record, newest first, optionally filtered by device and kind.

Parameters:
  • name (str | None)

  • kind (str | None)

Return type:

list[Job]

finish(job_id: str, returncode: int) → Job

Close the record of job_id with returncode; returns it.

Parameters:
  • job_id (str)

  • returncode (int)

Return type:

Job

refresh(job: Job, alive: collections.abc.Callable[[int], bool], exit_code: collections.abc.Callable[[str], int | None]) → Job

Settle a running record against what the process layer knows.

Parameters:
  • job (Job) – The record to bring up to date (mutated and saved when it ends).

  • alive (collections.abc.Callable[[int], bool]) – Whether a pid is still running.

  • exit_code (collections.abc.Callable[[str], int | None]) – The exit code left for a job id, or None when none.

Returns:

job, finished when an exit code exists or the pid is gone (LOST_EXIT then), unchanged while it still runs.

Return type:

Job

log(job_id: str, offset: int = 0, limit: int = LOG_CHUNK) → tuple[str, int]

Read the job log from byte offset.

Parameters:
  • job_id (str) – The job.

  • offset (int) – Where the previous read stopped (0 for the start).

  • limit (int) – Maximum bytes to return.

Returns:

The text read and the offset to continue from. Decoding is lenient — a chunk may cut a multi-byte character.

Return type:

tuple[str, int]

log_end(job_id: str, limit: int = LOG_END) → tuple[str, int, int]

The end of the job log; see log_end().

Parameters:
  • job_id (str)

  • limit (int)

Return type:

tuple[str, int, int]

log_before(job_id: str, end: int, limit: int = LOG_END) → tuple[str, int]

The part of the job log before byte end; see log_before().

Parameters:
  • job_id (str)

  • end (int)

  • limit (int)

Return type:

tuple[str, int]

tail(job_id: str, lines: int = 50) → str

The last lines lines of the job log (empty when there is none yet).

Parameters:
  • job_id (str)

  • lines (int)

Return type:

str

plesty.server.model.tail_file(path: pathlib.Path, lines: int, limit: int = TAIL_LIMIT) → str

The last lines lines of path, read from at most its last limit bytes.

One read of a bounded span. The loop this replaced prepended each 8 KB block to everything read so far until enough newlines turned up — on a file with none (a truncated log a server kept writing at its old offset fills with NULs) that is quadratic in the file’s size, and an 11.7 MB log hung the console, the agent and every reader with it (#23). A line longer than limit is shown from its last limit bytes.

Parameters:
  • path (pathlib.Path)

  • lines (int)

  • limit (int)

Return type:

str

plesty.server.model.PEERS_FILENAME = 'peers.yaml'
plesty.server.model.PEERS_VERSION = 1
class plesty.server.model.Peer

One bench this master drives.

Variables:
  • name – What the console calls it — the Host column’s value, and unique among peers. Not required to be the node’s hostname: a bench is named for the rig it runs, which is what an operator looks for.

  • address – Where the node’s agent listens: host, host:port or a full ZMQ endpoint. Resolved to an endpoint where it is used, so that this model needs nothing of the client.

  • token – The node’s shared secret, or empty when it checks none.

  • data – Where that bench sees the lab’s shared data disk — the same disk every bench sees, each under its own path. Empty when unset.

name: str
address: str
token: str = ''
data: str = ''
property guarded: bool

Whether a secret is sent to this node (never the secret itself).

Return type:

bool

property host: str

The machine the bench runs on, without its agent’s port or scheme.

tcp://lab-bench-03:5550, lab-bench-03:5550 and lab-bench-03 are all lab-bench-03: the device servers on that bench listen on their own ports of the same machine.

Return type:

str

validate() → Peer

Return self, having refused a peer that could not be reached.

Raises:

FleetError – When the name or the address is empty.

Return type:

Peer

to_mapping() → dict[str, Any]

This peer’s entry in the document, without its name.

Return type:

dict[str, Any]

classmethod from_mapping(name: str, raw: Any) → Peer

Build the peer name from its entry.

Raises:

FleetError – When the entry is not a mapping or carries a key this version does not know.

Parameters:
  • name (str)

  • raw (Any)

Return type:

Peer

plesty.server.model.load_peers(home: plesty.server.model.home.Home) → list[Peer]

The nodes home declares, empty when it declares none.

Raises:

FleetError – When the file cannot be read or is malformed. A master whose list is broken is told so — an empty table would say the nodes are gone, which is a different thing entirely.

Parameters:

home (plesty.server.model.home.Home)

Return type:

list[Peer]

plesty.server.model.parse_peers(document: Any) → list[Peer]

Every peer of a peers.yaml document, in the order it declares them.

Raises:

FleetError – When the document, its peers key or any entry is malformed.

Parameters:

document (Any)

Return type:

list[Peer]

plesty.server.model.peers_file(home: plesty.server.model.home.Home) → pathlib.Path

Where home keeps the node list.

Parameters:

home (plesty.server.model.home.Home)

Return type:

pathlib.Path

plesty.server.model.save_peers(home: plesty.server.model.home.Home, peers: list[Peer]) → pathlib.Path

Write peers to home; returns the path.

The file holds the nodes’ shared secrets, so it is made readable by its owner alone — the same guard, and the same Windows caveat, as the agent’s own settings.

Raises:

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

Parameters:
Return type:

pathlib.Path

plesty.server.model.AGENT_PORT = 5550
plesty.server.model.DEFAULT_TCP_PORT = 5555
plesty.server.model.DEVICE_PORT_BASE = 5551
plesty.server.model.allocate_port(taken: collections.abc.Iterable[int], base: int = DEVICE_PORT_BASE, is_free: collections.abc.Callable[[int], bool] = port_is_free) → int

The lowest free port from base up, skipping taken and the agent’s.

Parameters:
  • taken (collections.abc.Iterable[int]) – Ports already spoken for on this bench.

  • base (int) – Where counting starts.

  • is_free (collections.abc.Callable[[int], bool]) – Asks the host whether a port can be bound; injected by tests.

Returns:

A port no other device on the bench holds and nothing on the host is listening on.

Raises:

FleetError – When counting runs past the end of the port range.

Return type:

int

plesty.server.model.declares_port(args: tuple[str, Ellipsis], env: collections.abc.Mapping[str, str]) → bool

Whether args or env name the port themselves.

A declaration that names its own port is left alone — the bench allocates only for devices that would otherwise take the default and collide.

Parameters:
  • args (tuple[str, Ellipsis])

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

Return type:

bool

plesty.server.model.port_from(args: tuple[str, Ellipsis], env: collections.abc.Mapping[str, str]) → int

Resolve the TCP port a server binds from its args and environment.

Parameters:
  • args (tuple[str, Ellipsis]) – Command-line arguments passed to python -m <module>.

  • env (collections.abc.Mapping[str, str]) – Environment values the fleet sets for the process.

Returns:

The port to probe, falling back to DEFAULT_TCP_PORT.

Return type:

int

plesty.server.model.port_is_free(port: int, host: str = '127.0.0.1') → bool

Whether port can actually be bound right now.

The fleet file and the process records say what this bench believes it is using. A server that outlived its record, or any other program on the machine, believes nothing — and the socket is the only thing that knows.

The socket option differs because the platforms disagree about what SO_REUSEADDR means. On POSIX it says “a port left in TIME_WAIT is still free”, which is the question. On Windows it says “bind even if another socket is listening here” — so the probe succeeded against a live server and every busy port reported free, which silently disabled everything built on this: the port a start moves away from, and the port allocate_port() hands out. SO_EXCLUSIVEADDRUSE is the Windows way to ask the question POSIX answers with SO_REUSEADDR.

Parameters:
  • port (int)

  • host (str)

Return type:

bool

plesty.server.model.port_listening(port: int, host: str = '127.0.0.1', timeout: float = 0.3) → bool

Whether something accepts TCP connections on port right now.

The complement of port_is_free() for a port a server holds: a device server binds tcp://*, and on some hosts a specific-address bind still succeeds beside a wildcard listener — connecting is the one test that answers the question everywhere.

Parameters:
  • port (int)

  • host (str)

  • timeout (float)

Return type:

bool

plesty.server.model.EXITED = 'exited'
plesty.server.model.RUNNING = 'running'
plesty.server.model.STARTING = 'starting'
plesty.server.model.STATES
plesty.server.model.STOPPED = 'stopped'
class plesty.server.model.ProcessRecord

What the supervisor persists about a started server.

Variables:
  • name – Device instance name.

  • pid – Process id of the launcher that owns the server.

  • port – TCP port it was told to bind.

  • started_utc – ISO timestamp of the launch.

  • command – The full command line.

  • cwd – Working directory the server runs in.

  • log – Path of the log file its output goes to.

  • python – The interpreter — what a relaunch has to repeat.

  • module – The python -m target — likewise.

  • args – The spec’s arguments — likewise.

  • env – The spec’s environment — likewise.

  • package – Distribution the server runs, for listings.

  • version – Its installed version, for listings.

  • relayed – Its waiter copies the output into the log and can switch it to a new file while it runs. False for a server an older bench started, whose log is the server’s own handle (#23).

name: str
pid: int
port: int
started_utc: str
command: list[str]
cwd: str
log: str
python: str = ''
module: str = ''
args: list[str] = []
env: dict[str, str]
package: str = ''
version: str = ''
relayed: bool = False
to_dict() → dict[str, Any]

JSON form.

Return type:

dict[str, Any]

classmethod from_dict(data: collections.abc.Mapping[str, Any]) → ProcessRecord

Rebuild a record from to_dict() output (extra keys ignored).

Parameters:

data (collections.abc.Mapping[str, Any])

Return type:

ProcessRecord

classmethod load(path: pathlib.Path) → ProcessRecord | None

Read a record file; None when absent or unreadable.

Parameters:

path (pathlib.Path)

Return type:

ProcessRecord | None

save(path: pathlib.Path) → pathlib.Path

Write the record to path; returns it.

Parameters:

path (pathlib.Path)

Return type:

pathlib.Path

class plesty.server.model.ProcessStatus

The observed state of one device server.

Variables:
  • name – Device instance name.

  • state – One of STATES.

  • pid – Process id when a record exists.

  • port – Probed port when a record exists.

  • started_utc – Launch time when a record exists.

  • log – Log path when a record exists.

  • returncode – Exit code for an exited server, when the launcher recorded one (-1 when it vanished without).

  • detail – For an exited server, the tail of its log — why it died.

name: str
state: str
pid: int | None = None
port: int | None = None
started_utc: str | None = None
log: str | None = None
returncode: int | None = None
detail: str = ''
property alive: bool

Whether the process exists (running or still starting).

Return type:

bool

to_dict() → dict[str, Any]

JSON form (what status prints and the agent returns).

Return type:

dict[str, Any]

plesty.server.model.MARKER = '[TRAFFIC]'
class plesty.server.model.TrafficRecord

One served request, as the server reported it.

Variables:
  • when – The log line’s own prefix, verbatim; "" when it had none.

  • type – Request type — call, write, query, connect…

  • target – What it acted on: a function or parameter name, or "".

  • client – Which client asked.

  • outcome – ok or error.

  • ms – How long the server took, in milliseconds.

  • delivered – Whether the reply reached the client.

  • error – Exception type when outcome is not ok, else "".

when: str
type: str
target: str
client: str
outcome: str
ms: float
delivered: bool
error: str
property ok: bool

Whether this exchange both succeeded and arrived.

Return type:

bool

property note: str

The one thing a reader needs to see about a record that is not plain.

Return type:

str

plesty.server.model.parse_traffic(text: str) → list[TrafficRecord]

Every traffic record in text, oldest first.

Tolerant by design: a log holds ordinary server lines, and a truncated tail can begin mid-line. Anything that is not a well-formed record is skipped rather than raising — a monitor that refuses a damaged log shows nothing exactly when something is wrong.

Parameters:

text (str) – A slice of a server log.

Return type:

list[TrafficRecord]