dsh-labscript 0.2.3 → 0.2.4

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.4
4
+
5
+ - Fix: labs_data no longer answers h5: 0 for a run folder that does not exist. A missing
6
+ folder raises RUN_FOLDER_MISSING with the raw and mapped paths plus a hint: runmanager's
7
+ shot output folder is the folder the NEXT run will use and only exists after engage, so
8
+ historical runs must be passed explicitly. An existing folder with no shots reports
9
+ empty: true instead.
10
+ - Fix: labs_status with keys=[...] drops keys that are not panel globals instead of
11
+ returning null, so the rendered output shows <missing>. UI group names (for example
12
+ Select_Sequence) are not globals: read their leaf keys or a run's h5.
13
+ - Descriptions: labs_status says the shot output folder is the NEXT run folder and that
14
+ keys are leaf global names; labs_data explains the run_folder default and the error.
15
+ - Tests: missing run folder, empty run folder, missing keys dropped.
16
+
3
17
  ## 0.2.3
4
18
 
5
19
  - Fix: labs_status now prints panel values. keys=[...] and raw=true were accepted but the
package/README.md CHANGED
@@ -126,7 +126,7 @@ is not a physics simulation.
126
126
 
127
127
  npm install # only runtime dependency: yaml
128
128
  npm test # node --test, 36 tests (fake bridge, no python needed)
129
- python python/tests/run_tests.py # 15 tests (needs numpy + h5py)
129
+ python python/tests/run_tests.py # 18 tests (needs numpy + h5py)
130
130
  LABSCRIPT_PYTHON=/path/to/python3 node scripts/demo.mjs # end-to-end mock demo
131
131
 
132
132
  A fresh clone must run `npm install` before `npm test`; the CI workflow does this.
@@ -157,7 +157,9 @@ A fresh clone must run `npm install` before `npm test`; the CI workflow does thi
157
157
  partial). compiled_not_executed means shots exist but show no acquisition data yet;
158
158
  BLACS may be paused or not executing the queue.
159
159
  - Real runmanager engage does not return a run folder; the bridge captures the
160
- output folder before engage and reports it as the run folder.
160
+ output folder before engage and reports it as the run folder. That shot output
161
+ folder is the NEXT run's folder and does not exist until engage, so labs_data
162
+ errors (RUN_FOLDER_MISSING) instead of reporting h5: 0 for a path that is not there.
161
163
  - The panel is snapshotted before every write; labs_restore restores only the keys the
162
164
  write touched (full=true restores the whole panel), so unrelated concurrent edits survive.
163
165
  - Actions are logged to events.jsonl next to the snapshots.
