plesty-lib 0.3.4__tar.gz → 0.3.5.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/CHANGELOG.md +36 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/PKG-INFO +1 -1
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/experiment.md +62 -10
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/monitor.md +60 -6
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/gl-secret-detection-report.json +5 -5
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/data/__init__.py +13 -1
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/data/io.py +149 -22
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/experiment/__init__.py +6 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/experiment/base_experiment.py +86 -24
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/experiment/journal.py +2 -2
- plesty_lib-0.3.5.dev1/plesty/lib/experiment/runs.py +356 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/monitor/__init__.py +2 -0
- plesty_lib-0.3.5.dev1/plesty/lib/monitor/assets/viz.yaml +37 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/monitor/sources.py +90 -60
- plesty_lib-0.3.5.dev1/plesty/lib/monitor/viz.py +261 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/ui/qt/shell.py +7 -2
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_experiment.py +18 -6
- plesty_lib-0.3.5.dev1/tests/test_experiment_runs.py +218 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_monitor.py +47 -14
- plesty_lib-0.3.5.dev1/tests/test_viz.py +151 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/.gitignore +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/.gitlab-ci.yml +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/LICENSE +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/LICENSES/LGPL-3.0-or-later.txt +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/README.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/REUSE.toml +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/analyzer.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/api_reference.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/architecture.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/contributing.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/data_schemas.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/device/base_device.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/device/cmd_solver.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/device/composite_device.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/device/error_handling.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/device/func_system.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/device/index.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/device/logging_system.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/device/param_system.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/device/test_helper.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/device/traffic_manager.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/imgs/device_layers.svg +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/imgs/device_standard.svg +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/imgs/plesty_framework.svg +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/index.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/quickstart.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/toc.yaml +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/docs/ui.md +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/__init__.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/analyzer/__init__.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/analyzer/base_analyzer.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/data/array.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/data/ctype_manager.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/data/table.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/data/types.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/data/units.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/__init__.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/async_wrapper.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/base_apt_device.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/base_device_sync.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/base_tcp_scpi_device.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/base_visa_scpi_device.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/composite_device.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/device_utils.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/doc.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/funcs.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/params.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/device/telemetry.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/experiment/schedule.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/monitor/base_monitor.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/service/__init__.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/service/resource_manager.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/service/tcp_ip_client.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/service/tcp_ip_server.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/sim/__init__.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/sim/apt.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/sim/data_generator.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/sim/demo_device.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/solver/__init__.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/solver/iceblock.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/solver/scpi.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/__init__.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/analyzer_pipeline.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/client_field_test.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/device_func_system.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/device_param_system.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/device_pipeline.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/experiment_pipeline.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/field_test.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/field_test_concurrency.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/grouped_param_keys.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/report_artifact.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/resource_allocation.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/schema_params.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/test/schema_refresh.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/traffic/__init__.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/traffic/apt.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/traffic/apt_protocol.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/traffic/serial.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/traffic/tcp_ip.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/traffic/usb_utils.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/traffic/utils.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/traffic/visa.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/ui/__init__.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/ui/assets/palette.json +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/ui/assets/shell.qss +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/ui/monitor_panel.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/ui/panel.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/ui/qt/__init__.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/ui/qt/app.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/ui/qt/plot.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/ui/qt/recorder.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/ui/theme.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/utils/__init__.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/utils/config.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/utils/dll_utils.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/utils/error_utils.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/utils/logger.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/utils/registry.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/plesty/lib/utils/settings.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/pyproject.toml +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_analyzer.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_analyzer_pipeline.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_async_wrapper_threading.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_client_field_test.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_composite_config.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_config.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_data_array.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_data_io_hdf5.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_data_types.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_demo_device.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_device_apt.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_device_base.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_device_data_path.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_device_funcs.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_device_params.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_device_pipeline.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_device_scpi.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_device_telemetry.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_experiment_pipeline.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_field_test.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_misc.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_report_artifact.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_schema_params.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_schema_refresh.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_service_manager.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_settings.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_shipped_imports.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_sim.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_solver.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_tcp_ip_resources.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_traffic_drivers.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_traffic_orphan.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_traffic_utils.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_ui.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_utils.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/tests/test_visa_discovery.py +0 -0
- {plesty_lib-0.3.4 → plesty_lib-0.3.5.dev1}/uv.lock +0 -0
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.5.dev1 — 2026-08-17
|
|
4
|
+
|
|
5
|
+
Development pre-release for field-testing the run convention (#34); the
|
|
6
|
+
final release of this line is 0.4.0 — the API diff against 0.3.4 (the
|
|
7
|
+
`pattern=` arguments and `data_dir` attributes of the run sources are
|
|
8
|
+
gone, `Experiment(run_root=)` defaults to the mount) is a minor bump.
|
|
9
|
+
|
|
10
|
+
- `plesty.lib.monitor.Viz` (#34): the `monitor` / `render` command line over
|
|
11
|
+
an experiment's panels. The experiment registers one function
|
|
12
|
+
(`@viz.panels`, `Run` → panels) and calls `viz.main()`; run discovery,
|
|
13
|
+
`--run-root/--mount/--data-dir/--run`, live vs `--replay`, `--record`,
|
|
14
|
+
offscreen `render` with self-stop, and the text catalog
|
|
15
|
+
(`monitor/assets/viz.yaml`) are the lib's. `Run(..., data_dir=)` supplies
|
|
16
|
+
the host spelling for runs that did not record it.
|
|
17
|
+
- **Run layout: one `records.jsonl` per run instead of one `data/step_NNNN.json`
|
|
18
|
+
per step** (#34). Each completed step appends one fsynced line (its
|
|
19
|
+
`ResultDocument` + `index`; blobs still go to `data/`), and readers tail
|
|
20
|
+
the file by byte offset — writing and following a long run on a network
|
|
21
|
+
share no longer costs a file open and a directory listing per step. New:
|
|
22
|
+
`plesty.lib.data.append_record` / `read_records` / `record_value`;
|
|
23
|
+
`RunSource`/`ReplaySource` lose their `pattern` argument and gain `unread`
|
|
24
|
+
and `legacy`; the journal's `step_completed` carries `recorded` instead of
|
|
25
|
+
a `result` path; a resumed run also counts steps with a committed record
|
|
26
|
+
as done. Runs in the old layout are still read (`RunSource.legacy`).
|
|
27
|
+
- The run convention is spelled out once and exported (#34):
|
|
28
|
+
`plesty.lib.experiment.runs` — `run_id`/`parse_run_id`, `list_runs`,
|
|
29
|
+
`latest_run`, and `Run` (plan, config, journal status, records, and
|
|
30
|
+
`local()` translation of device-written paths onto this machine).
|
|
31
|
+
- One environment convention for the shared disk: `PLESTY_DATA_DIR` (as the
|
|
32
|
+
acquiring host sees it) and `PLESTY_DATA_MOUNT` (as this machine sees it).
|
|
33
|
+
`Experiment` reads both (or `data_dir=`/`data_mount=`), defaults
|
|
34
|
+
`run_root` to the mount when set, exposes `self.run_id`/`self.run_dir`
|
|
35
|
+
before `setup()` and `self.raw_dir` = `<data_dir>/<run_id>/raw` for the
|
|
36
|
+
writing devices, and journals all three with `run_started`. `run_root`
|
|
37
|
+
still defaults to `runs` without a mount.
|
|
38
|
+
|
|
3
39
|
## 0.3.4 — 2026-08-14
|
|
4
40
|
|
|
5
41
|
- The server never cancels a receive (#33): the old
|
|
@@ -17,23 +17,29 @@ instances, never raw hardware.
|
|
|
17
17
|
| `Step` | The smallest unit of work: a stable `id`, an operation name `op`, and its `params`. A step either completes (result persisted and journaled) or is re-run on resume. |
|
|
18
18
|
| `Plan` | The frozen, ordered schedule of steps plus the run configuration. Written to disk at run start, never mutated; a SHA-256 `content_hash()` identifies the schedule. |
|
|
19
19
|
| `Journal` | A crash-safe, append-only JSONL event log (`run_started`, `step_started`, `step_completed`, `step_failed`, `run_completed`/`_canceled`/`_aborted`). Replaying it derives the run state and the completed-step set. |
|
|
20
|
+
| `records.jsonl` | One line per completed step: its result document (`plesty.lib.data.append_record` / `read_records`). One file, appended and fsynced per step, tailed by offset — not one small file per step, which is slow to write and slow to follow on a network share. |
|
|
20
21
|
| `Experiment` | The async ABC tying it together: validate → journal → execute with retries → persist results → resume. |
|
|
22
|
+
| `Run` | Read access to a stored run — plan, config, journal status, records, and the translation of device-written paths onto this machine (`plesty.lib.experiment.runs`, which also spells out the naming below and finds runs: `list_runs`, `latest_run`). |
|
|
21
23
|
|
|
22
24
|
Every run owns a directory:
|
|
23
25
|
|
|
24
26
|
```text
|
|
25
|
-
<run_root>/<run_id>/
|
|
26
|
-
├── plan.json
|
|
27
|
-
├── journal.jsonl
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
└──
|
|
27
|
+
<run_root>/<run_id>/ # run_id = <name>_<YYYYmmdd-HHMMSS>
|
|
28
|
+
├── plan.json # frozen schedule + config + content hash
|
|
29
|
+
├── journal.jsonl # append-only event log
|
|
30
|
+
├── records.jsonl # one line per completed step: its result document
|
|
31
|
+
└── data/ # blobs the records reference (arrays, images), if any
|
|
32
|
+
└── step_0003.npy
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
Each record line is a typed [`ResultDocument`](data_schemas.md) with
|
|
36
|
+
provenance (`step_id`, `op`, `params`) plus the step `index`; a JSON value
|
|
37
|
+
result is inlined, an array or image stays a raw blob in `data/` that the
|
|
38
|
+
line references. The line is the commit — it is appended (and fsynced) only
|
|
39
|
+
after the blob is on disk, and a resumed run treats a committed record as a
|
|
40
|
+
completed step even if the crash came before its journal event. Runs
|
|
41
|
+
written by plesty-lib ≤ 0.3.4 (one `data/step_*.json` per step) are still
|
|
42
|
+
read by `RunSource`, `records()`, and `Run`.
|
|
37
43
|
|
|
38
44
|
## Writing an experiment
|
|
39
45
|
|
|
@@ -78,6 +84,52 @@ Step methods may be sync or async; results are awaited automatically. Each
|
|
|
78
84
|
step is retried (`max_retries`, `retry_sleep` constructor arguments) before
|
|
79
85
|
the run aborts.
|
|
80
86
|
|
|
87
|
+
## Data on a shared disk
|
|
88
|
+
|
|
89
|
+
Blobs a device writes itself (spectrometer frames, camera images) never
|
|
90
|
+
cross the network: the acquiring host saves them to a shared disk and the
|
|
91
|
+
step record carries only the path. Two machines spell that share
|
|
92
|
+
differently, so it is named once, by environment:
|
|
93
|
+
|
|
94
|
+
| Variable | Meaning |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `PLESTY_DATA_DIR` | the share as the acquiring host sees it (e.g. `G:\RAWDATA`) |
|
|
97
|
+
| `PLESTY_DATA_MOUNT` | the same share as this machine sees it (e.g. `/mnt/group/RAWDATA`) |
|
|
98
|
+
|
|
99
|
+
The experiment reads both (or takes `data_dir=` / `data_mount=` at
|
|
100
|
+
construction). With the mount set, `run_root` defaults to it, so runs land
|
|
101
|
+
on the share as well. Before `setup()` is awaited the run id is known and
|
|
102
|
+
`self.raw_dir` names `<data_dir>/<run_id>/raw` in the host's spelling — one
|
|
103
|
+
folder per run, the same one on resume — which `setup()` routes the writing
|
|
104
|
+
devices to:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
async def setup(self) -> None:
|
|
108
|
+
await super().setup()
|
|
109
|
+
if self.raw_dir is not None:
|
|
110
|
+
self.devices.set_data_path(self.raw_dir, devices=["spec"])
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`run_started` journals `data_dir`, `data_mount`, and `raw_dir`, so a reader
|
|
114
|
+
needs at most its own mount:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from plesty.lib.experiment import Run, latest_run
|
|
118
|
+
|
|
119
|
+
run = Run(latest_run("/mnt/group/RAWDATA", name="pol_pl"))
|
|
120
|
+
run.config["powermeter"] # the frozen configuration
|
|
121
|
+
run.status()["state"] # "running", "completed", ...
|
|
122
|
+
for record in run.records():
|
|
123
|
+
run.local(record["data_file"]) # G:\RAWDATA\... → /mnt/group/RAWDATA/...
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`Run.local` uses, in order, an explicit `mount=`, `PLESTY_DATA_MOUNT`, and
|
|
127
|
+
the mount journaled by the machine that ran the experiment (only when that
|
|
128
|
+
directory exists here); with neither a data root nor a mount the path is
|
|
129
|
+
returned as recorded — the case of reading on the acquiring host itself.
|
|
130
|
+
`run.source(mapper)` returns a `RunSource` (live) or, with
|
|
131
|
+
`Run(..., replay_rows=n)`, a `ReplaySource` — see [Monitors](monitor.md).
|
|
132
|
+
|
|
81
133
|
## Crash, cancel, resume
|
|
82
134
|
|
|
83
135
|
`run()` returns the run id. If the run is interrupted — `Ctrl-C`, a crash,
|
|
@@ -82,16 +82,19 @@ clock belongs to the caller: a GUI timer, a test loop, a notebook cell.
|
|
|
82
82
|
|
|
83
83
|
| Source | Follows | Typical use |
|
|
84
84
|
|---|---|---|
|
|
85
|
-
| `RunSource` | `<run_dir>/
|
|
85
|
+
| `RunSource` | `<run_dir>/records.jsonl`, tailed by offset | any running experiment |
|
|
86
86
|
| `TelemetrySource` | `TelemetryEvent`s of devices | live device readings |
|
|
87
87
|
| `PushSource` | whatever the producer emits | tests, notebooks |
|
|
88
88
|
|
|
89
89
|
`RunSource` is the generic live view of *any* PLESTY experiment: the
|
|
90
|
-
[experiment framework](experiment.md)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
[experiment framework](experiment.md) appends one record line per completed
|
|
91
|
+
step, and that line is the commit record of the write, so following the file
|
|
92
|
+
needs no cooperation from the experiment and cannot disturb it. Each poll
|
|
93
|
+
reads only the bytes appended since the last one, so following a run of
|
|
94
|
+
thousands of steps on a network share stays cheap. It also means a viewer
|
|
95
|
+
started mid-run still sees every earlier row. A `Run` from
|
|
96
|
+
`plesty.lib.experiment` builds the source for you (`run.source(mapper)`),
|
|
97
|
+
live or replayed.
|
|
95
98
|
|
|
96
99
|
```python
|
|
97
100
|
from plesty.lib.monitor import RunSource
|
|
@@ -137,6 +140,57 @@ infrastructure. Reusable views live in their own package
|
|
|
137
140
|
(`plesty-common-monitors`); rig-specific ones live next to the experiment
|
|
138
141
|
that needs them.
|
|
139
142
|
|
|
143
|
+
## Watching a run: `Viz`
|
|
144
|
+
|
|
145
|
+
Everything about *watching* is the same for every experiment — which run
|
|
146
|
+
(the newest, or `--run`), where the share is mounted, live or replayed, in a
|
|
147
|
+
window or rendered offscreen to a video, recorded or not. `Viz` owns all of
|
|
148
|
+
it; the experiment writes one function that says which record key means
|
|
149
|
+
what, and that is its whole viz module:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
# plesty/pol_pl/viz.py
|
|
153
|
+
import sys
|
|
154
|
+
|
|
155
|
+
from plesty.common_monitors import SeriesMonitor, SpectrumMonitor, WaterfallMonitor
|
|
156
|
+
from plesty.common_monitors.series import series_mapper
|
|
157
|
+
from plesty.common_monitors.spectrum import spectrum_mapper
|
|
158
|
+
from plesty.lib.monitor import Viz
|
|
159
|
+
from plesty.lib.ui import MonitorPanel
|
|
160
|
+
|
|
161
|
+
viz = Viz("Polarization PL", experiment="pol_pl")
|
|
162
|
+
|
|
163
|
+
@viz.panels
|
|
164
|
+
def panels(run):
|
|
165
|
+
spectra = spectrum_mapper("data_file", locate=run.local, row_key="hwp_deg")
|
|
166
|
+
yield MonitorPanel(SpectrumMonitor(run.source(spectra), name="spectrum"), weight=3)
|
|
167
|
+
yield MonitorPanel(WaterfallMonitor(run.source(spectra), name="map", y_label="HWP angle (deg)"), weight=3)
|
|
168
|
+
if run.config.get("powermeter"):
|
|
169
|
+
power = run.source(series_mapper("hwp_deg", "power_w", y_scale=1e6))
|
|
170
|
+
yield MonitorPanel(SeriesMonitor(power, name="power", y_label="Power (uW)"), weight=2)
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
sys.exit(viz.main())
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
python -m plesty.pol_pl.viz monitor # follow the newest run live
|
|
178
|
+
python -m plesty.pol_pl.viz monitor --replay 2 --record demo.mp4
|
|
179
|
+
python -m plesty.pol_pl.viz render --run pol_pl_20260805-091909 # <run-id>.mp4, no window
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The function receives a [`Run`](experiment.md#data-on-a-shared-disk):
|
|
183
|
+
`run.source(mapper)` is already a `RunSource` or a `ReplaySource` as the
|
|
184
|
+
subcommand decided — call it **once per view**: a source hands each record
|
|
185
|
+
out once, so two views polling one source would split the rows between them
|
|
186
|
+
(a mapper, by contrast, is freely shared); `run.local` translates the acquiring host's paths onto
|
|
187
|
+
this machine, `run.config` is the frozen configuration. Runs are looked for
|
|
188
|
+
under `--run-root` (default `PLESTY_DATA_MOUNT`, else `runs`); `--mount`
|
|
189
|
+
and `--data-dir` override the share's two spellings for runs that did not
|
|
190
|
+
record them. `render` sets Qt's offscreen platform before the toolkit is
|
|
191
|
+
imported and stops by itself once every source is exhausted; neither
|
|
192
|
+
`--help` nor `resolve_run` needs the `gui` extra.
|
|
193
|
+
|
|
140
194
|
## Testing a monitor
|
|
141
195
|
|
|
142
196
|
`NullRenderer` records draw calls instead of drawing them, so the whole path
|
|
@@ -19,19 +19,19 @@
|
|
|
19
19
|
"version": "8.30.1"
|
|
20
20
|
},
|
|
21
21
|
"type": "secret_detection",
|
|
22
|
-
"start_time": "2026-08-
|
|
23
|
-
"end_time": "2026-08-
|
|
22
|
+
"start_time": "2026-08-17T12:45:23",
|
|
23
|
+
"end_time": "2026-08-17T12:45:24",
|
|
24
24
|
"status": "success",
|
|
25
25
|
"observability": {
|
|
26
26
|
"events": [
|
|
27
27
|
{
|
|
28
28
|
"event": "collect_secrets_analyzer_scan_metrics_from_pipeline",
|
|
29
|
-
"time_s": 0.
|
|
29
|
+
"time_s": 0.469797198,
|
|
30
30
|
"exit_code": 0,
|
|
31
31
|
"git_strategy": "FetchShallow",
|
|
32
|
-
"repo_size_kb":
|
|
32
|
+
"repo_size_kb": 685,
|
|
33
33
|
"commit_count": 1,
|
|
34
|
-
"bytes_scanned":
|
|
34
|
+
"bytes_scanned": 314,
|
|
35
35
|
"pipeline_type": "Tag"
|
|
36
36
|
},
|
|
37
37
|
{
|
|
@@ -15,7 +15,16 @@ from .types import (
|
|
|
15
15
|
cast_basic_type,
|
|
16
16
|
)
|
|
17
17
|
from .table import TableHeader, PlestyTable2D, PlestyTable3D
|
|
18
|
-
from .io import
|
|
18
|
+
from .io import (
|
|
19
|
+
ResultDocument,
|
|
20
|
+
append_record,
|
|
21
|
+
convert_to_hdf5,
|
|
22
|
+
load_document,
|
|
23
|
+
load_result,
|
|
24
|
+
read_records,
|
|
25
|
+
record_value,
|
|
26
|
+
save_result,
|
|
27
|
+
)
|
|
19
28
|
|
|
20
29
|
__all__ = [
|
|
21
30
|
"PlestyArray",
|
|
@@ -24,6 +33,9 @@ __all__ = [
|
|
|
24
33
|
"load_result",
|
|
25
34
|
"load_document",
|
|
26
35
|
"convert_to_hdf5",
|
|
36
|
+
"append_record",
|
|
37
|
+
"read_records",
|
|
38
|
+
"record_value",
|
|
27
39
|
"Units",
|
|
28
40
|
"TableHeader",
|
|
29
41
|
"PlestyTable2D",
|
|
@@ -21,7 +21,15 @@ reference link to the blob:
|
|
|
21
21
|
|
|
22
22
|
Writes are crash-safe: the blob is written first (atomically, via a temporary
|
|
23
23
|
sibling file and :func:`os.replace`), then the JSON document. The JSON
|
|
24
|
-
document is the commit record — a result exists once its
|
|
24
|
+
document is the commit record — a result exists once its document does.
|
|
25
|
+
|
|
26
|
+
Two layouts carry the documents. :func:`save_result` writes one ``.json``
|
|
27
|
+
file per result — the right shape for standalone results. An experiment run
|
|
28
|
+
writes all of its step documents to **one** append-only ``records.jsonl``
|
|
29
|
+
(:func:`append_record` / :func:`read_records`): opening, syncing, and later
|
|
30
|
+
listing thousands of small files on a network share is what made a run slow
|
|
31
|
+
to write and slow to follow, whereas one line appended and fsynced costs one
|
|
32
|
+
write and is read back by offset. Blobs still go to ``data/`` beside it.
|
|
25
33
|
"""
|
|
26
34
|
|
|
27
35
|
import json
|
|
@@ -160,32 +168,20 @@ def _sniff_suffix(payload: bytes) -> str:
|
|
|
160
168
|
return "bin"
|
|
161
169
|
|
|
162
170
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
) -> Path:
|
|
168
|
-
"""Persist a measurement result as raw blob + JSON metadata document.
|
|
171
|
+
#: Name of a run's single record file (one result document per line).
|
|
172
|
+
RECORDS_FILE = "records.jsonl"
|
|
173
|
+
#: Directory under a run holding the blobs its records reference.
|
|
174
|
+
BLOB_SUBDIR = "data"
|
|
169
175
|
|
|
170
|
-
Args:
|
|
171
|
-
result: The value to persist — a :class:`PlestyArray`, encoded
|
|
172
|
-
``bytes`` (e.g. an image), or any JSON-serializable value.
|
|
173
|
-
path_stem: Destination path without suffix; the JSON document is
|
|
174
|
-
written to ``<stem>.json`` and any blob next to it.
|
|
175
|
-
provenance: Optional context (step id, operation, parameters,
|
|
176
|
-
device identity) recorded verbatim in the document.
|
|
177
176
|
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
def _persist(result: Any, stem: Path, provenance: Optional[dict[str, Any]]) -> ResultDocument:
|
|
178
|
+
"""Write *result*'s blob (if it has one) next to *stem*; return its document.
|
|
180
179
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
JSON-serializable.
|
|
180
|
+
The document's ``blob`` is the bare file name; callers writing the
|
|
181
|
+
document elsewhere than beside the blob re-base it.
|
|
184
182
|
"""
|
|
185
|
-
stem = Path(path_stem)
|
|
186
|
-
stem.parent.mkdir(parents=True, exist_ok=True)
|
|
187
|
-
|
|
188
183
|
if isinstance(result, PlestyArray):
|
|
184
|
+
stem.parent.mkdir(parents=True, exist_ok=True)
|
|
189
185
|
blob_path = stem.with_suffix(".npy")
|
|
190
186
|
buffer = BytesIO()
|
|
191
187
|
np.save(buffer, np.asarray(result))
|
|
@@ -200,6 +196,7 @@ def save_result(
|
|
|
200
196
|
meta={k: getattr(result, k, None) for k in PlestyArray._META_KEYS},
|
|
201
197
|
)
|
|
202
198
|
elif isinstance(result, (bytes, bytearray)):
|
|
199
|
+
stem.parent.mkdir(parents=True, exist_ok=True)
|
|
203
200
|
payload = bytes(result)
|
|
204
201
|
suffix = _sniff_suffix(payload)
|
|
205
202
|
blob_path = stem.with_suffix(f".{suffix}")
|
|
@@ -209,12 +206,142 @@ def save_result(
|
|
|
209
206
|
)
|
|
210
207
|
else:
|
|
211
208
|
document = ResultDocument(type="value", provenance=provenance or {}, value=result)
|
|
209
|
+
return document
|
|
212
210
|
|
|
211
|
+
|
|
212
|
+
def save_result(
|
|
213
|
+
result: Any,
|
|
214
|
+
path_stem: str | Path,
|
|
215
|
+
provenance: Optional[dict[str, Any]] = None,
|
|
216
|
+
) -> Path:
|
|
217
|
+
"""Persist a measurement result as raw blob + JSON metadata document.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
result: The value to persist — a :class:`PlestyArray`, encoded
|
|
221
|
+
``bytes`` (e.g. an image), or any JSON-serializable value.
|
|
222
|
+
path_stem: Destination path without suffix; the JSON document is
|
|
223
|
+
written to ``<stem>.json`` and any blob next to it.
|
|
224
|
+
provenance: Optional context (step id, operation, parameters,
|
|
225
|
+
device identity) recorded verbatim in the document.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
The path of the JSON metadata document.
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
TypeError: If the result is neither a PlestyArray, bytes, nor
|
|
232
|
+
JSON-serializable.
|
|
233
|
+
"""
|
|
234
|
+
stem = Path(path_stem)
|
|
235
|
+
document = _persist(result, stem, provenance)
|
|
236
|
+
stem.parent.mkdir(parents=True, exist_ok=True)
|
|
213
237
|
target = stem.with_suffix(".json")
|
|
214
238
|
_atomic_write_bytes(target, json.dumps(document.to_dict(), default=str, indent=2).encode())
|
|
215
239
|
return target
|
|
216
240
|
|
|
217
241
|
|
|
242
|
+
def append_record(
|
|
243
|
+
run_dir: str | Path,
|
|
244
|
+
index: int,
|
|
245
|
+
result: Any,
|
|
246
|
+
provenance: Optional[dict[str, Any]] = None,
|
|
247
|
+
) -> dict[str, Any]:
|
|
248
|
+
"""Append one step result to the run's ``records.jsonl``.
|
|
249
|
+
|
|
250
|
+
A blob-backed result (array, bytes) is written to
|
|
251
|
+
``<run_dir>/data/step_<index>.<fmt>`` first, atomically; the document
|
|
252
|
+
line — the commit record — is appended and fsynced afterwards, so a
|
|
253
|
+
reader never sees a document whose blob is missing.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
run_dir: The run directory.
|
|
257
|
+
index: Position of the step in the plan; stamped onto the line
|
|
258
|
+
and used for the blob file name.
|
|
259
|
+
result: The value to persist (see :func:`save_result`).
|
|
260
|
+
provenance: Context recorded verbatim in the document.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
The line that was written, as a dictionary — the document fields
|
|
264
|
+
plus ``index``; ``blob`` (when present) is relative to *run_dir*.
|
|
265
|
+
|
|
266
|
+
Raises:
|
|
267
|
+
TypeError: If the result is neither a PlestyArray, bytes, nor
|
|
268
|
+
JSON-serializable.
|
|
269
|
+
"""
|
|
270
|
+
root = Path(run_dir)
|
|
271
|
+
document = _persist(result, root / BLOB_SUBDIR / f"step_{index:04d}", provenance)
|
|
272
|
+
line: dict[str, Any] = {"index": index, **document.to_dict()}
|
|
273
|
+
if document.blob is not None:
|
|
274
|
+
line["blob"] = f"{BLOB_SUBDIR}/{document.blob}"
|
|
275
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
276
|
+
with open(root / RECORDS_FILE, "a", encoding="utf-8") as stream:
|
|
277
|
+
stream.write(json.dumps(line, default=str) + "\n")
|
|
278
|
+
stream.flush()
|
|
279
|
+
os.fsync(stream.fileno())
|
|
280
|
+
return line
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def read_records(run_dir: str | Path, offset: int = 0) -> tuple[list[dict[str, Any]], int]:
|
|
284
|
+
"""Read the record lines appended to ``records.jsonl`` since *offset*.
|
|
285
|
+
|
|
286
|
+
Only complete lines are returned; a line still being written (no
|
|
287
|
+
trailing newline yet) is left for the next call, which is what makes
|
|
288
|
+
tailing a run in progress safe. Lines that fail to parse are skipped.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
run_dir: The run directory.
|
|
292
|
+
offset: Byte position to read from — pass back the returned offset
|
|
293
|
+
to read only what appeared since.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
The parsed lines (see :func:`append_record`) and the offset just
|
|
297
|
+
after the last complete line; ``([], offset)`` while the file does
|
|
298
|
+
not exist yet.
|
|
299
|
+
"""
|
|
300
|
+
path = Path(run_dir) / RECORDS_FILE
|
|
301
|
+
try:
|
|
302
|
+
with open(path, "rb") as stream:
|
|
303
|
+
stream.seek(offset)
|
|
304
|
+
chunk = stream.read()
|
|
305
|
+
except FileNotFoundError:
|
|
306
|
+
return [], offset
|
|
307
|
+
end = chunk.rfind(b"\n")
|
|
308
|
+
if end < 0:
|
|
309
|
+
return [], offset
|
|
310
|
+
lines: list[dict[str, Any]] = []
|
|
311
|
+
for raw in chunk[: end + 1].splitlines():
|
|
312
|
+
try:
|
|
313
|
+
lines.append(json.loads(raw))
|
|
314
|
+
except json.JSONDecodeError:
|
|
315
|
+
continue
|
|
316
|
+
return lines, offset + end + 1
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def record_value(line: dict[str, Any], run_dir: str | Path) -> Any:
|
|
320
|
+
"""Return the value a ``records.jsonl`` line stands for.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
line: A line as returned by :func:`read_records`.
|
|
324
|
+
run_dir: The run directory the line's ``blob`` is relative to.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
The inlined value, or the :class:`PlestyArray` / raw ``bytes`` loaded
|
|
328
|
+
from the referenced blob.
|
|
329
|
+
|
|
330
|
+
Raises:
|
|
331
|
+
ValueError: If the line declares an unknown result type.
|
|
332
|
+
"""
|
|
333
|
+
document = ResultDocument.from_dict(line)
|
|
334
|
+
if document.type == "value":
|
|
335
|
+
return document.value
|
|
336
|
+
blob = Path(run_dir) / (document.blob or "")
|
|
337
|
+
if document.type == "array":
|
|
338
|
+
meta = dict(document.meta or {})
|
|
339
|
+
if meta.get("range") is not None:
|
|
340
|
+
meta["range"] = tuple(meta["range"])
|
|
341
|
+
return PlestyArray(np.load(blob), **meta)
|
|
342
|
+
return blob.read_bytes()
|
|
343
|
+
|
|
344
|
+
|
|
218
345
|
def load_document(path: str | Path) -> ResultDocument:
|
|
219
346
|
"""Load only the metadata document of a persisted result — no blob I/O.
|
|
220
347
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
from .base_experiment import Experiment, InvalidOperationError, PlanMismatchError
|
|
9
9
|
from .journal import Journal
|
|
10
|
+
from .runs import Run, latest_run, list_runs, parse_run_id, run_id
|
|
10
11
|
from .schedule import Plan, Step
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
@@ -15,5 +16,10 @@ __all__ = [
|
|
|
15
16
|
"PlanMismatchError",
|
|
16
17
|
"Journal",
|
|
17
18
|
"Plan",
|
|
19
|
+
"Run",
|
|
18
20
|
"Step",
|
|
21
|
+
"latest_run",
|
|
22
|
+
"list_runs",
|
|
23
|
+
"parse_run_id",
|
|
24
|
+
"run_id",
|
|
19
25
|
]
|