plesty.server.view.window
The console’s main window: menu bar, device table, detail pane.
Layout (designed for a bench, not docked):
┌ Bench Device View Help ──────────────────────────────────────────┐
│ ┌ Devices ─────────────────────────────────────────────────────────┐ │
│ │ NAME SOURCE STATE PORT PID STARTED │ │
│ │ pm100d plesty-pm100d==0.2.1 running 5551 4242 10:21:03 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ ┌ pm100d ── [Log] [Jobs] [Reports] [Terminal] [Traffic] ───────────┐ │
│ │ === plesty-server start … :: python -m plesty.pm100d … │ │
│ │ [SERVER] Running at 127.0.0.1:5551 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ ~/.plesty/server · fleet.yaml · 2 devices · refreshed 10:21:05 │
└────────────────────────────────────────────────────────────────────┘
The window owns a Bench and a Worker; every menu action
calls one presenter method on the worker and refreshes when it returns.
Attributes
Classes
The console window. |
|
Trigger a checkable menu entry on release without closing the menu. |
Functions
|
A titled card with optional buttons: the console's layout primitive. |
|
A card-header button wired to slot. |
|
Turn a card's text buttons into icons, each named in its tooltip (#41). |
|
Name an icon button label, drawn as icon; does is what it does, kept if |
|
Set what button does — or why it cannot — under its name, in the tooltip. |
|
The stroke of an icon on a button of role, as the stylesheet colours its text. |
|
|
|
The Lucide icon name ( |
|
What the device table shows, for the status line: |
|
A path with the home directory abbreviated to |
|
Size a Source column by hand, never by its contents (#25). |
|
|
|
Every expanded row of a tree, as the path of labels leading to it. |
|
Open again every row whose path is in opened. |
|
Build an experiment's own views as a widget, to dock in the console. |
|
Build a view of a run out of what the console itself can read. |
|
The fields of a run's records that are worth drawing against the step. |
|
When a report was written and how big it is, as one readable phrase. |
|
Return how one record reads: its step, its summary, and what to open. |
|
One line naming what a document holds, for the row that opens it. |
|
Add value under parent, a level per level of the document. |
|
How one value reads on its own row. |
|
The run state as the table shows it. |
|
Which palette tone a run state is coloured with. |
|
One line saying how far a run has got, and what it is doing now. |
|
What a run has been told to do and has not done yet, or |
|
The palette colour for a state, looked up from the theme on the window. |
Module Contents
- plesty.server.view.window.COLUMNS = ('Fleet', 'Name', 'Host', 'Source', 'State', 'Port', 'PID', 'Started')
- plesty.server.view.window.CHECK_COLUMN = 0
- plesty.server.view.window.NAME_COLUMN = 1
- plesty.server.view.window.HOST_COLUMN = 2
- plesty.server.view.window.SOURCE_COLUMN = 3
- plesty.server.view.window.STATE_COLUMN = 4
- plesty.server.view.window.LOCAL_HOST = 'this bench'
- plesty.server.view.window.TAB_TITLES = ('Log', 'Jobs', 'Reports', 'Terminal', 'Traffic')
- plesty.server.view.window.LOCAL_TABS = ('Log', 'Jobs', 'Reports', 'Terminal', 'Traffic')
- plesty.server.view.window.EXPERIMENT_COLUMNS = ('Run', 'Experiment', 'Source', 'State', 'Progress', 'Started', 'Ended', 'Operator', 'Note')
- plesty.server.view.window.EXP_RUN_COLUMN = 0
- plesty.server.view.window.EXP_NAME_COLUMN = 1
- plesty.server.view.window.EXP_SOURCE_COLUMN = 2
- plesty.server.view.window.EXP_STATE_COLUMN = 3
- plesty.server.view.window.EXP_PROGRESS_COLUMN = 4
- plesty.server.view.window.EXP_NOTE_COLUMN = 8
- plesty.server.view.window.EXPERIMENT_TABS = ('Log', 'Data', 'Report', 'Visualization')
- plesty.server.view.window.RESUMABLE_STATES = ('canceled', 'aborted')
- plesty.server.view.window.LIVE_STATES = ('running', 'paused')
- plesty.server.view.window.DATA_ROWS = 50
- plesty.server.view.window.VIZ_PLOTS = 6
- plesty.server.view.window.DATA_DEPTH = 6
- plesty.server.view.window.DATA_BRANCHES = 32
- plesty.server.view.window.REFRESH_MS = 1000
- plesty.server.view.window.NOTICE_MS = 20000
- class plesty.server.view.window.MainWindow(bench: plesty.server.presenter.Bench, title: str = 'PLESTY bench', version: str = '')
Bases:
PySide6.QtWidgets.QMainWindowThe console window.
Build the window around bench.
- Parameters:
bench (plesty.server.presenter.Bench) – The presenter.
title (str) – Window title.
version (str) – Shown in the About box.
- notice
- bench
- version = ''
- worker
- _views: list[plesty.server.presenter.DeviceView] = []
- _starting: set[str]
- _pending_select: str | None = None
- _control: Any = None
- _device_controls: dict[str, Any]
- _update_to: str | None = None
- _timer
- Return type:
None
- _build_terminal() PySide6.QtWidgets.QWidget
The Terminal tab: run a command against the selected device’s own venv.
Which interpreter a command runs against is the whole point — plesty check, plesty init and plesty field-test have to be the device environment’s, and finding that path by hand in a system terminal is what this replaces. The venv is named above the output so it is never in doubt which one is about to run.
- Return type:
PySide6.QtWidgets.QWidget
- _build_experiment() PySide6.QtWidgets.QWidget
The Experiment tab: what is declared, and what the selected one is doing.
The same two-card shape as the Devices tab — a table of what this bench holds above, the detail of the selected one below — so an operator who has learned one half of the window has learned the other.
- Return type:
PySide6.QtWidgets.QWidget
- _build_data() PySide6.QtWidgets.QWidget
The Data pane: what each step committed, as a tree.
A record is a document, not a line — a spectrum step commits an angle, a file name and whatever the analysis added, and one of those may be a list of fitted lines. Flattened to one JSON line per record it is unreadable at exactly the moment it matters. Each step is a row that opens, and so is every document inside it.
- Return type:
PySide6.QtWidgets.QWidget
- _build_report() PySide6.QtWidgets.QWidget
The Report pane: what was written about this run, and writing more.
An operator’s note belongs to the run it was written on, so it is read where the run is read rather than behind a button on the toolbar. What produced the run is here too — which plesty distributions at which versions — because that is the other thing a reader asks of a run they did not make themselves.
- Return type:
PySide6.QtWidgets.QWidget
- _build_visualization() PySide6.QtWidgets.QWidget
The Visualization pane: the experiment’s own views, drawn here.
A module declares its viewer in its manifest, and when that module is installed beside this console the views are imported and docked into this pane. When it is not — the usual case, since an experiment is installed in an environment of its own — they cannot be drawn here at all, and the pane offers to open them in the module’s environment instead. It says which of the two it is doing, because an empty half-tab reads as something broken.
- Return type:
PySide6.QtWidgets.QWidget
- _add(menu: PySide6.QtWidgets.QMenu, text: str, slot: collections.abc.Callable[[], Any], shortcut: Any = None) PySide6.QtGui.QAction
- Parameters:
menu (PySide6.QtWidgets.QMenu)
text (str)
slot (collections.abc.Callable[[], Any])
shortcut (Any)
- Return type:
PySide6.QtGui.QAction
- _build_body() None
- Return type:
None
- property selected_view: plesty.server.presenter.DeviceView | None
The selected device, or
None.Keyed by the row rather than by the name: two benches may each declare a
pm100d, and the row is what says which of them is meant. Every repaint fills the table fromself._viewsin order, so the index is the same key on both sides.- Return type:
- property selected: str | None
Name of the selected device, or
None.- Return type:
str | None
- property selected_host: str
The node the selected device runs on, empty for this bench’s own.
- Return type:
str
- _select_after_repaint(name: str) None
Select name once the table has been rebuilt.
A renamed device has no row under its new name yet — the refresh that follows the call is what creates it — so the selection is remembered instead of applied.
- Parameters:
name (str)
- Return type:
None
- select(name: str, host: str = '') None
Select the row of name on host (no-op when absent).
- Parameters:
name (str)
host (str)
- Return type:
None
- refresh() None
Re-read the devices — this bench’s and its nodes’ — and repaint them.
- Return type:
None
- refresh_experiments() None
Re-read what every declared experiment is doing, and repaint it.
A status is a read of this bench’s own run directory, so this costs a few small file reads however long a measurement has been going and cannot disturb one.
- Return type:
None
- _read_experiments() list[dict[str, Any]]
Read every run of every declared experiment, off the GUI thread.
- Return type:
list[dict[str, Any]]
- _show_experiments(rows: list[dict[str, Any]]) None
Repaint the run table, keeping the selection where it was.
A run just created takes the selection instead, once a repaint that holds it lands: selecting it straight after asking for the refresh looked for a row that was not drawn yet (#31).
- Parameters:
rows (list[dict[str, Any]])
- Return type:
None
- _show_banner() None
Say what the bench is doing: each run going on, how far, and its devices (#41).
Read from what the two tables last drew — no read of its own — so it is as current as they are and costs the refresh nothing.
- Return type:
None
- _select_run(name: str, run: str | None) None
Put the selection back on one run, when it still has a row.
- Parameters:
name (str)
run (str | None)
- Return type:
None
- property _selected_row: dict[str, Any]
The table row that is selected, as it was last read; empty when none.
- Return type:
dict[str, Any]
- property selected_run: tuple[str, str | None] | None
The selected experiment and its run id, or
None.- Return type:
tuple[str, str | None] | None
- property selected_experiment: str | None
The name of the experiment whose run is selected, or
None.- Return type:
str | None
- property selected_experiment_status: dict[str, Any]
The last-read status of the selected run; empty when none.
- Return type:
dict[str, Any]
- property _selected_is_loaded: bool
Whether the selected run was loaded from elsewhere rather than run here.
- Return type:
bool
- _experiment_selected() None
A different run was picked: the panes follow it.
- Return type:
None
- _update_experiment_buttons() None
Enable each control for what the selected experiment is actually doing.
- Return type:
None
- _gate_experiment_buttons() None
Enable each button for what the selected experiment is actually doing.
- Return type:
None
- _refresh_experiment_detail() None
Repaint whichever experiment pane is in front.
- Return type:
None
- _show_not_started(name: str, run: str) None
Empty every pane, for a run that has produced nothing yet.
A created run has a name, a configuration and a place to write, and nothing else — no log, no records, no report, no views. Saying that in each pane is the honest answer; showing the previous run’s is not.
- Parameters:
name (str) – The experiment.
run (str) – The run’s id, or
""when the experiment has never run.
- Return type:
None
- _tail_experiment_log(name: str) None
Follow the log of the job that ran the selected run.
The experiment’s newest job is not the selected run’s: picking a run created a minute ago, or an older one from the list, showed whatever the last measurement had printed — a log that belongs to a different run and says so nowhere.
- Parameters:
name (str)
- Return type:
None
- _read_experiment_log(key: tuple[str, str, str], ended: bool) Any
Read the next part of the shown log, off the GUI thread.
Reads what the pane is at when the read runs, not when it was asked for: a read asked for during the last one starts where that one ended.
- Returns:
("end", key, text, start, end)for the first read of a log,("more", key, text, offset, ended)after it, orNonewhen the pane has moved to another log meanwhile.- Parameters:
key (tuple[str, str, str])
ended (bool)
- Return type:
Any
- _show_experiment_log(read: Any) None
Put a read of the log on screen, without moving what is being read.
The first read opens on the end of the log, as it stood when the run finished or as far as a running one has got. After it, what a running run adds is appended, and the view follows only when it was already at the end. An ended run, once read to its end, is not read again (#28).
- Parameters:
read (Any)
- Return type:
None
- _log_scrolled(value: int) None
At the top of the shown part of a long log: load the part before it.
- Parameters:
value (int)
- Return type:
None
- _prepend_experiment_log(read: tuple[tuple[str, str, str], str, int]) None
Put an earlier part of the log above the shown one, keeping the view still.
- Parameters:
read (tuple[tuple[str, str, str], str, int])
- Return type:
None
- _show_experiment_data(name: str) None
Show the newest committed records — the raw data the run has produced.
- Parameters:
name (str)
- Return type:
None
- _paint_experiment_data(records: list[dict[str, Any]]) None
Render each record as a row that opens, keeping what was open open.
The tree is rebuilt on every refresh, so what the operator had open has to be reopened — at every level. Restoring only the top level made a nested row fall shut about a second after it was opened, which is worse than not remembering at all.
- Parameters:
records (list[dict[str, Any]])
- Return type:
None
- _show_experiment_report(name: str, status: dict[str, Any]) None
Show what was written about this run, and what produced it.
- Parameters:
name (str)
status (dict[str, Any])
- Return type:
None
- _show_visualization(name: str, status: dict[str, Any]) None
Draw the experiment’s own views here, or offer to open them elsewhere.
- Parameters:
name (str)
status (dict[str, Any])
- Return type:
None
- _embed_views(name: str, status: dict[str, Any], manifest: Any) None
Dock the module’s views in this pane when they can be imported here.
An experiment is installed in an environment of its own, so its viewer usually cannot be imported into this console at all — that is the ordinary case, not a failure, and the pane then offers to open the views in the environment that does have them.
- Parameters:
name (str)
status (dict[str, Any])
manifest (Any)
- Return type:
None
Put “open in a window” on the views’ own menu bar.
Where the shell already keeps Panels and Layout, rather than a row of console chrome above the plots — that was tried and only took height from them. The shell is a QMainWindow, so this is its menu bar and the action sits beside the ones it adds itself.
- Parameters:
shell (Any)
title (str)
- Return type:
None
- popout_views() None
Float the whole viewer out of this pane into a window of its own.
The console’s pane is sized for the console, and a plot worth reading closely wants a screen. This takes the whole shell out — every panel it has, its menus and its layout — rather than floating panels one by one, so what lands in the window is the viewer as the module built it.
- Return type:
None
- dock_views() None
Put the views back in this pane, wherever their window ended up.
- Return type:
None
- _offer_viewer(name: str, status: dict[str, Any], module: str, failure: str = '') None
Say the views cannot be drawn here, and why; offer the module’s own window.
- Parameters:
name (str) – The experiment.
status (dict[str, Any]) – The run’s status.
module (str) – The viewer the module declares;
""when none.failure (str) – Why loading it here failed, when it was tried.
- Return type:
None
- _clear_embedded_views() None
Take down any docked views and show the offer again.
Views out in a window of their own go with them: they are this run’s, and the pane is being cleared because the run they belong to is not the one shown any more.
- Return type:
None
- run_experiment() None
Start a run of the selected experiment, or continue the one that stopped.
- Return type:
None
- _start_run(name: str, resume: str | None, start: str | None) None
Start a run of name — continuing resume, or under the created name start.
- Parameters:
name (str)
resume (str | None)
start (str | None)
- Return type:
None
- _experiment_started(name: str, job: plesty.server.model.Job) None
Note the run and follow its log.
- Parameters:
name (str)
job (plesty.server.model.Job)
- Return type:
None
- property selected_run_id: str | None
The run the table has selected, for the controls that drive one.
Asking for a run by name and getting whichever is newest is the same bug three times over: pause, stop and abort each wrote their request into the newest run directory on disk rather than into the run the operator was looking at. With one run they are the same directory, which is why it held together until a second run existed — and then Pause silently paused something that had finished hours ago.
- Return type:
str | None
- pause_experiment() None
Hold the run before its next step, or let a held one go on.
- Return type:
None
- stop_experiment() None
Ask the run to finish the step in flight and then stop.
- Return type:
None
- abort_experiment() None
Ask the run to stop without finishing the step in flight.
- Return type:
None
- add_run_note() None
Experiment → Add note…: one more note on the selected run, open or ended (#40).
- Return type:
None
- report_experiment() None
Record something on the running measurement.
- Return type:
None
- create_run() None
Name a run of a declared experiment, without starting it.
Creating and starting are separate: a measurement takes hours and moves the rig, so the name, the configuration and the plan are worth seeing in the table before anything moves. Run starts it.
- Return type:
None
- _declared_then_create(name: str, run_id: str, config: str, operator: str = '', note: str = '') None
Repaint what was just declared, then create the run that was asked for.
- Parameters:
name (str)
run_id (str)
config (str)
operator (str)
note (str)
- Return type:
None
- _create_experiment_run(name: str, run_id: str, config: str, operator: str = '', note: str = '') None
Create the run and put it on the table.
- Parameters:
name (str) – The experiment as this bench declares it.
run_id (str) – What to call the run; the bench names it when this is empty.
config (str) – The configuration file, relative to the checkout.
operator (str) – Who measures it (#40).
note (str) – Why; its first note.
- Return type:
None
- duplicate_run() None
Create a run repeating the selected one, with the settings it used.
- Return type:
None
- _run_created(row: dict[str, Any]) None
Repaint, and put the selection on the run that was just named.
- Parameters:
row (dict[str, Any])
- Return type:
None
- load_run() None
Read a run measured elsewhere, from the directory it was written to.
A run carries what is needed to read it — the plan, the journal, the records, and the versions that produced it — so one measured on another bench, or restored from a backup, is openable here without declaring the experiment it came from. It is read, not adopted: nothing about it can be driven from this console.
- Return type:
None
- _forget_loaded(path: str) None
Take a loaded run off the table, leaving its directory alone.
- Parameters:
path (str)
- Return type:
None
- _run_loaded(row: dict[str, Any]) None
Put a loaded run in the table, replacing an earlier read of it.
- Parameters:
row (dict[str, Any])
- Return type:
None
- _experiment_asked(name: str, what: str) None
Say what was asked of the run, and repaint.
- Parameters:
name (str)
what (str)
- Return type:
None
- watch_experiment() None
Open the module’s own viewer on the run being shown.
- Return type:
None
- configure_experiment() None
Edit what the selected run is configured with — or, with no run, the experiment.
Configuring changes the selected run alone: a run created and waiting is started with its own copy, so the module’s file and the runs beside it stay as they were. The row of an experiment that has no run yet configures the experiment, which is what Create starts from.
- Return type:
None
- _open_experiment_config(name: str, manifest: Any, config: str, spec: Any, run: dict[str, Any] | None = None) None
Open the settings dialog on what the module declared.
- Parameters:
name (str)
manifest (Any)
config (str)
spec (Any)
run (dict[str, Any] | None)
- Return type:
None
- export_run_report() None
Compile the selected run into one HTML file, where the operator says.
The run directory is the measurement; this is a reading of it that can be sent to somebody who cannot open a run directory — a supervisor, a collaborator, a paper’s supplementary material.
- Return type:
None
- report_problem() None
Describe what went wrong, with what the bench can say about itself.
Always available: a report is wanted most when something is broken, so this asks nothing of the bench being in a good state.
- Return type:
None
- check_for_updates() None
Show the installed and the newest version, and update to it when asked (#29).
The update is not run from here: the console is the environment being replaced. Asking for it closes the window — through the ordinary close, which asks before stopping running servers and jobs — and the helper installs once this process has gone, then starts the console again.
- Return type:
None
- show_about() None
Help ▸ About: this version, how it was installed, and the last update (#29).
- Return type:
None
- offer_welcome_at_start() None
Open the welcome on the first start of a bench that holds nothing (#43).
- Return type:
None
- open_welcome() None
Help → Welcome: install the demo, create its first run, and run it (#43).
One PC, no lab hardware: the demo experiment’s setup brings its own stage simulator and a spectrometer that replays measured spectra, all on this bench. The install is a job like any other; when it has ended well, the experiment is declared, the run created and started.
- Return type:
None
- check_for_updates_at_start() None
Ask PyPI for a newer bench, off the GUI thread, and say so in one line (#39).
Benches ran old versions for weeks because the only way to learn of a release was to open Help ▸ Check for updates…. At most once a day (
startup_update()); never for a source checkout, which cannot update itself and is never “the newest”; and not at all when Server ▸ Configure… turned it off. No network, or a check that fails, says nothing.- Return type:
None
- _offer_update(latest: str | None) None
Show the update line for latest; nothing when there is none.
- Parameters:
latest (str | None)
- Return type:
None
- report_last_update() None
Say how the last update went, once, on the start that follows it (#29).
- Return type:
None
- declare_experiment() None
Declare an experiment on this bench, without creating a run of it.
The menu’s way in, for declaring ahead of time. Create does its own declaring, so this is not on the path from installing a module to running it — it is there for someone setting a bench up.
The installed modules that may be experiments are offered to choose between, as the device dialog does: the bench knows where it installed each one from, so choosing one fills in the repository, the ref and a proposed name rather than asking for them again.
- Return type:
None
- _experiment_declared(name: str) None
Repaint and select what was just declared.
A newly declared experiment has no run, so its row is the one with no run id — which is what
_select_run()is asked for.- Parameters:
name (str)
- Return type:
None
- remove_run() None
Delete the selected run, or take a loaded one off the table.
A row is a run, so the button beside it removes that run — dropping the whole experiment from here would take every run with it, which is what the Experiment menu is for.
A run loaded from elsewhere is only forgotten: its directory belongs to whoever measured it, and this console is reading it, not keeping it.
- Return type:
None
- open_run() None
Open the selected run’s directory in the desktop’s file manager.
The run directory is the measurement — the records, the journal, the analyses and now the report — and everything this pane shows is a reading of it. An operator who wants the files themselves, to copy one out or to attach it to something, should not have to retype a path the console already knows.
- Return type:
None
- remove_experiment() None
Drop the selected experiment from the fleet, leaving its runs.
- Return type:
None
- install_experiment() None
Install the selected experiment’s module as a job.
- Return type:
None
- update_experiment() None
Pull and reinstall the selected experiment’s module as a job.
- Return type:
None
- _experiment_install(update: bool) None
Install or update the module of the selected experiment.
The installer is keyed by the package and knows nothing of devices or experiments, so this is the device path unchanged.
- Parameters:
update (bool)
- Return type:
None
- _show_devices(views: list[plesty.server.presenter.DeviceView]) None
- Parameters:
views (list[plesty.server.presenter.DeviceView])
- Return type:
None
- _selection_changed() None
- Return type:
None
- _update_buttons() None
Enable the device buttons for the selected row and its state.
- Return type:
None
- _refresh_detail() None
- Return type:
None
- _set_tabs_live(live: collections.abc.Sequence[str]) None
Enable the detail tabs that have something to read, disable the rest.
A disabled tab and its pane, which says which bench to read it on, are between them enough. A marker on every tab name was a second thing to learn for a state the tab already shows.
- Parameters:
live (collections.abc.Sequence[str])
- Return type:
None
- _show_log(text: str) None
- Parameters:
text (str)
- Return type:
None
- _show_jobs(jobs: list[plesty.server.model.Job]) None
- Parameters:
jobs (list[plesty.server.model.Job])
- Return type:
None
- _build_traffic() PySide6.QtWidgets.QWidget
The Traffic tab: what this device’s server has served.
The console cannot watch the wire — the server is another process — so this reads the records plesty-lib writes to the server log. Metadata only: what was asked, of what, how long it took and how it ended. A result never appears here, because a result can be a spectrum.
- Return type:
PySide6.QtWidgets.QWidget
- _show_traffic(result: tuple[str, list[Any]]) None
Fill the Traffic tab, newest last, and colour what went wrong.
An empty pane is ambiguous — a server that has served nothing looks exactly like one whose plesty-lib is too old to write records — so the caption says both rather than leaving a blank table to read as broken.
A result for a device that is no longer selected is dropped: it was read before the selection moved, and painting it here would put one device’s traffic under another one’s name.
- Parameters:
result (tuple[str, list[Any]])
- Return type:
None
- _show_reports(reports: list[Any]) None
- Parameters:
reports (list[Any])
- Return type:
None
- _note(line: str) None
Show a progress line on its own row, and keep it.
Its own row, because a failure is standing information the operator has to act on and progress is not: neither may hide the other, which is also why this is not
QStatusBar.showMessage— that hides every non-permanent widget, the standing failure included.It clears itself after
NOTICE_MS, because “installing …” an hour later is a lie about what the bench is doing. Nothing is lost — View ▸ Messages… has every line of the session.- Parameters:
line (str)
- Return type:
None
- clear_notice() None
Drop the progress line (the timer, and any action that finishes).
- Return type:
None
- property notice_text: str
The progress line currently shown (empty when none).
- Return type:
str
- _view_choices() tuple[list[str], list[str]]
What View → Fleet offers: this bench’s fleets, and the other benches.
- Return type:
tuple[list[str], list[str]]
View → Fleet: All, then every fleet of this bench and every other bench.
Each is ticked on its own, so any mix shows; All ticks or clears them all. Filled when it opens, since fleets and benches come and go under it.
- Return type:
None
- _sync_view_checks() None
Tick the View → Fleet entries that are shown.
- Return type:
None
- _toggle_view(data: tuple[str, str], checked: bool) None
One View → Fleet entry ticked or cleared.
- Parameters:
data (tuple[str, str])
checked (bool)
- Return type:
None
- set_view(fleets: frozenset[str] | None, benches: frozenset[str] | None) None
Show the devices of fleets of this bench and of the other benches.
Noneis every one, including those added later; an empty set is none.- Parameters:
fleets (frozenset[str] | None)
benches (frozenset[str] | None)
- Return type:
None
- show_messages() None
View → Messages…: every progress line this session, newest last.
- Return type:
None
- _show_message(text: str, tone: str) None
- Parameters:
text (str)
tone (str)
- Return type:
None
- show_error(message: str) None
Report a failure in the window, where it costs the operator nothing to read.
Not a modal. What fails here fails asynchronously — an install takes minutes and the operator is often elsewhere — and a modal blocks the Qt event loop: the device table stops refreshing, an open job log stops tailing, and the console is frozen until somebody clicks it away. The message stays until it is dismissed or another replaces it.
- Parameters:
message (str)
- Return type:
None
- show_outcome(message: str) None
Report something that ended as it should, where a failure would be reported.
Not a modal, for the reason
show_error()gives, and not a progress line (_note()), which clears itself: an install that ended while the operator was away is still news when they are back. The next message replaces it.- Parameters:
message (str)
- Return type:
None
- clear_error() None
Hide the standing message (the Dismiss button, and any action that succeeds).
- Return type:
None
- property error_text: str
What the window is currently reporting as failed (empty when nothing).
- Return type:
str
- _call(action: collections.abc.Callable[[], Any], then: collections.abc.Callable[[Any], None] | None = None) None
Run a presenter call on the worker; refresh afterwards, report on error.
- Parameters:
action (collections.abc.Callable[[], Any])
then (collections.abc.Callable[[Any], None] | None)
- Return type:
None
- _require() str | None
- Return type:
str | None
- _item_changed(item: PySide6.QtWidgets.QTableWidgetItem) None
A check box toggled: include/exclude the device from the working fleet.
- Parameters:
item (PySide6.QtWidgets.QTableWidgetItem)
- Return type:
None
- _fleets_dir() pathlib.Path
Where the fleet file dialogs start:
<home>/fleets/(created on demand).- Return type:
pathlib.Path
- new_fleet(path: str | None = None) None
File → New fleet…: an empty fleet file, made current (devices are added to it).
- Parameters:
path (str | None)
- Return type:
None
- open_fleet(path: str | None = None) None
File → Open fleet…: replace the declarations with another fleet file.
- Parameters:
path (str | None)
- Return type:
None
- _swap_fleet(path: str, create: bool) None
Stop the running devices, then make path the current fleet.
The servers of the fleet being left behind would otherwise keep running out of reach of the console, holding their ports against the devices of the fleet being opened. They are named before they are stopped, and Cancel leaves the current fleet as it is. With nothing running there is nothing to say, and the fleet opens straight away.
- Parameters:
path (str)
create (bool)
- Return type:
None
- save_fleet_as(path: str | None = None) None
File → Save working fleet as…: the checked devices to a new fleet file.
- Parameters:
path (str | None)
- Return type:
None
- _fleet_changed() None
- Return type:
None
- edit_device_config() None
Edit → Device config…: declaration + .env of the highlighted device.
The catalogue is fetched on the worker first, so the module’s repository URL and latest tag are in the drop-downs when the operator switches a release declaration to a repository one.
- Return type:
None
- edit_fleet_file() None
Edit → Fleet file…: the YAML of the whole fleet.
- Return type:
None
- run_in_venv() None
Terminal → Run: start the typed command in the selected device’s environment.
One at a time: the pane shows one command’s output, and a second command started underneath it would interleave into the same view with no way to tell them apart.
- Return type:
None
- stop_venv_command() None
Terminal → Stop: cancel the command running in the pane.
- Return type:
None
- _refresh_terminal() None
Name the environment, and append whatever the running command has written.
- Return type:
None
- _show_venv(label: str) None
Say which environment the next command runs against, or why there is none.
- Parameters:
label (str)
- Return type:
None
- configure_agent() None
Server → Configure…: the endpoint, token and workers this bench serves on.
Saving does not move a running agent: its clients connected to the endpoint it bound, with the token it was started with. The operator is told to restart it rather than have it change underneath them.
- Return type:
None
- toggle_agent() None
Server → Start/Stop agent: serve plesty-bench clients from this console, or stop.
The console is the long-running process a lab PC already has open, so the agent runs on a thread of it — the same service
fleet run --agentstarts, with no second process to supervise.- Return type:
None
- stop_orphans() None
Server → Stop servers outside this fleet: end what no fleet row can reach (#24).
- Return type:
None
- _show_agent() None
Name the action for what it would now do, and repaint the status line.
- Return type:
None
- _show_role() None
Offer the bench list only on a master, and say why when it is not offered.
- Return type:
None
- edit_peers() None
Server → Benches…: the other benches this master shows and drives.
- Return type:
None
- open_catalogue() None
Hub → Browse…: fetch the Hub’s list of modules, install one.
Only the module: the fleet is not touched here. Declaring an instance of it is Device ▸ Add device…, which offers what the bench holds.
- Return type:
None
- load_local_module() None
Hub → Load from…: a module’s repository on this machine, loaded where it lives.
Like an install, it stocks the bench and declares nothing; unlike one, the folder stays where it is and only its path and what it is are kept.
- Return type:
None
- open_installed_modules() None
Hub → Installed modules…: every module on the bench, declared or not, to uninstall.
- Return type:
None
- declare_device() None
Device → Add device…: declare an instance of an installed module.
With no device module installed the user is sent to the Hub instead. An installed experiment is not offered: declared here, it would be served as a device.
- Return type:
None
- remove_device() None
Edit → Remove from fleet (also the Remove button).
- Return type:
None
- start_device() None
Device → Start (one start at a time per device).
- Return type:
None
- stop_device() None
Device → Stop.
- Return type:
None
- restart_device() None
Device → Restart.
- Return type:
None
- _run_then_reread(action: collections.abc.Callable[[], Any], name: str, host: str) None
Run a stop or restart, then let the held panels of name read again.
On failure as well as success: a panel left held would refuse every call for good.
- Parameters:
action (collections.abc.Callable[[], Any])
name (str)
host (str)
- Return type:
None
- _open_panel_windows(name: str, host: str) list[Any]
The control windows that may hold a panel of name, open ones only.
- Parameters:
name (str)
host (str)
- Return type:
list[Any]
- _hold_panels(name: str, host: str = '') None
Have every open panel of name stop calling a server that is going away (#22).
- Parameters:
name (str)
host (str)
- Return type:
None
- _reread_panels(name: str, host: str = '') None
Have every open panel of name read the device it now runs as (#22).
A device can be docked in its own control window and on the wall at once; both read again.
- Parameters:
name (str)
host (str)
- Return type:
None
- install_device() None
Device → Install: starts the job and opens its dialog.
A node’s install is started there and left to run: this console keeps no job list for a device it does not host, so what comes back is the job’s identifier in a status line rather than a dialog following it.
- Return type:
None
- update_device() None
Device → Update: starts the job and opens its dialog.
- Return type:
None
- uninstall_device() None
Device → Uninstall.
- Return type:
None
- clear_device_log() None
Clear log: empty the selected device’s log after confirming.
Asked first, because it is not undoable and it takes the Traffic tab with it — the traffic history is read back out of this same file, and an operator who wanted only one of the two would be surprised by the other. A running server keeps writing into the emptied file.
- Return type:
None
- control_device() None
Device → Control…: open this device’s controls, and only this device’s.
A window of its own, named for it. The bench’s Control window is a wall that keeps every panel docked in it, so opening one device’s controls there showed the last device’s beside them — Control on the table is what opens the wall.
- Return type:
None
- device_control_window(host: str, name: str) Any
Return the window for one device’s controls, creating it once.
One device, one window. The bench’s own Control window is a shell that keeps every panel docked in it, which is what makes it a wall of instruments — and what made Control on the detail card open the last device’s panel beside this one’s. Asking for a single device’s controls gets that device and nothing else.
- Parameters:
host (str) – The bench it runs on, empty for this one.
name (str) – The device.
- Returns:
The
ControlWindowfor it.- Return type:
Any
- control_window() Any
Return the bench’s Control window, creating it the first time.
This one is the wall: every running device docked together, which is what Control on the device table opens.
- Returns:
The
ControlWindow.- Return type:
Any
- show_control_window() None
View → Control window: bring it up, showing every running device.
It used to open whatever was already there, which on first use was nothing: a Shell with no panels has no central widget, so an operator with two running devices opened the window and was shown an empty frame. Opening it now means “control this bench” — every device whose server is answering gets a panel, and the ones that cannot are named in the status bar rather than silently missing.
A device already open keeps its panel, so this can be used to pick up devices started since, without disturbing the arrangement.
- Return type:
None
- _close_control_window() None
Close every control window, releasing the connections they hold.
The bench’s own, and one per device opened from a detail card: each holds a link to a device server, and a socket released after its server has gone waits out its own timeout on the way down.
- Return type:
None
- field_test_device() None
Device → Field test…: configure, start the job, open its dialog.
- Return type:
None
- fleet_up() None
Device → Fleet up (the working fleet).
- Return type:
None
- fleet_down() None
Device → Fleet down (the working fleet).
- Return type:
None
- show_jobs() None
View → Jobs…: the Jobs tab of the detail pane.
- Return type:
None
- _open_job(job: plesty.server.model.Job, then: collections.abc.Callable[[plesty.server.model.Job], None] | None = None, close_when_done: bool = False) None
Follow job in a window; then is called with it once it has ended.
close_when_done closes the window once the job has ended well — for a job that is a step on the way to something else, like the welcome’s install (#43). One that failed stays open, its log the reason.
- Parameters:
job (plesty.server.model.Job)
then (collections.abc.Callable[[plesty.server.model.Job], None] | None)
close_when_done (bool)
- Return type:
None
- _announce(job: plesty.server.model.Job) None
Say how an install or update ended, in the window and not only in its log.
- Parameters:
job (plesty.server.model.Job)
- Return type:
None
- _open_job_row(item: PySide6.QtWidgets.QTableWidgetItem) None
- Parameters:
item (PySide6.QtWidgets.QTableWidgetItem)
- Return type:
None
- Parameters:
pos (Any)
- Return type:
None
- closeEvent(event: Any) None
Closing the console closes what it manages: servers and running jobs.
Asks first when something is running — an accidental close must not take a bench down — then stops everything synchronously so nothing is left unmanaged behind a window that is gone.
- Parameters:
event (Any)
- Return type:
None
- _hand_over_update(version: str) None
Start the helper that installs version once this console has exited.
- Parameters:
version (str)
- Return type:
None
- plesty.server.view.window._card(title: str | PySide6.QtWidgets.QLabel, body: PySide6.QtWidgets.QWidget, buttons: list[PySide6.QtWidgets.QWidget] | None = None, own_row: bool = False) PySide6.QtWidgets.QWidget
A titled card with optional buttons: the console’s layout primitive.
- Parameters:
title (str | PySide6.QtWidgets.QLabel) – Card title (a label is reused as-is).
body (PySide6.QtWidgets.QWidget) – The card’s content.
buttons (list[PySide6.QtWidgets.QWidget] | None) – Buttons shown on the title line (few) or, with own_row, on a bar of their own between the title and the body (many).
own_row (bool) – Put the buttons on their own row.
- Return type:
PySide6.QtWidgets.QWidget
- plesty.server.view.window._button(text: str, slot: collections.abc.Callable[[], Any], tooltip: str = '', primary: bool = False, danger: bool = False) PySide6.QtWidgets.QPushButton
A card-header button wired to slot.
- Parameters:
text (str)
slot (collections.abc.Callable[[], Any])
tooltip (str)
primary (bool)
danger (bool)
- Return type:
PySide6.QtWidgets.QPushButton
- plesty.server.view.window.ICON_SIZE = 16
- plesty.server.view.window._iconify(buttons: collections.abc.Mapping[str, PySide6.QtWidgets.QPushButton], icons: collections.abc.Mapping[str, str]) None
Turn a card’s text buttons into icons, each named in its tooltip (#41).
The label stays the button’s accessible name and its
labelproperty, so it is still what the button is called — to a screen reader, and to the code that renames it (_relabel()).- Parameters:
buttons (collections.abc.Mapping[str, PySide6.QtWidgets.QPushButton])
icons (collections.abc.Mapping[str, str])
- Return type:
None
- plesty.server.view.window._relabel(button: PySide6.QtWidgets.QPushButton, label: str, icon: str, does: str | None = None) None
Name an icon button label, drawn as icon; does is what it does, kept if
None.- Parameters:
button (PySide6.QtWidgets.QPushButton)
label (str)
icon (str)
does (str | None)
- Return type:
None
- plesty.server.view.window._describe(button: PySide6.QtWidgets.QPushButton, does: str) None
Set what button does — or why it cannot — under its name, in the tooltip.
- Parameters:
button (PySide6.QtWidgets.QPushButton)
does (str)
- Return type:
None
- plesty.server.view.window._icon_colour(role: str) str
The stroke of an icon on a button of role, as the stylesheet colours its text.
- Parameters:
role (str)
- Return type:
str
- plesty.server.view.window._theme_tokens() dict[str, str]
- Return type:
dict[str, str]
- plesty.server.view.window._icon(name: str, colour: str) PySide6.QtGui.QIcon
The Lucide icon name (
assets/icons), stroked colour, faint when disabled.Lucide draws in
currentColor, which Qt reads as black on a dark console, so the colour is written into the SVG before it is rendered.- Parameters:
name (str)
colour (str)
- Return type:
PySide6.QtGui.QIcon
- class plesty.server.view.window._StayOpen
Bases:
PySide6.QtCore.QObjectTrigger a checkable menu entry on release without closing the menu.
- eventFilter(watched: PySide6.QtCore.QObject, event: PySide6.QtCore.QEvent) bool
- Parameters:
watched (PySide6.QtCore.QObject)
event (PySide6.QtCore.QEvent)
- Return type:
bool
- plesty.server.view.window._shown(fleets: frozenset[str] | None, benches: frozenset[str] | None, others: bool = True) str
What the device table shows, for the status line:
all fleets, or each by name.others says whether any other bench is shown at all; without one, “all other benches” is not said.
- Parameters:
fleets (frozenset[str] | None)
benches (frozenset[str] | None)
others (bool)
- Return type:
str
- plesty.server.view.window._short(path: Any) str
A path with the home directory abbreviated to
~.- Parameters:
path (Any)
- Return type:
str
- plesty.server.view.window.SOURCE_WIDTH = 240
- plesty.server.view.window._source_column(table: PySide6.QtWidgets.QTableWidget, column: int) None
Size a Source column by hand, never by its contents (#25).
Source is the longest cell in the window — a distribution, a version and a commit. Sized to its contents it pushed the window wider than the screen; as
Stretchit took what the other columns left and could not be dragged at all. It starts atSOURCE_WIDTH, the operator drags it from there, the cell elides and its tooltip carries the full text, and the last column takes up whatever width is spare.- Parameters:
table (PySide6.QtWidgets.QTableWidget)
column (int)
- Return type:
None
- plesty.server.view.window.CLOCK_FORMAT = '%y-%m-%d,%H:%M:%S'
- plesty.server.view.window._clock(stamp: str | None) str
YY-MM-DD,HH:MM:SSof an ISO timestamp (local time), or"".- Parameters:
stamp (str | None)
- Return type:
str
- plesty.server.view.window._opened_paths(root: PySide6.QtWidgets.QTreeWidgetItem, prefix: tuple[str, Ellipsis] = ()) set[tuple[str, Ellipsis]]
Every expanded row of a tree, as the path of labels leading to it.
The path rather than the label, because labels repeat: every step of a sweep has a
paramsand avalue, and remembering “params is open” would open it under every step at once.- Parameters:
root (PySide6.QtWidgets.QTreeWidgetItem) – The item whose children to walk.
prefix (tuple[str, Ellipsis]) – The labels leading to root.
- Returns:
One path per expanded row.
- Return type:
set[tuple[str, Ellipsis]]
- plesty.server.view.window._reopen(root: PySide6.QtWidgets.QTreeWidgetItem, opened: set[tuple[str, Ellipsis]], prefix: tuple[str, Ellipsis] = ()) None
Open again every row whose path is in opened.
- Parameters:
root (PySide6.QtWidgets.QTreeWidgetItem) – The item whose children to walk.
opened (set[tuple[str, Ellipsis]]) – Paths from
_opened_paths().prefix (tuple[str, Ellipsis]) – The labels leading to root.
- Return type:
None
- plesty.server.view.window._embedded_view(module: str, run_dir: Any, site_dirs: collections.abc.Sequence[Any] = ()) Any
Build an experiment’s own views as a widget, to dock in the console.
The viewer is loaded the way plesty-lib’s viewer contract loads one (
plesty.lib.monitor.load_viz()): by importing it. An experiment is installed in an environment of its own, so it is usually not importable here as it stands; site_dirs — that environment’s packages — are then added behind this console’s own on the import path, so the module and what only it depends on are found there while everything the console already has stays the console’s.plestyis a namespace package, so the module’s part of it joins this one. It also needs a plesty-lib carrying the viewer contract; an older one raises, and the caller says so.- Parameters:
module (str) – Import path of the experiment’s viewer, from its manifest.
run_dir (Any) – The run to watch.
site_dirs (collections.abc.Sequence[Any]) – The module environment’s
site-packages, to import it from when it is not installed here.
- Returns:
A shell holding the panels, not yet started.
- Raises:
Exception – Anything the import or the composition raises — the caller treats every one of them the same way.
- Return type:
Any
- plesty.server.view.window._own_view(run_dir: Any, title: str = 'Run') Any
Build a view of a run out of what the console itself can read.
An experiment is installed in an environment of its own, so its viewer usually cannot be imported here and the pane would offer a button and nothing else. But the console has plesty-lib, and a run is a directory of committed records — so the numbers a run recorded can be drawn here whatever produced them, and every run gets a plot instead of almost none.
What this cannot do is the experiment’s own views: a spectrum lives in a file the record only names, and knowing that
data_fileis a spectrum is exactly the experiment-specific knowledge the console does not have. Those stay in the module’s window, which is offered alongside.- Parameters:
run_dir (Any) – The run to draw.
title (str) – What to call the view.
- Returns:
A shell holding one plot per numeric field, not yet started.
- Raises:
Exception – When plesty-lib cannot build a view, or the run carries no number worth plotting — the caller falls back to the offer.
- Return type:
Any
- plesty.server.view.window._plottable(rows: list[dict[str, Any]]) list[str]
The fields of a run’s records that are worth drawing against the step.
A number that never changes is a setting, not a measurement, and a run’s records carry plenty of those — the exposure, the sample, the frame count. Drawing them would fill the pane with flat lines and push the one field that moves off the bottom.
- Parameters:
rows (list[dict[str, Any]]) – The committed records, as read.
- Returns:
The field names, in the order first seen.
- Return type:
list[str]
- plesty.server.view.window._written(report: collections.abc.Mapping[str, Any]) str
When a report was written and how big it is, as one readable phrase.
The size is what says whether a report is worth sending as it stands or wants trimming first, and the time is what says whether it is the one for the run as it finished or a stale one from an earlier export.
- Parameters:
report (collections.abc.Mapping[str, Any])
- Return type:
str
- plesty.server.view.window._unwrap(record: collections.abc.Mapping[str, Any], offset: int) tuple[str, str, dict[str, Any]]
Return how one record reads: its step, its summary, and what to open.
A committed record is an envelope —
index,saved_at,provenance,type,value— around what the step actually produced. Shown as it stands, every row of a sweep carries the same five field names and the step is not named at all, which is a tree that sorts nothing. So the envelope is opened here: the row is the step the provenance names, and its children are the measurement.- Parameters:
record (collections.abc.Mapping[str, Any]) – One committed record.
offset (int) – Its place among the records shown, for a record that carries no step of its own.
- Returns:
The row’s label, the line beside it, and the document under it.
- Return type:
tuple[str, str, dict[str, Any]]
- plesty.server.view.window._fields_of(document: collections.abc.Mapping[str, Any]) str
One line naming what a document holds, for the row that opens it.
A closed row still has to be worth reading, so this names the fields rather than their values: which are there is what tells one step from another, and the values are one click away.
- Parameters:
document (collections.abc.Mapping[str, Any])
- Return type:
str
- plesty.server.view.window._fill_tree(parent: PySide6.QtWidgets.QTreeWidgetItem, value: Any, depth: int = 0) None
Add value under parent, a level per level of the document.
- Parameters:
parent (PySide6.QtWidgets.QTreeWidgetItem) – The row the value belongs to.
value (Any) – A mapping, a sequence, or a leaf.
depth (int) – How deep this is, so a document that nests without end — or one that refers to itself — cannot take the window down with it.
- Return type:
None
- plesty.server.view.window._leaf(value: Any) str
How one value reads on its own row.
- Parameters:
value (Any)
- Return type:
str
- plesty.server.view.window._experiment_state(state: str) str
The run state as the table shows it.
not_startedis the one worth rewording: it is the ordinary state of an experiment that is declared and ready, not a failure to start.- Parameters:
state (str)
- Return type:
str
- plesty.server.view.window._state_tone(state: str) str
Which palette tone a run state is coloured with.
- Parameters:
state (str)
- Return type:
str
- plesty.server.view.window._progress_text(status: collections.abc.Mapping[str, Any]) str
One line saying how far a run has got, and what it is doing now.
The step in flight is named when it reports itself: a long exposure is otherwise indistinguishable from a run that has stopped answering.
- Parameters:
status (collections.abc.Mapping[str, Any])
- Return type:
str
- plesty.server.view.window._asked_text(status: collections.abc.Mapping[str, Any]) str
What a run has been told to do and has not done yet, or
"".A request lands at a step boundary, so there is a stretch — a whole exposure — where the operator has asked for something and nothing has visibly happened. Saying so is the difference between waiting and wondering whether the click registered.
- Parameters:
status (collections.abc.Mapping[str, Any])
- Return type:
str
- plesty.server.view.window._tone_brush(widget: PySide6.QtWidgets.QWidget, state: str) Any
The palette colour for a state, looked up from the theme on the window.
- Parameters:
widget (PySide6.QtWidgets.QWidget)
state (str)
- Return type:
Any