labnotes 0.0.25__tar.gz → 0.0.26__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.
- {labnotes-0.0.25 → labnotes-0.0.26}/PKG-INFO +1 -1
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/context.md +4 -2
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/models.py +2 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/describe_event.py +6 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/run_experiment.py +67 -19
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tui/run_app.py +50 -9
- {labnotes-0.0.25 → labnotes-0.0.26}/.gitignore +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/CHANGELOG.md +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/MIGRATIONS.md +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/README.md +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/pyproject.toml +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/cell_progress.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/experiment_progress.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/init.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/pivot_cells.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/lock_path.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/start_experiment.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/wait_for_group.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/server.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/stop_idle_kernels.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/templates/macros.html +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.25 → labnotes-0.0.26}/uv.lock +0 -0
|
@@ -88,10 +88,12 @@ Runs can take a long time. Start `run` in the background, then check on it with
|
|
|
88
88
|
In a terminal, `run` shows a live table of runs (status and time) and stays
|
|
89
89
|
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
|
-
next
|
|
91
|
+
next. `d` deselects a pending run so it won't run; `d` again puts it back at
|
|
92
|
+
the end of the queue. `+` and `-` change how many run at once; lowering it lets running runs
|
|
93
|
+
finish; `q` quits. Elsewhere, or with `--no-tui`, it prints a
|
|
92
94
|
line per event instead.
|
|
93
95
|
|
|
94
|
-
- `--concurrency N` runs N at a time (default 1).
|
|
96
|
+
- `--concurrency N` runs N at a time to start with (default 1).
|
|
95
97
|
- `-e PATH` reads another file instead of `experiment.toml`.
|
|
96
98
|
- Ctrl-C stops the running scripts and records their end. A second Ctrl-C
|
|
97
99
|
kills them. Closing the terminal or sending SIGTERM stops them the same way,
|
|
@@ -14,6 +14,12 @@ def describe_event(event: dict[str, object]) -> str:
|
|
|
14
14
|
return f"{event['run']} {outcome}"
|
|
15
15
|
case "queued":
|
|
16
16
|
return f"{event['run']} runs next."
|
|
17
|
+
case "deselected":
|
|
18
|
+
return f"{event['run']} deselected."
|
|
19
|
+
case "reselected":
|
|
20
|
+
return f"{event['run']} back in the queue."
|
|
21
|
+
case "concurrency":
|
|
22
|
+
return f"{event['concurrency']} at a time."
|
|
17
23
|
case "stopping":
|
|
18
24
|
return "Stopping runs. Ctrl-C again to kill them."
|
|
19
25
|
case _:
|
|
@@ -19,6 +19,8 @@ from labnotes.runner.run_folder import run_folder
|
|
|
19
19
|
from labnotes.tables.list_experiment_runs import list_experiment_runs
|
|
20
20
|
from labnotes.tables.open_tables import open_tables
|
|
21
21
|
|
|
22
|
+
Entry = tuple[str, dict[str, object]]
|
|
23
|
+
|
|
22
24
|
|
|
23
25
|
async def run_experiment(
|
|
24
26
|
config: LabnotesConfig,
|
|
@@ -37,7 +39,7 @@ async def run_experiment(
|
|
|
37
39
|
run in planned or run_folder(config.root, config.runs, name, run).exists()
|
|
38
40
|
)
|
|
39
41
|
|
|
40
|
-
plan: list[
|
|
42
|
+
plan: list[Entry] = []
|
|
41
43
|
for params in plan_runs(
|
|
42
44
|
param_grid(experiment.params), experiment.repeats, finished
|
|
43
45
|
):
|
|
@@ -65,26 +67,72 @@ async def run_experiment(
|
|
|
65
67
|
report({"event": "killing"})
|
|
66
68
|
signal_runs(signal.SIGKILL)
|
|
67
69
|
|
|
70
|
+
held: list[Entry] = []
|
|
71
|
+
|
|
72
|
+
def find(entries: list[Entry], run: str) -> Entry | None:
|
|
73
|
+
return next((entry for entry in entries if entry[0] == run), None)
|
|
74
|
+
|
|
75
|
+
def arrange(event: str, run: str) -> None:
|
|
76
|
+
pending = [id_ for id_, _ in plan]
|
|
77
|
+
deselected = [id_ for id_, _ in held]
|
|
78
|
+
report({"event": event, "run": run, "pending": pending, "held": deselected})
|
|
79
|
+
|
|
68
80
|
def run_next(run: str) -> None:
|
|
69
|
-
entry =
|
|
81
|
+
entry = find(plan, run)
|
|
70
82
|
if entry is not None:
|
|
71
83
|
plan.remove(entry)
|
|
72
84
|
plan.insert(0, entry)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
arrange("queued", run)
|
|
86
|
+
|
|
87
|
+
def toggle(run: str) -> None:
|
|
88
|
+
if stopping.is_set():
|
|
89
|
+
return
|
|
90
|
+
if (entry := find(plan, run)) is not None:
|
|
91
|
+
plan.remove(entry)
|
|
92
|
+
held.append(entry)
|
|
93
|
+
arrange("deselected", run)
|
|
94
|
+
elif (entry := find(held, run)) is not None:
|
|
95
|
+
held.remove(entry)
|
|
96
|
+
plan.append(entry)
|
|
97
|
+
arrange("reselected", run)
|
|
98
|
+
fill()
|
|
99
|
+
|
|
100
|
+
limit = concurrency
|
|
101
|
+
live: set[asyncio.Task[None]] = set()
|
|
102
|
+
started: list[asyncio.Task[None]] = []
|
|
103
|
+
idle = asyncio.Event()
|
|
104
|
+
|
|
105
|
+
def fill() -> None:
|
|
106
|
+
while plan and len(live) < limit:
|
|
107
|
+
task = asyncio.create_task(start(*plan.pop(0)))
|
|
108
|
+
task.add_done_callback(end)
|
|
109
|
+
live.add(task)
|
|
110
|
+
started.append(task)
|
|
111
|
+
if not live:
|
|
112
|
+
idle.set()
|
|
113
|
+
|
|
114
|
+
def end(task: asyncio.Task[None]) -> None:
|
|
115
|
+
live.remove(task)
|
|
116
|
+
fill()
|
|
117
|
+
|
|
118
|
+
def set_concurrency(count: int) -> None:
|
|
119
|
+
nonlocal limit
|
|
120
|
+
limit = max(1, count)
|
|
121
|
+
report({"event": "concurrency", "concurrency": limit})
|
|
122
|
+
fill()
|
|
123
|
+
|
|
124
|
+
async def start(run: str, params: dict[str, object]) -> None:
|
|
125
|
+
folder = run_folder(config.root, config.runs, name, run)
|
|
126
|
+
folder.mkdir(parents=True)
|
|
127
|
+
report({"event": "started", "run": run, "params": params})
|
|
128
|
+
result = await execute_run(
|
|
129
|
+
config.root, folder, experiment.command, params, running, stopping
|
|
130
|
+
)
|
|
131
|
+
report({"event": "ended", "run": run, **result})
|
|
132
|
+
results.append(result)
|
|
133
|
+
|
|
134
|
+
listen(Controls(interrupt, run_next, set_concurrency, toggle))
|
|
135
|
+
fill()
|
|
136
|
+
await idle.wait()
|
|
137
|
+
await asyncio.gather(*started)
|
|
90
138
|
return results
|
|
@@ -19,6 +19,9 @@ class RunApp(App[None]):
|
|
|
19
19
|
BINDINGS = [
|
|
20
20
|
Binding("ctrl+c", "interrupt", "Stop runs"),
|
|
21
21
|
Binding("n", "run_next", "Run next"),
|
|
22
|
+
Binding("d", "toggle_run", "Deselect"),
|
|
23
|
+
Binding("plus,equals_sign", "concurrency(1)", "More at once", key_display="+"),
|
|
24
|
+
Binding("minus", "concurrency(-1)", "Fewer at once", key_display="-"),
|
|
22
25
|
Binding("q", "quit", "Quit"),
|
|
23
26
|
]
|
|
24
27
|
|
|
@@ -27,7 +30,10 @@ class RunApp(App[None]):
|
|
|
27
30
|
self.start = start
|
|
28
31
|
self.title = title
|
|
29
32
|
self.results: list[dict[str, object]] = []
|
|
30
|
-
self.controls = Controls(
|
|
33
|
+
self.controls = Controls(
|
|
34
|
+
lambda: None, lambda run: None, lambda count: None, lambda run: None
|
|
35
|
+
)
|
|
36
|
+
self.concurrency = 1
|
|
31
37
|
self.done = False
|
|
32
38
|
self.started: dict[str, float] = {}
|
|
33
39
|
self.pending: set[str] = set()
|
|
@@ -68,6 +74,7 @@ class RunApp(App[None]):
|
|
|
68
74
|
cells = (planned["run"], args, "pending", "")
|
|
69
75
|
table.add_row(*map(dim, cells), key=planned["run"])
|
|
70
76
|
self.pending.add(planned["run"])
|
|
77
|
+
self.show_concurrency(event)
|
|
71
78
|
self.show(describe_event(event))
|
|
72
79
|
case "started":
|
|
73
80
|
self.pending.discard(run)
|
|
@@ -82,12 +89,22 @@ class RunApp(App[None]):
|
|
|
82
89
|
self.tick()
|
|
83
90
|
del self.started[run]
|
|
84
91
|
case "queued":
|
|
85
|
-
|
|
86
|
-
row = table.get_row(pending)
|
|
87
|
-
table.remove_row(pending)
|
|
88
|
-
table.add_row(*row, key=pending)
|
|
92
|
+
self.reorder(event)
|
|
89
93
|
table.move_cursor(row=table.get_row_index(run))
|
|
90
94
|
self.show(describe_event(event))
|
|
95
|
+
case "deselected":
|
|
96
|
+
self.pending.discard(run)
|
|
97
|
+
table.update_cell(run, "status", dim("deselected"))
|
|
98
|
+
self.reorder(event)
|
|
99
|
+
self.show(describe_event(event))
|
|
100
|
+
case "reselected":
|
|
101
|
+
self.pending.add(run)
|
|
102
|
+
table.update_cell(run, "status", dim("pending"))
|
|
103
|
+
self.reorder(event)
|
|
104
|
+
self.show(describe_event(event))
|
|
105
|
+
case "concurrency":
|
|
106
|
+
self.show_concurrency(event)
|
|
107
|
+
self.show(describe_event(event))
|
|
91
108
|
case "stopping":
|
|
92
109
|
for pending in self.pending:
|
|
93
110
|
table.update_cell(pending, "status", dim("skipped"))
|
|
@@ -95,6 +112,18 @@ class RunApp(App[None]):
|
|
|
95
112
|
case _:
|
|
96
113
|
self.show(describe_event(event))
|
|
97
114
|
|
|
115
|
+
def reorder(self, event: dict[str, object]) -> None:
|
|
116
|
+
table = self.query_one(DataTable)
|
|
117
|
+
waiting: list[str] = [*event["pending"], *event["held"]] # type: ignore[misc]
|
|
118
|
+
for run in waiting:
|
|
119
|
+
row = table.get_row(run)
|
|
120
|
+
table.remove_row(run)
|
|
121
|
+
table.add_row(*row, key=run)
|
|
122
|
+
|
|
123
|
+
def show_concurrency(self, event: dict[str, object]) -> None:
|
|
124
|
+
self.concurrency = int(event["concurrency"]) # type: ignore[call-overload]
|
|
125
|
+
self.sub_title = f"{self.concurrency} at a time"
|
|
126
|
+
|
|
98
127
|
def tick(self) -> None:
|
|
99
128
|
table = self.query_one(DataTable)
|
|
100
129
|
for run, began in self.started.items():
|
|
@@ -117,8 +146,20 @@ class RunApp(App[None]):
|
|
|
117
146
|
else:
|
|
118
147
|
self.show("Runs are still going. Ctrl-C stops them.")
|
|
119
148
|
|
|
120
|
-
def
|
|
149
|
+
def cursor_run(self) -> str | None:
|
|
121
150
|
table = self.query_one(DataTable)
|
|
122
|
-
if table.row_count:
|
|
123
|
-
|
|
124
|
-
|
|
151
|
+
if not table.row_count:
|
|
152
|
+
return None
|
|
153
|
+
cell = table.coordinate_to_cell_key(table.cursor_coordinate)
|
|
154
|
+
return str(cell.row_key.value)
|
|
155
|
+
|
|
156
|
+
def action_run_next(self) -> None:
|
|
157
|
+
if (run := self.cursor_run()) is not None:
|
|
158
|
+
self.controls.run_next(run)
|
|
159
|
+
|
|
160
|
+
def action_toggle_run(self) -> None:
|
|
161
|
+
if (run := self.cursor_run()) is not None:
|
|
162
|
+
self.controls.toggle(run)
|
|
163
|
+
|
|
164
|
+
def action_concurrency(self, change: int) -> None:
|
|
165
|
+
self.controls.set_concurrency(self.concurrency + change)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|