labnotes 0.0.26__tar.gz → 0.0.28__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.26 → labnotes-0.0.28}/PKG-INFO +1 -1
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/context.md +3 -1
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tui/run_app.py +61 -19
- {labnotes-0.0.26 → labnotes-0.0.28}/.gitignore +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/CHANGELOG.md +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/MIGRATIONS.md +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/README.md +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/pyproject.toml +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/cell_progress.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/experiment_progress.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/init.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/models.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/pivot_cells.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/lock_path.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/run_experiment.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/start_experiment.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/wait_for_group.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/server.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/stop_idle_kernels.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/templates/macros.html +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.26 → labnotes-0.0.28}/uv.lock +0 -0
|
@@ -89,7 +89,9 @@ 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
91
|
next. `d` deselects a pending run so it won't run; `d` again puts it back at
|
|
92
|
-
the end of the queue.
|
|
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
|
|
94
|
+
`-` change how many run at once; lowering it lets running runs
|
|
93
95
|
finish; `q` quits. Elsewhere, or with `--no-tui`, it prints a
|
|
94
96
|
line per event instead.
|
|
95
97
|
|
|
@@ -3,7 +3,7 @@ import time
|
|
|
3
3
|
|
|
4
4
|
from textual.app import App, ComposeResult
|
|
5
5
|
from textual.binding import Binding
|
|
6
|
-
from textual.widgets import DataTable, Footer, Header, Static
|
|
6
|
+
from textual.widgets import DataTable, Footer, Header, Input, Static
|
|
7
7
|
|
|
8
8
|
from labnotes.models import Controls, Start
|
|
9
9
|
from labnotes.runner.describe_event import describe_event
|
|
@@ -20,6 +20,9 @@ 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("slash", "filter", "Filter", key_display="/"),
|
|
25
|
+
Binding("escape", "clear_filter", "Clear filter"),
|
|
23
26
|
Binding("plus,equals_sign", "concurrency(1)", "More at once", key_display="+"),
|
|
24
27
|
Binding("minus", "concurrency(-1)", "Fewer at once", key_display="-"),
|
|
25
28
|
Binding("q", "quit", "Quit"),
|
|
@@ -37,10 +40,13 @@ class RunApp(App[None]):
|
|
|
37
40
|
self.done = False
|
|
38
41
|
self.started: dict[str, float] = {}
|
|
39
42
|
self.pending: set[str] = set()
|
|
43
|
+
self.cells: dict[str, list[object]] = {}
|
|
44
|
+
self.filter = ""
|
|
40
45
|
|
|
41
46
|
def compose(self) -> ComposeResult:
|
|
42
47
|
yield Header()
|
|
43
48
|
yield DataTable(id="runs", cursor_type="row")
|
|
49
|
+
yield Input(id="filter", placeholder="Filter params")
|
|
44
50
|
yield Static(id="status")
|
|
45
51
|
yield Footer()
|
|
46
52
|
|
|
@@ -48,6 +54,7 @@ class RunApp(App[None]):
|
|
|
48
54
|
table = self.query_one(DataTable)
|
|
49
55
|
for column in COLUMNS:
|
|
50
56
|
table.add_column(column, key=column)
|
|
57
|
+
self.query_one(Input).display = False
|
|
51
58
|
|
|
52
59
|
def on_ready(self) -> None:
|
|
53
60
|
self.set_interval(1, self.tick)
|
|
@@ -72,8 +79,9 @@ class RunApp(App[None]):
|
|
|
72
79
|
for planned in event["runs"]: # type: ignore[attr-defined]
|
|
73
80
|
args = " ".join(param_args(planned["params"]))
|
|
74
81
|
cells = (planned["run"], args, "pending", "")
|
|
75
|
-
|
|
82
|
+
self.cells[planned["run"]] = [*map(dim, cells)]
|
|
76
83
|
self.pending.add(planned["run"])
|
|
84
|
+
self.redraw(table.cursor_row)
|
|
77
85
|
self.show_concurrency(event)
|
|
78
86
|
self.show(describe_event(event))
|
|
79
87
|
case "started":
|
|
@@ -82,54 +90,63 @@ class RunApp(App[None]):
|
|
|
82
90
|
args = " ".join(param_args(event["params"])) # type: ignore[arg-type]
|
|
83
91
|
cells = (run, args, "running", format_elapsed(0))
|
|
84
92
|
for column, cell in zip(COLUMNS, cells):
|
|
85
|
-
|
|
93
|
+
self.set_cell(run, column, cell)
|
|
86
94
|
case "ended":
|
|
87
95
|
outcome = event["error"] or f"exited {event['exit_code']}"
|
|
88
|
-
|
|
96
|
+
self.set_cell(run, "status", outcome)
|
|
89
97
|
self.tick()
|
|
90
98
|
del self.started[run]
|
|
91
99
|
case "queued":
|
|
92
|
-
self.reorder(event)
|
|
100
|
+
self.reorder(event, table.cursor_row)
|
|
93
101
|
table.move_cursor(row=table.get_row_index(run))
|
|
94
102
|
self.show(describe_event(event))
|
|
95
103
|
case "deselected":
|
|
96
104
|
self.pending.discard(run)
|
|
97
|
-
|
|
98
|
-
self.reorder(event)
|
|
105
|
+
self.set_cell(run, "status", dim("deselected"))
|
|
106
|
+
self.reorder(event, table.cursor_row)
|
|
99
107
|
self.show(describe_event(event))
|
|
100
108
|
case "reselected":
|
|
101
109
|
self.pending.add(run)
|
|
102
|
-
|
|
103
|
-
self.reorder(event)
|
|
110
|
+
self.set_cell(run, "status", dim("pending"))
|
|
111
|
+
self.reorder(event, table.cursor_row)
|
|
104
112
|
self.show(describe_event(event))
|
|
105
113
|
case "concurrency":
|
|
106
114
|
self.show_concurrency(event)
|
|
107
115
|
self.show(describe_event(event))
|
|
108
116
|
case "stopping":
|
|
109
117
|
for pending in self.pending:
|
|
110
|
-
|
|
118
|
+
self.set_cell(pending, "status", dim("skipped"))
|
|
111
119
|
self.show(describe_event(event))
|
|
112
120
|
case _:
|
|
113
121
|
self.show(describe_event(event))
|
|
114
122
|
|
|
115
|
-
def reorder(self, event: dict[str, object]) -> None:
|
|
116
|
-
table = self.query_one(DataTable)
|
|
123
|
+
def reorder(self, event: dict[str, object], row: int) -> None:
|
|
117
124
|
waiting: list[str] = [*event["pending"], *event["held"]] # type: ignore[misc]
|
|
118
125
|
for run in waiting:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
self.cells[run] = self.cells.pop(run)
|
|
127
|
+
self.redraw(row)
|
|
128
|
+
|
|
129
|
+
def redraw(self, row: int) -> None:
|
|
130
|
+
table = self.query_one(DataTable)
|
|
131
|
+
table.clear()
|
|
132
|
+
for run, cells in self.cells.items():
|
|
133
|
+
if self.filter in str(cells[1]):
|
|
134
|
+
table.add_row(*cells, key=run)
|
|
135
|
+
table.move_cursor(row=row)
|
|
136
|
+
|
|
137
|
+
def set_cell(self, run: str, column: str, cell: object) -> None:
|
|
138
|
+
self.cells[run][COLUMNS.index(column)] = cell
|
|
139
|
+
table = self.query_one(DataTable)
|
|
140
|
+
if run in table.rows:
|
|
141
|
+
table.update_cell(run, column, cell, update_width=True)
|
|
122
142
|
|
|
123
143
|
def show_concurrency(self, event: dict[str, object]) -> None:
|
|
124
144
|
self.concurrency = int(event["concurrency"]) # type: ignore[call-overload]
|
|
125
145
|
self.sub_title = f"{self.concurrency} at a time"
|
|
126
146
|
|
|
127
147
|
def tick(self) -> None:
|
|
128
|
-
table = self.query_one(DataTable)
|
|
129
148
|
for run, began in self.started.items():
|
|
130
|
-
|
|
131
|
-
run, "time", format_elapsed(time.monotonic() - began), update_width=True
|
|
132
|
-
)
|
|
149
|
+
self.set_cell(run, "time", format_elapsed(time.monotonic() - began))
|
|
133
150
|
|
|
134
151
|
def show(self, text: str) -> None:
|
|
135
152
|
self.query_one("#status", Static).update(text)
|
|
@@ -161,5 +178,30 @@ class RunApp(App[None]):
|
|
|
161
178
|
if (run := self.cursor_run()) is not None:
|
|
162
179
|
self.controls.toggle(run)
|
|
163
180
|
|
|
181
|
+
def action_deselect_shown(self) -> None:
|
|
182
|
+
shown = [str(key.value) for key in self.query_one(DataTable).rows]
|
|
183
|
+
for run in shown:
|
|
184
|
+
if run in self.pending:
|
|
185
|
+
self.controls.toggle(run)
|
|
186
|
+
|
|
164
187
|
def action_concurrency(self, change: int) -> None:
|
|
165
188
|
self.controls.set_concurrency(self.concurrency + change)
|
|
189
|
+
|
|
190
|
+
def action_filter(self) -> None:
|
|
191
|
+
field = self.query_one(Input)
|
|
192
|
+
field.display = True
|
|
193
|
+
field.focus()
|
|
194
|
+
|
|
195
|
+
def action_clear_filter(self) -> None:
|
|
196
|
+
field = self.query_one(Input)
|
|
197
|
+
field.value = ""
|
|
198
|
+
field.display = False
|
|
199
|
+
self.query_one(DataTable).focus()
|
|
200
|
+
|
|
201
|
+
def on_input_changed(self, event: Input.Changed) -> None:
|
|
202
|
+
self.filter = event.value
|
|
203
|
+
self.redraw(self.query_one(DataTable).cursor_row)
|
|
204
|
+
|
|
205
|
+
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
206
|
+
event.input.display = bool(event.value)
|
|
207
|
+
self.query_one(DataTable).focus()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|