Architecture
plesty-server is built as model – services – presenter – view, with one
extra module, host, for everything that depends on the operating system.
The split answers one question for each piece of code: does it need the
OS, the screen, or neither?
Layer |
Package |
Depends on |
Knows about |
|---|---|---|---|
Model |
|
stdlib, |
Data and rules only: the fleet file, device specs, installed versions, job and process records, field-test requests and reports. Pure Python, no subprocesses, no Qt. Fully testable in memory. |
Services |
|
model, |
The model’s side effects on the bench: creating environments ( |
Presenter |
|
model, services |
|
View |
|
presenter, |
The Qt console: plesty-lib’s palette and fonts, our own layout (menu bar, device table, detail pane, dialogs). Renders state, emits intents, never touches a service — see The console. |
CLI |
|
presenter |
|
Host |
|
stdlib |
The only module with platform branches — see Cross-platform development. |
Re-exec |
|
stdlib |
How |
Client |
|
|
The remote client — a separate, published package; see Remote benches. |
Dependencies point downwards only. Services never import each other’s internals; a presenter composes them.
The home directory
Everything plesty-server writes lives under one directory,
~/.plesty/server by default (PLESTY_SERVER_HOME, home: in the fleet
file or --home override it). Home in the model is the one place that
knows the layout:
~/.plesty/server/
├── fleet.yaml the bench's fleet (optional; cwd first)
├── fleets/ where the console's New/Open/Save fleet dialogs start
├── packages/ installed device modules, one env per version
│ └── plesty-pm100d/
│ ├── 0.2.1/ uv venv (relocatable)
│ ├── 0.2.1.json InstalledVersion record
│ ├── 0.3.0/
│ └── 0.3.0.json
├── repos/ git checkouts, one per (package, ref)
│ └── plesty-pm100d/exp/ checkout; its .venv is the environment
├── state/ one ProcessRecord per started server
│ ├── pm100d.json
│ └── pm100d.exit exit code, written when it ends
├── run/pm100d/ the server's working directory
├── logs/
│ ├── pm100d.log server stdout+stderr, appended per start
│ └── jobs/<job-id>.log one log per job
└── jobs/
├── <job-id>.json Job record
└── <job-id>.exit
Every record is a small JSON file. That is deliberate: several
plesty-server processes look at the same bench at once — a CLI call, the
agent, the GUI — and a file is the simplest thing all of them can read and
that survives the process that wrote it.
The model
Module |
Holds |
|---|---|
|
|
|
The directory layout above. |
|
What is installed: |
|
|
|
|
|
|
|
|
|
The device’s |
The services
Service |
Does |
|---|---|
|
Creates environments with |
|
The detached launcher every server and job goes through. See Process management. |
|
Runs a command as a job: launches, then supplies |
|
Starts/stops/probes device servers and keeps their |
|
Fetches and searches the hub catalogue (stdlib HTTP, injectable fetcher); |
|
Finds |
|
Serves |
How a request flows
“Start pm100d” from the CLI or the GUI:
presenter
│ fleet = load_fleet(...) model
│ spec = fleet["pm100d"] model
│ inst = manager.resolve(spec) model ─ None? → installer.install(spec) (a job)
│ py = installer.python(inst) service
│ status = supervisor.start(spec, py) service ─ launch() → ProcessRecord → probe until ready
└ view.show(status) view
Nothing in that chain imports Qt until the last line, and nothing before the service line touches the operating system.
Long work runs as a job of plesty-server itself
Bench.install(), update() and field_test() return a Job at once. The
job’s command is python -m plesty.server --home <home> -f <fleet> _job install <name> — the same program, a hidden command group, run detached
through the launcher like every other job. So the install’s uv output
lands in logs/jobs/<id>.log, the CLI streams it (plesty-server install pm100d follows the log until the exit code), the GUI tails the same file,
and a closed terminal does not kill a half-done install. The foreground
twins (install_now, update_now, field_test_now) are what the _job
commands call.