package/lib/tools.js CHANGED
@@ -129,7 +129,7 @@ export function registerTools(ctx, runtime) {
129
129
 
130
130
  ctx.tools.register({
131
131
  name: 'labs_status',
132
- description: 'Read the labscript runmanager panel: loaded labscript, globals (evaluated; raw expressions with raw=true), n_shots, error_in_globals, run_shots, shot output folder, pending run progress, and the panel snapshot directory. Read-only; call this before any write. Pass keys=[...] to print the selected panel values (240 chars each; raw=true prints the raw expression). Paths in the output are already mapped for this machine through pathMap; do not map them again.',
132
+ description: 'Read the labscript runmanager panel: loaded labscript, globals (evaluated; raw expressions with raw=true), n_shots, error_in_globals, run_shots, shot output folder, pending run progress, and the panel snapshot directory. Read-only; call this before any write. Pass keys=[...] (leaf global names; UI group names are not globals) to print the selected panel values (240 chars each; raw=true prints the raw expression), missing keys are shown as <missing>. The shot output folder is the folder the NEXT run will use; it does not exist until engage. Paths in the output are already mapped for this machine through pathMap; do not map them again.',
133
133
  parameters: {
134
134
  type: 'object',
135
135
  properties: {
@@ -339,11 +339,11 @@ export function registerTools(ctx, runtime) {
339
339
 
340
340
  ctx.tools.register({
341
341
  name: 'labs_data',
342
- description: 'Read a run folder HDF5: globals of the first shot (execution facts) and, with scan: a per-shot table of scan values and numeric results. Read-only.',
342
+ description: 'Read a run folder HDF5: globals of the first shot (execution facts) and, with scan: a per-shot table of scan values and numeric results. Read-only. run_folder defaults to the run this DSH session submitted; for a historical run pass its folder explicitly. A missing folder is an error: runmanager\'s shot output is the NEXT run folder and only exists after engage.',
343
343
  parameters: {
344
344
  type: 'object',
345
345
  properties: {
346
- run_folder: { type: 'string', description: 'Run folder; defaults to the pending/last submitted run' },
346
+ run_folder: { type: 'string', description: 'Run folder; defaults to the run submitted from this DSH session (pass a historical run folder explicitly)' },
347
347
  scan: { type: 'string', description: 'Panel global name used as the scan axis; adds a per-shot table' },
348
348
  globals: { type: 'boolean', description: 'Include the first shot globals (default true)' },
349
349
  results: { type: 'boolean', description: 'Include the results layout' },
@@ -353,7 +353,7 @@ export function registerTools(ctx, runtime) {
353
353
  isConcurrencySafe: () => true,
354
354
  output: looseOutput((_args, value) => text(lines([
355
355
  'run: ' + (value.mapped_run_folder || value.run_folder),
356
- 'h5: ' + value.n_h5,
356
+ 'h5: ' + value.n_h5 + (value.empty ? ' (folder exists, no shots yet)' : ''),
357
357
  value.scan_table ? 'scan ' + value.scan_table.scan + ': ' + value.scan_table.n_points + ' shots' : '',
358
358
  value.globals ? 'globals: ' + Object.keys(value.globals).length + ' keys' : '',
359
359
  ]))),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-labscript",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Control labscript-suite experiments from DeepSeek Harness: runmanager globals and scans, submits and waits, YAML fixed-operation protocols with postprocess write-back, panel snapshots, a built-in mock backend and a read-only doctor.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
package/python/bridge.py CHANGED
@@ -922,8 +922,8 @@ def cmd_status(config, args):
922
922
  values = backend.get_globals(raw=False)
923
923
  raw_values = backend.get_globals(raw=True)
924
924
  if keys:
925
- values = dict((k, values.get(k)) for k in keys)
926
- raw_values = dict((k, raw_values.get(k)) for k in keys)
925
+ values = dict((k, values[k]) for k in keys if k in values)
926
+ raw_values = dict((k, raw_values[k]) for k in keys if k in raw_values)
927
927
  out = {
928
928
  "backend": config.get("backend"),
929
929
  "version": backend.version() if hasattr(backend, "version") else None,
@@ -1182,9 +1182,16 @@ def cmd_data(config, args):
1182
1182
  if not run_folder:
1183
1183
  raise BridgeError("BAD_ARGS", "run_folder is required")
1184
1184
  folder = map_path(config, run_folder)
1185
+ if not os.path.isdir(folder):
1186
+ raise BridgeError("RUN_FOLDER_MISSING", "run folder does not exist: " + str(folder), {
1187
+ "run_folder": run_folder,
1188
+ "mapped_run_folder": folder,
1189
+ "hint": "runmanager's shot output folder is the folder the NEXT run will use; it does not exist until engage. Pass a historical run folder explicitly.",
1190
+ })
1185
1191
  shots = sorted(glob.glob(os.path.join(folder, "**", "*.h5"), recursive=True))
1186
1192
  out = {"run_folder": run_folder, "mapped_run_folder": folder, "n_h5": len(shots)}
1187
1193
  if not shots:
1194
+ out["empty"] = True
1188
1195
  return out
1189
1196
  scan = args.get("scan")
1190
1197
  with h5py.File(shots[0], "r", locking=False) as handle:
@@ -346,6 +346,48 @@ def test_progress_reports_mapped_run_folder():
346
346
  assert out["mapped_run_folder"] == "\\\\lab-pc\\Experiments\\nope", out
347
347
 
348
348
 
349
+ def test_data_missing_run_folder():
350
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-data-missing-")
351
+ try:
352
+ cfg = make_config(tmp)
353
+ missing = os.path.join(tmp, "no_such_run")
354
+ try:
355
+ bridge.cmd_data(cfg, {"run_folder": missing})
356
+ except bridge.BridgeError as exc:
357
+ assert exc.code == "RUN_FOLDER_MISSING", exc.message
358
+ assert exc.detail["mapped_run_folder"] == missing, exc.detail
359
+ assert "next" in exc.detail["hint"].lower(), exc.detail
360
+ else:
361
+ raise AssertionError("expected RUN_FOLDER_MISSING")
362
+ finally:
363
+ shutil.rmtree(tmp, ignore_errors=True)
364
+
365
+
366
+ def test_data_empty_run_folder():
367
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-data-empty-")
368
+ try:
369
+ cfg = make_config(tmp)
370
+ run_dir = os.path.join(tmp, "empty_run")
371
+ os.makedirs(run_dir, exist_ok=True)
372
+ out = bridge.cmd_data(cfg, {"run_folder": run_dir})
373
+ assert out["n_h5"] == 0, out
374
+ assert out.get("empty") is True, out
375
+ finally:
376
+ shutil.rmtree(tmp, ignore_errors=True)
377
+
378
+
379
+ def test_status_drops_missing_keys():
380
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-status-missing-")
381
+ try:
382
+ cfg = make_config(tmp)
383
+ out = bridge.cmd_status(cfg, {"keys": ["amplitude", "Select_Sequence"], "raw": True})
384
+ assert "amplitude" in out["globals"], out["globals"]
385
+ assert "Select_Sequence" not in out["globals"], out["globals"]
386
+ assert "Select_Sequence" not in out["raw_globals"], out["raw_globals"]
387
+ finally:
388
+ shutil.rmtree(tmp, ignore_errors=True)
389
+
390
+
349
391
  def main():
350
392
  tests = [value for name, value in sorted(globals().items()) if name.startswith("test_")]
351
393
  failures = 0