labnotes 0.0.28__tar.gz → 0.0.29__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.
Files changed (89) hide show
  1. {labnotes-0.0.28 → labnotes-0.0.29}/PKG-INFO +1 -1
  2. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/context.md +2 -1
  3. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/models.py +1 -0
  4. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/describe_event.py +4 -0
  5. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/run_experiment.py +24 -6
  6. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tui/run_app.py +10 -1
  7. {labnotes-0.0.28 → labnotes-0.0.29}/.gitignore +0 -0
  8. {labnotes-0.0.28 → labnotes-0.0.29}/CHANGELOG.md +0 -0
  9. {labnotes-0.0.28 → labnotes-0.0.29}/MIGRATIONS.md +0 -0
  10. {labnotes-0.0.28 → labnotes-0.0.29}/README.md +0 -0
  11. {labnotes-0.0.28 → labnotes-0.0.29}/pyproject.toml +0 -0
  12. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/__init__.py +0 -0
  13. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/asset_url.py +0 -0
  14. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/cell_progress.py +0 -0
  15. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/cli.py +0 -0
  16. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/context_guide.py +0 -0
  17. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/count_runs.py +0 -0
  18. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/create_app.py +0 -0
  19. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/create_experiment.py +0 -0
  20. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/data_html.py +0 -0
  21. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/default_config_text.py +0 -0
  22. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/default_counts.py +0 -0
  23. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/drain_watch.py +0 -0
  24. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/experiment_notes.py +0 -0
  25. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/experiment_progress.py +0 -0
  26. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/experiment_runs.py +0 -0
  27. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/experiments.py +0 -0
  28. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/format_value.py +0 -0
  29. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/init.py +0 -0
  30. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/list_experiments.py +0 -0
  31. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/list_runs.py +0 -0
  32. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/load_config.py +0 -0
  33. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/load_experiment.py +0 -0
  34. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/notebook_kernel.py +0 -0
  35. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/output_html.py +0 -0
  36. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/parse_run_view.py +0 -0
  37. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/pivot_cells.py +0 -0
  38. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/print_rows.py +0 -0
  39. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/python_blocks.py +0 -0
  40. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/read_config.py +0 -0
  41. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/read_path.py +0 -0
  42. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/render_markdown.py +0 -0
  43. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/__init__.py +0 -0
  44. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/execute_run.py +0 -0
  45. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/lock_path.py +0 -0
  46. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/new_run_id.py +0 -0
  47. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/param_args.py +0 -0
  48. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/param_grid.py +0 -0
  49. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/plan_runs.py +0 -0
  50. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/read_experiment_config.py +0 -0
  51. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/run_folder.py +0 -0
  52. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/run_headless.py +0 -0
  53. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/shell_command.py +0 -0
  54. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/start_experiment.py +0 -0
  55. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/stop_then_kill.py +0 -0
  56. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/wait_for_group.py +0 -0
  57. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/runner/write_run_json.py +0 -0
  58. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/saved_outputs.py +0 -0
  59. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/serve_app.py +0 -0
  60. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/server.py +0 -0
  61. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/sort_href.py +0 -0
  62. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/sql_quote.py +0 -0
  63. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/static/labnotes.css +0 -0
  64. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/static/row-link.js +0 -0
  65. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/static/run-cell.js +0 -0
  66. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/static/view-toggle.js +0 -0
  67. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/stop_idle_kernels.py +0 -0
  68. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/summarize_config.py +0 -0
  69. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/__init__.py +0 -0
  70. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/experiment_rows.py +0 -0
  71. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/file_prefix.py +0 -0
  72. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/iso_time.py +0 -0
  73. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/list_experiment_runs.py +0 -0
  74. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/open_tables.py +0 -0
  75. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/path_glob.py +0 -0
  76. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/path_pattern.py +0 -0
  77. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/read_experiment.py +0 -0
  78. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/read_file.py +0 -0
  79. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/read_run.py +0 -0
  80. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tables/summarize_experiments.py +0 -0
  81. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/templates/base.html +0 -0
  82. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/templates/experiment.html +0 -0
  83. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/templates/index.html +0 -0
  84. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/templates/macros.html +0 -0
  85. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tui/__init__.py +0 -0
  86. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tui/dim.py +0 -0
  87. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tui/format_elapsed.py +0 -0
  88. {labnotes-0.0.28 → labnotes-0.0.29}/src/labnotes/tui/run_tui.py +0 -0
  89. {labnotes-0.0.28 → labnotes-0.0.29}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.28
3
+ Version: 0.0.29
4
4
  Summary: Browse experiment designs, notes, and run data from a folder.
5
5
  Project-URL: Repository, https://github.com/thekevinscott/labnotes
6
6
  Author-email: Kevin Scott <me@thekevinscott.com>
@@ -90,7 +90,8 @@ open when they finish. Runs not started yet show grayed as `pending`, and as
90
90
  `skipped` once stopped. Move the cursor to a pending run and press `n` to run it
91
91
  next. `d` deselects a pending run so it won't run; `d` again puts it back at
92
92
  the end of the queue. `/` filters rows by params text; Enter keeps the filter,
93
- Escape clears it. `D` deselects every pending run the filter shows. `+` and
93
+ Escape clears it. `D` deselects every pending run the filter shows. `k` stops the
94
+ running run under the cursor; `k` again kills it. `+` and
94
95
  `-` change how many run at once; lowering it lets running runs
