labnotes 0.0.27__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.27 → labnotes-0.0.29}/PKG-INFO +1 -1
  2. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/context.md +3 -1
  3. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/models.py +1 -0
  4. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/describe_event.py +4 -0
  5. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/run_experiment.py +24 -6
  6. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tui/run_app.py +17 -1
  7. {labnotes-0.0.27 → labnotes-0.0.29}/.gitignore +0 -0
  8. {labnotes-0.0.27 → labnotes-0.0.29}/CHANGELOG.md +0 -0
  9. {labnotes-0.0.27 → labnotes-0.0.29}/MIGRATIONS.md +0 -0
  10. {labnotes-0.0.27 → labnotes-0.0.29}/README.md +0 -0
  11. {labnotes-0.0.27 → labnotes-0.0.29}/pyproject.toml +0 -0
  12. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/__init__.py +0 -0
  13. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/asset_url.py +0 -0
  14. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/cell_progress.py +0 -0
  15. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/cli.py +0 -0
  16. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/context_guide.py +0 -0
  17. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/count_runs.py +0 -0
  18. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/create_app.py +0 -0
  19. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/create_experiment.py +0 -0
  20. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/data_html.py +0 -0
  21. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/default_config_text.py +0 -0
  22. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/default_counts.py +0 -0
  23. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/drain_watch.py +0 -0
  24. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/experiment_notes.py +0 -0
  25. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/experiment_progress.py +0 -0
  26. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/experiment_runs.py +0 -0
  27. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/experiments.py +0 -0
  28. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/format_value.py +0 -0
  29. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/init.py +0 -0
  30. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/list_experiments.py +0 -0
  31. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/list_runs.py +0 -0
  32. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/load_config.py +0 -0
  33. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/load_experiment.py +0 -0
  34. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/notebook_kernel.py +0 -0
  35. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/output_html.py +0 -0
  36. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/parse_run_view.py +0 -0
  37. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/pivot_cells.py +0 -0
  38. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/print_rows.py +0 -0
  39. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/python_blocks.py +0 -0
  40. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/read_config.py +0 -0
  41. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/read_path.py +0 -0
  42. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/render_markdown.py +0 -0
  43. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/__init__.py +0 -0
  44. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/execute_run.py +0 -0
  45. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/lock_path.py +0 -0
  46. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/new_run_id.py +0 -0
  47. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/param_args.py +0 -0
  48. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/param_grid.py +0 -0
  49. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/plan_runs.py +0 -0
  50. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/read_experiment_config.py +0 -0
  51. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/run_folder.py +0 -0
  52. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/run_headless.py +0 -0
  53. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/shell_command.py +0 -0
  54. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/start_experiment.py +0 -0
  55. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/stop_then_kill.py +0 -0
  56. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/wait_for_group.py +0 -0
  57. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/runner/write_run_json.py +0 -0
  58. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/saved_outputs.py +0 -0
  59. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/serve_app.py +0 -0
  60. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/server.py +0 -0
  61. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/sort_href.py +0 -0
  62. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/sql_quote.py +0 -0
  63. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/static/labnotes.css +0 -0
  64. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/static/row-link.js +0 -0
  65. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/static/run-cell.js +0 -0
  66. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/static/view-toggle.js +0 -0
  67. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/stop_idle_kernels.py +0 -0
  68. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/summarize_config.py +0 -0
  69. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/__init__.py +0 -0
  70. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/experiment_rows.py +0 -0
  71. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/file_prefix.py +0 -0
  72. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/iso_time.py +0 -0
  73. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/list_experiment_runs.py +0 -0
  74. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/open_tables.py +0 -0
  75. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/path_glob.py +0 -0
  76. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/path_pattern.py +0 -0
  77. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/read_experiment.py +0 -0
  78. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/read_file.py +0 -0
  79. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/read_run.py +0 -0
  80. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tables/summarize_experiments.py +0 -0
  81. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/templates/base.html +0 -0
  82. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/templates/experiment.html +0 -0
  83. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/templates/index.html +0 -0
  84. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/templates/macros.html +0 -0
  85. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tui/__init__.py +0 -0
  86. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tui/dim.py +0 -0
  87. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tui/format_elapsed.py +0 -0
  88. {labnotes-0.0.27 → labnotes-0.0.29}/src/labnotes/tui/run_tui.py +0 -0
  89. {labnotes-0.0.27 → 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.27
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,9 @@ 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. `+` and `-` change how many run at once; lowering it lets running runs
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
95
+ `-` change how many run at once; lowering it lets running runs
94
96
  finish; `q` quits. Elsewhere, or with `--no-tui`, it prints a
95
97
  line per event instead.
96
98
 
@@ -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)
@@ -20,6 +20,8 @@ class RunApp(App[None]):
20
20
  Binding("ctrl+c", "interrupt", "Stop runs"),
21
21
  Binding("n", "run_next", "Run next"),
22
22
  Binding("d", "toggle_run", "Deselect"),
23
+ Binding("D", "deselect_shown", "Deselect shown"),
24
+ Binding("k", "kill_run", "Stop run"),
23
25
  Binding("slash", "filter", "Filter", key_display="/"),
24
26
  Binding("escape", "clear_filter", "Clear filter"),
25
27
  Binding("plus,equals_sign", "concurrency(1)", "More at once", key_display="+"),
@@ -33,7 +35,11 @@ class RunApp(App[None]):
33
35
  self.title = title
34
36
  self.results: list[dict[str, object]] = []
35
37
  self.controls = Controls(
36
- 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,
37
43
  )
38
44
  self.concurrency = 1
39
45
  self.done = False
@@ -177,6 +183,16 @@ class RunApp(App[None]):
177
183
  if (run := self.cursor_run()) is not None:
178
184
  self.controls.toggle(run)
179
185
 
186
+ def action_kill_run(self) -> None:
187
+ if (run := self.cursor_run()) is not None:
188
+ self.controls.kill(run)
189
+
190
+ def action_deselect_shown(self) -> None:
191
+ shown = [str(key.value) for key in self.query_one(DataTable).rows]
192
+ for run in shown:
193
+ if run in self.pending:
194
+ self.controls.toggle(run)
195
+
180
196
  def action_concurrency(self, change: int) -> None:
181
197
  self.controls.set_concurrency(self.concurrency + change)
182
198
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes