labnotes 0.0.22__tar.gz → 0.0.24__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.22 → labnotes-0.0.24}/PKG-INFO +1 -1
  2. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/context.md +4 -2
  3. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/describe_event.py +2 -1
  4. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/run_experiment.py +19 -8
  5. labnotes-0.0.24/src/labnotes/tui/dim.py +5 -0
  6. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tui/run_app.py +21 -3
  7. {labnotes-0.0.22 → labnotes-0.0.24}/.gitignore +0 -0
  8. {labnotes-0.0.22 → labnotes-0.0.24}/CHANGELOG.md +0 -0
  9. {labnotes-0.0.22 → labnotes-0.0.24}/MIGRATIONS.md +0 -0
  10. {labnotes-0.0.22 → labnotes-0.0.24}/README.md +0 -0
  11. {labnotes-0.0.22 → labnotes-0.0.24}/pyproject.toml +0 -0
  12. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/__init__.py +0 -0
  13. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/asset_url.py +0 -0
  14. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/cell_progress.py +0 -0
  15. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/cli.py +0 -0
  16. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/context_guide.py +0 -0
  17. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/count_runs.py +0 -0
  18. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/create_app.py +0 -0
  19. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/create_experiment.py +0 -0
  20. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/data_html.py +0 -0
  21. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/default_config_text.py +0 -0
  22. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/default_counts.py +0 -0
  23. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/drain_watch.py +0 -0
  24. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/experiment_notes.py +0 -0
  25. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/experiment_progress.py +0 -0
  26. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/experiment_runs.py +0 -0
  27. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/experiments.py +0 -0
  28. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/format_value.py +0 -0
  29. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/init.py +0 -0
  30. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/list_experiments.py +0 -0
  31. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/list_runs.py +0 -0
  32. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/load_config.py +0 -0
  33. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/load_experiment.py +0 -0
  34. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/models.py +0 -0
  35. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/notebook_kernel.py +0 -0
  36. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/output_html.py +0 -0
  37. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/parse_run_view.py +0 -0
  38. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/pivot_cells.py +0 -0
  39. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/print_rows.py +0 -0
  40. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/python_blocks.py +0 -0
  41. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/read_config.py +0 -0
  42. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/read_path.py +0 -0
  43. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/render_markdown.py +0 -0
  44. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/__init__.py +0 -0
  45. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/execute_run.py +0 -0
  46. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/lock_path.py +0 -0
  47. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/new_run_id.py +0 -0
  48. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/param_args.py +0 -0
  49. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/param_grid.py +0 -0
  50. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/plan_runs.py +0 -0
  51. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/read_experiment_config.py +0 -0
  52. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/run_folder.py +0 -0
  53. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/run_headless.py +0 -0
  54. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/shell_command.py +0 -0
  55. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/start_experiment.py +0 -0
  56. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/stop_then_kill.py +0 -0
  57. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/wait_for_group.py +0 -0
  58. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/runner/write_run_json.py +0 -0
  59. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/saved_outputs.py +0 -0
  60. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/serve_app.py +0 -0
  61. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/server.py +0 -0
  62. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/sort_href.py +0 -0
  63. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/sql_quote.py +0 -0
  64. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/static/labnotes.css +0 -0
  65. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/static/row-link.js +0 -0
  66. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/static/run-cell.js +0 -0
  67. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/static/view-toggle.js +0 -0
  68. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/stop_idle_kernels.py +0 -0
  69. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/summarize_config.py +0 -0
  70. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/__init__.py +0 -0
  71. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/experiment_rows.py +0 -0
  72. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/file_prefix.py +0 -0
  73. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/iso_time.py +0 -0
  74. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/list_experiment_runs.py +0 -0
  75. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/open_tables.py +0 -0
  76. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/path_glob.py +0 -0
  77. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/path_pattern.py +0 -0
  78. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/read_experiment.py +0 -0
  79. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/read_file.py +0 -0
  80. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/read_run.py +0 -0
  81. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tables/summarize_experiments.py +0 -0
  82. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/templates/base.html +0 -0
  83. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/templates/experiment.html +0 -0
  84. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/templates/index.html +0 -0
  85. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/templates/macros.html +0 -0
  86. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tui/__init__.py +0 -0
  87. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tui/format_elapsed.py +0 -0
  88. {labnotes-0.0.22 → labnotes-0.0.24}/src/labnotes/tui/run_tui.py +0 -0
  89. {labnotes-0.0.22 → labnotes-0.0.24}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.22
3
+ Version: 0.0.24
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>
@@ -86,14 +86,16 @@ A script run by labnotes should:
86
86
  Runs can take a long time. Start `run` in the background, then check on it with `labnotes experiments foo runs`; a run with no `ended` is still going.
87
87
 
88
88
  In a terminal, `run` shows a live table of runs (status and time) and stays
89
- open when they finish; `q` quits. Elsewhere, or with `--no-tui`, it prints a
89
+ open when they finish. Runs not started yet show grayed as `pending`, and as
90
+ `skipped` once stopped; `q` quits. Elsewhere, or with `--no-tui`, it prints a
90
91
  line per event instead.
91
92
 
92
93
  - `--concurrency N` runs N at a time (default 1).
93
94
  - `-e PATH` reads another file instead of `experiment.toml`.
94
95
  - Ctrl-C stops the running scripts and records their end. A second Ctrl-C
95
96
  kills them. Closing the terminal or sending SIGTERM stops them the same way,
96
- then kills any still going 5 seconds later.
97
+ then kills any still going 5 seconds later. Once the runs are done, either
98
+ one closes the live view.
97
99
  - A second `run` of the same experiment fails while the first holds the lock.
98
100
  - It exits 1 if any run failed.
99
101
 
@@ -4,7 +4,8 @@ from labnotes.runner.param_args import param_args
4
4
  def describe_event(event: dict[str, object]) -> str:
5
5
  match event["event"]:
6
6
  case "planned":
7
- return f"{event['runs']} runs to go, {event['concurrency']} at a time."
7
+ runs = len(event["runs"]) # type: ignore[arg-type]
8
+ return f"{runs} runs to go, {event['concurrency']} at a time."
8
9
  case "started":
9
10
  args = " ".join(param_args(event["params"])) # type: ignore[arg-type]
10
11
  return f"{event['run']} started {args}".rstrip()
@@ -22,10 +22,24 @@ async def run_experiment(
22
22
  report: Report,
23
23
  listen: Listen,
24
24
  ) -> list[dict[str, object]]:
25
- runs = await list_experiment_runs(open_tables(config), name, experiment.counts)
26
- finished = [json.loads(str(run["params"])) for run in runs if run["counted"]]
27
- plan = plan_runs(param_grid(experiment.params), experiment.repeats, finished)
28
- report({"event": "planned", "runs": len(plan), "concurrency": concurrency})
25
+ existing = await list_experiment_runs(open_tables(config), name, experiment.counts)
26
+ finished = [json.loads(str(run["params"])) for run in existing if run["counted"]]
27
+ planned: set[str] = set()
28
+
29
+ def taken(run: str) -> bool:
30
+ return (
31
+ run in planned or run_folder(config.root, config.runs, name, run).exists()
32
+ )
33
+
34
+ plan: list[tuple[str, dict[str, object]]] = []
35
+ for params in plan_runs(
36
+ param_grid(experiment.params), experiment.repeats, finished
37
+ ):
38
+ run = new_run_id(taken)
39
+ planned.add(run)
40
+ plan.append((run, params))
41
+ runs = [{"run": run, "params": params} for run, params in plan]
42
+ report({"event": "planned", "runs": runs, "concurrency": concurrency})
29
43
  running: set[asyncio.subprocess.Process] = set()
30
44
  results: list[dict[str, object]] = []
31
45
  stopping = asyncio.Event()