95
96
  finish; `q` quits. Elsewhere, or with `--no-tui`, it prints a
96
97
  line per event instead.
@@ -14,6 +14,7 @@ class Controls:
14
14
  run_next: Callable[[str], None]
15
15
  set_concurrency: Callable[[int], None]
16
16
  toggle: Callable[[str], None]
17
+ kill: Callable[[str], None]
17
18
 
18
19
 
19
20
  Listen = Callable[[Controls], None]
@@ -18,6 +18,10 @@ def describe_event(event: dict[str, object]) -> str:
18
18
  return f"{event['run']} deselected."
19
19
  case "reselected":
20
20
  return f"{event['run']} back in the queue."
21
+ case "stopping run":
22
+ return f"Stopping {event['run']}."
23
+ case "killing run":
24
+ return f"Killing {event['run']}."
21
25
  case "concurrency":
22
26
  return f"{event['concurrency']} at a time."
23
27
  case "stopping":
@@ -48,25 +48,41 @@ async def run_experiment(
48
48
  plan.append((run, params))
49
49
  runs = [{"run": run, "params": params} for run, params in plan]
50
50
  report({"event": "planned", "runs": runs, "concurrency": concurrency})
51
- running: set[asyncio.subprocess.Process] = set()
51
+ running: dict[str, set[asyncio.subprocess.Process]] = {}
52
+ stopped: set[str] = set()
52
53
  results: list[dict[str, object]] = []
53
54
  stopping = asyncio.Event()
54
55
 
55
- def signal_runs(signal_number: int) -> None:
56
- for process in running:
56
+ def signal_run(run: str, signal_number: int) -> None:
57
+ for process in running[run]:
57
58
  with contextlib.suppress(ProcessLookupError):
58
59
  os.killpg(process.pid, signal_number)
59
60
 
61
+ def signal_runs(signal_number: int) -> None:
62
+ for run in running:
63
+ signal_run(run, signal_number)
64
+
60
65
  def interrupt() -> None:
61
66
  plan.clear()
62
67
  if not stopping.is_set():
63
68
  stopping.set()
64
69
  report({"event": "stopping"})
65
70
  signal_runs(signal.SIGTERM)
66
- elif running:
71
+ elif any(running.values()):
67
72
  report({"event": "killing"})
68
73
  signal_runs(signal.SIGKILL)
69
74
 
75
+ def kill(run: str) -> None:
76
+ if not running.get(run):
77
+ return
78
+ if run in stopped:
79
+ report({"event": "killing run", "run": run})
80
+ signal_run(run, signal.SIGKILL)
81
+ else:
82
+ stopped.add(run)
83
+ report({"event": "stopping run", "run": run})
84
+ signal_run(run, signal.SIGTERM)
85
+
70
86
  held: list[Entry] = []
71
87
 
72
88
  def find(entries: list[Entry], run: str) -> Entry | None:
@@ -125,13 +141,15 @@ async def run_experiment(
125
141
  folder = run_folder(config.root, config.runs, name, run)
126
142
  folder.mkdir(parents=True)
127
143
  report({"event": "started", "run": run, "params": params})
144
+ running[run] = set()
128
145
  result = await execute_run(
129
- config.root, folder, experiment.command, params, running, stopping
146
+ config.root, folder, experiment.command, params, running[run], stopping
130
147
  )
148
+ del running[run]
131
149
  report({"event": "ended", "run": run, **result})
132
150
  results.append(result)
133
151
 
134
- listen(Controls(interrupt, run_next, set_concurrency, toggle))
152
+ listen(Controls(interrupt, run_next, set_concurrency, toggle, kill))
135
153
  fill()
136
154
  await idle.wait()
137
155
  await asyncio.gather(*started)
@@ -21,6 +21,7 @@ class RunApp(App[None]):
21
21
  Binding("n", "run_next", "Run next"),
22
22
  Binding("d", "toggle_run", "Deselect"),
23
23
  Binding("D", "deselect_shown", "Deselect shown"),
24
+ Binding("k", "kill_run", "Stop run"),
24
25
  Binding("slash", "filter", "Filter", key_display="/"),
25
26
  Binding("escape", "clear_filter", "Clear filter"),
26
27
  Binding("plus,equals_sign", "concurrency(1)", "More at once", key_display="+"),
@@ -34,7 +35,11 @@ class RunApp(App[None]):
34
35
  self.title = title
35
36
  self.results: list[dict[str, object]] = []
36
37
  self.controls = Controls(
37
- lambda: None, lambda run: None, lambda count: None, lambda run: None
38
+ lambda: None,
39
+ lambda run: None,
40
+ lambda count: None,
41
+ lambda run: None,
42
+ lambda run: None,
38
43
  )
39
44
  self.concurrency = 1
40
45
  self.done = False
@@ -178,6 +183,10 @@ class RunApp(App[None]):
178
183
  if (run := self.cursor_run()) is not None:
179
184
  self.controls.toggle(run)
180
185
 
186
+ def action_kill_run(self) -> None:
187
+ if (run := self.cursor_run()) is not None:
188
+ self.controls.kill(run)
189
+
181
190
  def action_deselect_shown(self) -> None:
182
191
  shown = [str(key.value) for key in self.query_one(DataTable).rows]
183
192
  for run in shown:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes