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