labnotes 0.0.24__tar.gz → 0.0.25__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.24 → labnotes-0.0.25}/PKG-INFO +1 -1
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/context.md +2 -1
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/models.py +9 -1
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/describe_event.py +2 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/run_experiment.py +16 -2
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/run_headless.py +5 -5
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tui/run_app.py +20 -7
- {labnotes-0.0.24 → labnotes-0.0.25}/.gitignore +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/CHANGELOG.md +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/MIGRATIONS.md +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/README.md +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/pyproject.toml +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/cell_progress.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/experiment_progress.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/init.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/pivot_cells.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/lock_path.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/start_experiment.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/wait_for_group.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/server.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/stop_idle_kernels.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/templates/macros.html +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.24 → labnotes-0.0.25}/uv.lock +0 -0
|
@@ -87,7 +87,8 @@ Runs can take a long time. Start `run` in the background, then check on it with
|
|
|
87
87
|
|
|
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
|
-
`skipped` once stopped
|
|
90
|
+
`skipped` once stopped. Move the cursor to a pending run and press `n` to run it
|
|
91
|
+
next; `q` quits. Elsewhere, or with `--no-tui`, it prints a
|
|
91
92
|
line per event instead.
|
|
92
93
|
|
|
93
94
|
- `--concurrency N` runs N at a time (default 1).
|
|
@@ -6,7 +6,15 @@ from typing import Any
|
|
|
6
6
|
from labnotes.default_counts import DEFAULT_COUNTS
|
|
7
7
|
|
|
8
8
|
Report = Callable[[dict[str, object]], None]
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class Controls:
|
|
13
|
+
interrupt: Callable[[], None]
|
|
14
|
+
run_next: Callable[[str], None]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Listen = Callable[[Controls], None]
|
|
10
18
|
Start = Callable[[Report, Listen], Coroutine[Any, Any, list[dict[str, object]]]]
|
|
11
19
|
|
|
12
20
|
|
|
@@ -12,6 +12,8 @@ def describe_event(event: dict[str, object]) -> str:
|
|
|
12
12
|
case "ended":
|
|
13
13
|
outcome = event["error"] or f"exited {event['exit_code']}"
|
|
14
14
|
return f"{event['run']} {outcome}"
|
|
15
|
+
case "queued":
|
|
16
|
+
return f"{event['run']} runs next."
|
|
15
17
|
case "stopping":
|
|
16
18
|
return "Stopping runs. Ctrl-C again to kill them."
|
|
17
19
|
case _:
|
|
@@ -4,7 +4,13 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
import signal
|
|
6
6
|
|
|
7
|
-
from labnotes.models import
|
|
7
|
+
from labnotes.models import (
|
|
8
|
+
Controls,
|
|
9
|
+
ExperimentConfig,
|
|
10
|
+
LabnotesConfig,
|
|
11
|
+
Listen,
|
|
12
|
+
Report,
|
|
13
|
+
)
|
|
8
14
|
from labnotes.runner.execute_run import execute_run
|
|
9
15
|
from labnotes.runner.new_run_id import new_run_id
|
|
10
16
|
from labnotes.runner.param_grid import param_grid
|
|
@@ -59,6 +65,14 @@ async def run_experiment(
|
|
|
59
65
|
report({"event": "killing"})
|
|
60
66
|
signal_runs(signal.SIGKILL)
|
|
61
67
|
|
|
68
|
+
def run_next(run: str) -> None:
|
|
69
|
+
entry = next((entry for entry in plan if entry[0] == run), None)
|
|
70
|
+
if entry is not None:
|
|
71
|
+
plan.remove(entry)
|
|
72
|
+
plan.insert(0, entry)
|
|
73
|
+
pending = [id_ for id_, _ in plan]
|
|
74
|
+
report({"event": "queued", "run": run, "pending": pending})
|
|
75
|
+
|
|
62
76
|
async def work() -> None:
|
|
63
77
|
while plan:
|
|
64
78
|
run, params = plan.pop(0)
|
|
@@ -71,6 +85,6 @@ async def run_experiment(
|
|
|
71
85
|
report({"event": "ended", "run": run, **result})
|
|
72
86
|
results.append(result)
|
|
73
87
|
|
|
74
|
-
listen(interrupt)
|
|
88
|
+
listen(Controls(interrupt, run_next))
|
|
75
89
|
await asyncio.gather(*(work() for _ in range(concurrency)))
|
|
76
90
|
return results
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import signal
|
|
3
|
-
from collections.abc import Callable
|
|
4
3
|
|
|
5
4
|
import click
|
|
6
5
|
|
|
7
|
-
from labnotes.models import Start
|
|
6
|
+
from labnotes.models import Controls, Start
|
|
8
7
|
from labnotes.runner.describe_event import describe_event
|
|
9
8
|
from labnotes.runner.stop_then_kill import stop_then_kill
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
def run_headless(start: Start) -> list[dict[str, object]]:
|
|
13
|
-
def listen(
|
|
14
|
-
asyncio.get_running_loop()
|
|
15
|
-
|
|
12
|
+
def listen(controls: Controls) -> None:
|
|
13
|
+
loop = asyncio.get_running_loop()
|
|
14
|
+
loop.add_signal_handler(signal.SIGINT, controls.interrupt)
|
|
15
|
+
stop_then_kill(controls.interrupt, signal.SIGTERM, signal.SIGHUP)
|
|
16
16
|
|
|
17
17
|
return asyncio.run(start(lambda event: click.echo(describe_event(event)), listen))
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import signal
|
|
2
2
|
import time
|
|
3
|
-
from collections.abc import Callable
|
|
4
3
|
|
|
5
4
|
from textual.app import App, ComposeResult
|
|
6
5
|
from textual.binding import Binding
|
|
7
6
|
from textual.widgets import DataTable, Footer, Header, Static
|
|
8
7
|
|
|
9
|
-
from labnotes.models import Start
|
|
8
|
+
from labnotes.models import Controls, Start
|
|
10
9
|
from labnotes.runner.describe_event import describe_event
|
|
11
10
|
from labnotes.runner.stop_then_kill import stop_then_kill
|
|
12
11
|
from labnotes.runner.param_args import param_args
|
|
@@ -19,6 +18,7 @@ COLUMNS = ("run", "params", "status", "time")
|
|
|
19
18
|
class RunApp(App[None]):
|
|
20
19
|
BINDINGS = [
|
|
21
20
|
Binding("ctrl+c", "interrupt", "Stop runs"),
|
|
21
|
+
Binding("n", "run_next", "Run next"),
|
|
22
22
|
Binding("q", "quit", "Quit"),
|
|
23
23
|
]
|
|
24
24
|
|
|
@@ -27,14 +27,14 @@ class RunApp(App[None]):
|
|
|
27
27
|
self.start = start
|
|
28
28
|
self.title = title
|
|
29
29
|
self.results: list[dict[str, object]] = []
|
|
30
|
-
self.
|
|
30
|
+
self.controls = Controls(lambda: None, lambda run: None)
|
|
31
31
|
self.done = False
|
|
32
32
|
self.started: dict[str, float] = {}
|
|
33
33
|
self.pending: set[str] = set()
|
|
34
34
|
|
|
35
35
|
def compose(self) -> ComposeResult:
|
|
36
36
|
yield Header()
|
|
37
|
-
yield DataTable(id="runs", cursor_type="
|
|
37
|
+
yield DataTable(id="runs", cursor_type="row")
|
|
38
38
|
yield Static(id="status")
|
|
39
39
|
yield Footer()
|
|
40
40
|
|
|
@@ -54,8 +54,8 @@ class RunApp(App[None]):
|
|
|
54
54
|
passed = len(self.results) - failed
|
|
55
55
|
self.show(f"{passed} passed, {failed} failed. q to quit.")
|
|
56
56
|
|
|
57
|
-
def listen(self,
|
|
58
|
-
self.
|
|
57
|
+
def listen(self, controls: Controls) -> None:
|
|
58
|
+
self.controls = controls
|
|
59
59
|
stop_then_kill(self.action_interrupt, signal.SIGTERM, signal.SIGHUP)
|
|
60
60
|
|
|
61
61
|
def show_event(self, event: dict[str, object]) -> None:
|
|
@@ -81,6 +81,13 @@ class RunApp(App[None]):
|
|
|
81
81
|
table.update_cell(run, "status", outcome, update_width=True)
|
|
82
82
|
self.tick()
|
|
83
83
|
del self.started[run]
|
|
84
|
+
case "queued":
|
|
85
|
+
for pending in event["pending"]: # type: ignore[attr-defined]
|
|
86
|
+
row = table.get_row(pending)
|
|
87
|
+
table.remove_row(pending)
|
|
88
|
+
table.add_row(*row, key=pending)
|
|
89
|
+
table.move_cursor(row=table.get_row_index(run))
|
|
90
|
+
self.show(describe_event(event))
|
|
84
91
|
case "stopping":
|
|
85
92
|
for pending in self.pending:
|
|
86
93
|
table.update_cell(pending, "status", dim("skipped"))
|
|
@@ -102,10 +109,16 @@ class RunApp(App[None]):
|
|
|
102
109
|
if self.done:
|
|
103
110
|
self.exit()
|
|
104
111
|
else:
|
|
105
|
-
self.interrupt()
|
|
112
|
+
self.controls.interrupt()
|
|
106
113
|
|
|
107
114
|
async def action_quit(self) -> None:
|
|
108
115
|
if self.done:
|
|
109
116
|
self.exit()
|
|
110
117
|
else:
|
|
111
118
|
self.show("Runs are still going. Ctrl-C stops them.")
|
|
119
|
+
|
|
120
|
+
def action_run_next(self) -> None:
|
|
121
|
+
table = self.query_one(DataTable)
|
|
122
|
+
if table.row_count:
|
|
123
|
+
cell = table.coordinate_to_cell_key(table.cursor_coordinate)
|
|
124
|
+
self.controls.run_next(str(cell.row_key.value))
|
|
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
|