@@ -47,10 +61,7 @@ async def run_experiment(
47
61
 
48
62
  async def work() -> None:
49
63
  while plan:
50
- params = plan.pop(0)
51
- run = new_run_id(
52
- lambda id_: run_folder(config.root, config.runs, name, id_).exists()
53
- )
64
+ run, params = plan.pop(0)
54
65
  folder = run_folder(config.root, config.runs, name, run)
55
66
  folder.mkdir(parents=True)
56
67
  report({"event": "started", "run": run, "params": params})
@@ -0,0 +1,5 @@
1
+ from rich.text import Text
2
+
3
+
4
+ def dim(cell: str) -> Text:
5
+ return Text(cell, style="dim")
@@ -10,8 +10,11 @@ from labnotes.models import Start
10
10
  from labnotes.runner.describe_event import describe_event
11
11
  from labnotes.runner.stop_then_kill import stop_then_kill
12
12
  from labnotes.runner.param_args import param_args
13
+ from labnotes.tui.dim import dim
13
14
  from labnotes.tui.format_elapsed import format_elapsed
14
15
 
16
+ COLUMNS = ("run", "params", "status", "time")
17
+
15
18
 
16
19
  class RunApp(App[None]):
17
20
  BINDINGS = [
@@ -27,6 +30,7 @@ class RunApp(App[None]):
27
30
  self.interrupt: Callable[[], None] = lambda: None
28
31
  self.done = False
29
32
  self.started: dict[str, float] = {}
33
+ self.pending: set[str] = set()
30
34
 
31
35
  def compose(self) -> ComposeResult:
32
36
  yield Header()
@@ -36,7 +40,7 @@ class RunApp(App[None]):
36
40
 
37
41
  def on_mount(self) -> None:
38
42
  table = self.query_one(DataTable)
39
- for column in ("run", "params", "status", "time"):
43
+ for column in COLUMNS:
40
44
  table.add_column(column, key=column)
41
45
 
42
46
  def on_ready(self) -> None:
@@ -52,21 +56,35 @@ class RunApp(App[None]):
52
56
 
53
57
  def listen(self, interrupt: Callable[[], None]) -> None:
54
58
  self.interrupt = interrupt
55
- stop_then_kill(interrupt, signal.SIGTERM, signal.SIGHUP)
59
+ stop_then_kill(self.action_interrupt, signal.SIGTERM, signal.SIGHUP)
56
60
 
57
61
  def show_event(self, event: dict[str, object]) -> None:
58
62
  table = self.query_one(DataTable)
59
63
  run = str(event.get("run"))
60
64
  match event["event"]:
65
+ case "planned":
66
+ for planned in event["runs"]: # type: ignore[attr-defined]
67
+ args = " ".join(param_args(planned["params"]))
68
+ cells = (planned["run"], args, "pending", "")
69
+ table.add_row(*map(dim, cells), key=planned["run"])
70
+ self.pending.add(planned["run"])
71
+ self.show(describe_event(event))
61
72
  case "started":
73
+ self.pending.discard(run)
62
74
  self.started[run] = time.monotonic()
63
75
  args = " ".join(param_args(event["params"])) # type: ignore[arg-type]
64
- table.add_row(run, args, "running", "0s", key=run)
76
+ cells = (run, args, "running", format_elapsed(0))
77
+ for column, cell in zip(COLUMNS, cells):
78
+ table.update_cell(run, column, cell, update_width=True)
65
79
  case "ended":
66
80
  outcome = event["error"] or f"exited {event['exit_code']}"
67
81
  table.update_cell(run, "status", outcome, update_width=True)
68
82
  self.tick()
69
83
  del self.started[run]
84
+ case "stopping":
85
+ for pending in self.pending:
86
+ table.update_cell(pending, "status", dim("skipped"))
87
+ self.show(describe_event(event))
70
88
  case _:
71
89
  self.show(describe_event(event))
72
90
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes