labnotes 0.0.30__tar.gz → 0.0.32__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.30 → labnotes-0.0.32}/PKG-INFO +1 -1
- labnotes-0.0.32/src/labnotes/check_counts.py +17 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/context.md +2 -1
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/list_experiments.py +2 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/load_experiment.py +2 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/models.py +2 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/describe_event.py +4 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/run_experiment.py +25 -4
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/run_headless.py +1 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/static/labnotes.css +1 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/templates/experiment.html +1 -1
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/templates/index.html +3 -1
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tui/run_app.py +41 -10
- {labnotes-0.0.30 → labnotes-0.0.32}/.gitignore +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/CHANGELOG.md +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/MIGRATIONS.md +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/README.md +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/pyproject.toml +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/cell_progress.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/experiment_progress.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/init.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/pivot_cells.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/lock_path.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/start_experiment.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/wait_for_group.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/server.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/stop_idle_kernels.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/templates/macros.html +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.30 → labnotes-0.0.32}/uv.lock +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from dirsql import DirSQL
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
async def check_counts(
|
|
5
|
+
tables: DirSQL, source: str, summary: dict[str, object]
|
|
6
|
+
) -> dict[str, object]:
|
|
7
|
+
rule = str(summary["completed_run"])
|
|
8
|
+
try:
|
|
9
|
+
await tables.query(f"SELECT ({rule}) FROM {source} LIMIT 0")
|
|
10
|
+
except RuntimeError as error:
|
|
11
|
+
# A broken rule counts nothing, so the pages still render and say why.
|
|
12
|
+
return {
|
|
13
|
+
**summary,
|
|
14
|
+
"completed_run": "0",
|
|
15
|
+
"counts_error": f"{rule} doesn't work: {error}",
|
|
16
|
+
}
|
|
17
|
+
return summary
|
|
@@ -86,7 +86,8 @@ 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. Failed runs show their status in red
|
|
89
|
+
open when they finish. Failed runs show their status in red; `h` hides or shows them, and `r` on one
|
|
90
|
+
runs it again under a new id. Runs not started yet show grayed as `pending`, and as
|
|
90
91
|
`skipped` once stopped. Move the cursor to a pending run and press `n` to run it
|
|
91
92
|
next. `d` deselects a pending run so it won't run; `d` again puts it back at
|
|
92
93
|
the end of the queue. `/` filters rows by params text; Enter keeps the filter,
|
|
@@ -3,6 +3,7 @@ from typing import cast
|
|
|
3
3
|
|
|
4
4
|
from dirsql import DirSQL
|
|
5
5
|
|
|
6
|
+
from labnotes.check_counts import check_counts
|
|
6
7
|
from labnotes.experiment_progress import experiment_progress
|
|
7
8
|
from labnotes.experiment_runs import experiment_runs
|
|
8
9
|
from labnotes.summarize_config import summarize_config
|
|
@@ -19,6 +20,7 @@ async def list_experiments(tables: DirSQL) -> list[dict[str, object]]:
|
|
|
19
20
|
summary = summarize_config(config)
|
|
20
21
|
varies = cast(dict[str, list[object]], summary["varies"])
|
|
21
22
|
source = experiment_runs(name, list(varies))
|
|
23
|
+
summary = await check_counts(tables, source, summary)
|
|
22
24
|
progress = await experiment_progress(tables, source, summary)
|
|
23
25
|
listed.append(
|
|
24
26
|
{"name": name, "display_name": row["display_name"], **summary, **progress}
|
|
@@ -6,6 +6,7 @@ from typing import cast
|
|
|
6
6
|
from dirsql import DirSQL
|
|
7
7
|
|
|
8
8
|
from labnotes.experiment_notes import experiment_notes
|
|
9
|
+
from labnotes.check_counts import check_counts
|
|
9
10
|
from labnotes.experiment_progress import experiment_progress
|
|
10
11
|
from labnotes.experiment_runs import experiment_runs
|
|
11
12
|
from labnotes.list_runs import list_runs
|
|
@@ -39,6 +40,7 @@ async def load_experiment(
|
|
|
39
40
|
summary = summarize_config(config)
|
|
40
41
|
varies = cast(dict[str, list[object]], summary["varies"])
|
|
41
42
|
source = experiment_runs(name, list(varies))
|
|
43
|
+
summary = await check_counts(tables, source, summary)
|
|
42
44
|
progress = await experiment_progress(tables, source, summary)
|
|
43
45
|
shown = ["id", *(key for key in varies if key != "id")]
|
|
44
46
|
view = parse_run_view(params, shown)
|
|
@@ -22,6 +22,10 @@ def describe_event(event: dict[str, object]) -> str:
|
|
|
22
22
|
return f"Stopping {event['run']}."
|
|
23
23
|
case "killing run":
|
|
24
24
|
return f"Killing {event['run']}."
|
|
25
|
+
case "retried":
|
|
26
|
+
return f"{event['run']} retried as {event['retry']}."
|
|
27
|
+
case "idle":
|
|
28
|
+
return "No runs left."
|
|
25
29
|
case "concurrency":
|
|
26
30
|
return f"{event['concurrency']} at a time."
|
|
27
31
|
case "stopping":
|
|
@@ -113,10 +113,22 @@ async def run_experiment(
|
|
|
113
113
|
arrange("reselected", run)
|
|
114
114
|
fill()
|
|
115
115
|
|
|
116
|
+
ended: dict[str, dict[str, object]] = {}
|
|
117
|
+
|
|
118
|
+
def retry(run: str) -> None:
|
|
119
|
+
if stopping.is_set() or run not in ended:
|
|
120
|
+
return
|
|
121
|
+
again = new_run_id(taken)
|
|
122
|
+
planned.add(again)
|
|
123
|
+
plan.append((again, ended[run]))
|
|
124
|
+
report({"event": "retried", "run": run, "retry": again, "params": ended[run]})
|
|
125
|
+
fill()
|
|
126
|
+
|
|
116
127
|
limit = concurrency
|
|
117
128
|
live: set[asyncio.Task[None]] = set()
|
|
118
129
|
started: list[asyncio.Task[None]] = []
|
|
119
|
-
|
|
130
|
+
finishing = False
|
|
131
|
+
done = asyncio.Event()
|
|
120
132
|
|
|
121
133
|
def fill() -> None:
|
|
122
134
|
while plan and len(live) < limit:
|
|
@@ -125,7 +137,15 @@ async def run_experiment(
|
|
|
125
137
|
live.add(task)
|
|
126
138
|
started.append(task)
|
|
127
139
|
if not live:
|
|
128
|
-
idle
|
|
140
|
+
report({"event": "idle"})
|
|
141
|
+
if finishing:
|
|
142
|
+
done.set()
|
|
143
|
+
|
|
144
|
+
def finish() -> None:
|
|
145
|
+
nonlocal finishing
|
|
146
|
+
finishing = True
|
|
147
|
+
if not (live or plan):
|
|
148
|
+
done.set()
|
|
129
149
|
|
|
130
150
|
def end(task: asyncio.Task[None]) -> None:
|
|
131
151
|
live.remove(task)
|
|
@@ -147,10 +167,11 @@ async def run_experiment(
|
|
|
147
167
|
)
|
|
148
168
|
del running[run]
|
|
149
169
|
report({"event": "ended", "run": run, **result})
|
|
170
|
+
ended[run] = params
|
|
150
171
|
results.append(result)
|
|
151
172
|
|
|
152
|
-
listen(Controls(interrupt, run_next, set_concurrency, toggle, kill))
|
|
173
|
+
listen(Controls(interrupt, run_next, set_concurrency, toggle, kill, retry, finish))
|
|
153
174
|
fill()
|
|
154
|
-
await
|
|
175
|
+
await done.wait()
|
|
155
176
|
await asyncio.gather(*started)
|
|
156
177
|
return results
|
|
@@ -13,5 +13,6 @@ def run_headless(start: Start) -> list[dict[str, object]]:
|
|
|
13
13
|
loop = asyncio.get_running_loop()
|
|
14
14
|
loop.add_signal_handler(signal.SIGINT, controls.interrupt)
|
|
15
15
|
stop_then_kill(controls.interrupt, signal.SIGTERM, signal.SIGHUP)
|
|
16
|
+
controls.finish()
|
|
16
17
|
|
|
17
18
|
return asyncio.run(start(lambda event: click.echo(describe_event(event)), listen))
|
|
@@ -341,6 +341,7 @@ h2 .clear { font-family: var(--sans); font-size: 0.82rem; margin-left: 0.8rem; c
|
|
|
341
341
|
.status { font-size: 0.8rem; font-weight: 700; }
|
|
342
342
|
.status.good { color: var(--good); }
|
|
343
343
|
.status.bad { color: var(--bad); }
|
|
344
|
+
.counts-error { color: var(--bad); }
|
|
344
345
|
|
|
345
346
|
.record {
|
|
346
347
|
display: grid;
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
{% for key, value in e.other.items() %}
|
|
67
67
|
<dt>{{ key }}</dt><dd><code>{{ value | value }}</code></dd>
|
|
68
68
|
{% endfor %}
|
|
69
|
-
<dt>complete when</dt
|
|
69
|
+
<dt>complete when</dt>{% if e.counts_error %}<dd class="counts-error">{{ e.counts_error }}</dd>{% else %}<dd><code>{{ e.completed_run }}</code></dd>{% endif %}
|
|
70
70
|
</dl>
|
|
71
71
|
</section>
|
|
72
72
|
|
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
</td>
|
|
38
38
|
<td class="num" data-label="Runs">{{ experiment.runs }}</td>
|
|
39
39
|
<td class="progress-col">
|
|
40
|
-
{% if experiment.
|
|
40
|
+
{% if experiment.counts_error %}
|
|
41
|
+
<span class="counts-error" title="{{ experiment.counts_error }}">counts rule doesn't work</span>
|
|
42
|
+
{% elif experiment.filled is not none and experiment.target %}
|
|
41
43
|
<div class="progress-line">
|
|
42
44
|
{{ meter(experiment.filled, experiment.target) }}
|
|
43
45
|
<span class="num">{{ experiment.filled }}<span class="of">/{{ experiment.target }}</span></span>
|
|
@@ -23,6 +23,8 @@ class RunApp(App[None]):
|
|
|
23
23
|
Binding("d", "toggle_run", "Deselect"),
|
|
24
24
|
Binding("D", "deselect_shown", "Deselect shown"),
|
|
25
25
|
Binding("k", "kill_run", "Stop run"),
|
|
26
|
+
Binding("h", "hide_failed", "Hide failed"),
|
|
27
|
+
Binding("r", "retry", "Retry"),
|
|
26
28
|
Binding("slash", "filter", "Filter", key_display="/"),
|
|
27
29
|
Binding("escape", "clear_filter", "Clear filter"),
|
|
28
30
|
Binding("plus,equals_sign", "concurrency(1)", "More at once", key_display="+"),
|
|
@@ -41,13 +43,18 @@ class RunApp(App[None]):
|
|
|
41
43
|
lambda count: None,
|
|
42
44
|
lambda run: None,
|
|
43
45
|
lambda run: None,
|
|
46
|
+
lambda run: None,
|
|
47
|
+
lambda: None,
|
|
44
48
|
)
|
|
45
49
|
self.concurrency = 1
|
|
46
|
-
self.
|
|
50
|
+
self.idle = False
|
|
51
|
+
self.ended: set[str] = set()
|
|
47
52
|
self.started: dict[str, float] = {}
|
|
48
53
|
self.pending: set[str] = set()
|
|
49
54
|
self.cells: dict[str, list[object]] = {}
|
|
50
55
|
self.filter = ""
|
|
56
|
+
self.failed: set[str] = set()
|
|
57
|
+
self.hiding = False
|
|
51
58
|
|
|
52
59
|
def compose(self) -> ComposeResult:
|
|
53
60
|
yield Header()
|
|
@@ -68,10 +75,7 @@ class RunApp(App[None]):
|
|
|
68
75
|
|
|
69
76
|
async def drive(self) -> None:
|
|
70
77
|
self.results = await self.start(self.show_event, self.listen)
|
|
71
|
-
self.
|
|
72
|
-
failed = sum(result["exit_code"] != 0 for result in self.results)
|
|
73
|
-
passed = len(self.results) - failed
|
|
74
|
-
self.show(f"{passed} passed, {failed} failed. q to quit.")
|
|
78
|
+
self.exit()
|
|
75
79
|
|
|
76
80
|
def listen(self, controls: Controls) -> None:
|
|
77
81
|
self.controls = controls
|
|
@@ -91,6 +95,7 @@ class RunApp(App[None]):
|
|
|
91
95
|
self.show_concurrency(event)
|
|
92
96
|
self.show(describe_event(event))
|
|
93
97
|
case "started":
|
|
98
|
+
self.idle = False
|
|
94
99
|
self.pending.discard(run)
|
|
95
100
|
self.started[run] = time.monotonic()
|
|
96
101
|
args = " ".join(param_args(event["params"])) # type: ignore[arg-type]
|
|
@@ -100,9 +105,13 @@ class RunApp(App[None]):
|
|
|
100
105
|
case "ended":
|
|
101
106
|
outcome = event["error"] or f"exited {event['exit_code']}"
|
|
102
107
|
style = "red" if event["error"] or event["exit_code"] else ""
|
|
108
|
+
if style:
|
|
109
|
+
self.failed.add(run)
|
|
110
|
+
self.ended.add(run)
|
|
103
111
|
self.set_cell(run, "status", Text(str(outcome), style=style))
|
|
104
112
|
self.tick()
|
|
105
113
|
del self.started[run]
|
|
114
|
+
self.redraw(table.cursor_row)
|
|
106
115
|
case "queued":
|
|
107
116
|
self.reorder(event, table.cursor_row)
|
|
108
117
|
table.move_cursor(row=table.get_row_index(run))
|
|
@@ -117,6 +126,18 @@ class RunApp(App[None]):
|
|
|
117
126
|
self.set_cell(run, "status", dim("pending"))
|
|
118
127
|
self.reorder(event, table.cursor_row)
|
|
119
128
|
self.show(describe_event(event))
|
|
129
|
+
case "retried":
|
|
130
|
+
again = str(event["retry"])
|
|
131
|
+
args = " ".join(param_args(event["params"])) # type: ignore[arg-type]
|
|
132
|
+
self.cells[again] = [*map(dim, (again, args, "pending", ""))]
|
|
133
|
+
self.pending.add(again)
|
|
134
|
+
self.redraw(table.cursor_row)
|
|
135
|
+
self.show(describe_event(event))
|
|
136
|
+
case "idle":
|
|
137
|
+
self.idle = True
|
|
138
|
+
failed = len(self.failed)
|
|
139
|
+
passed = len(self.ended) - failed
|
|
140
|
+
self.show(f"{passed} passed, {failed} failed. q to quit.")
|
|
120
141
|
case "concurrency":
|
|
121
142
|
self.show_concurrency(event)
|
|
122
143
|
self.show(describe_event(event))
|
|
@@ -137,7 +158,8 @@ class RunApp(App[None]):
|
|
|
137
158
|
table = self.query_one(DataTable)
|
|
138
159
|
table.clear()
|
|
139
160
|
for run, cells in self.cells.items():
|
|
140
|
-
|
|
161
|
+
hidden = self.hiding and run in self.failed
|
|
162
|
+
if self.filter in str(cells[1]) and not hidden:
|
|
141
163
|
table.add_row(*cells, key=run)
|
|
142
164
|
table.move_cursor(row=row)
|
|
143
165
|
|
|
@@ -159,14 +181,14 @@ class RunApp(App[None]):
|
|
|
159
181
|
self.query_one("#status", Static).update(text)
|
|
160
182
|
|
|
161
183
|
def action_interrupt(self) -> None:
|
|
162
|
-
if self.
|
|
163
|
-
self.
|
|
184
|
+
if self.idle:
|
|
185
|
+
self.controls.finish()
|
|
164
186
|
else:
|
|
165
187
|
self.controls.interrupt()
|
|
166
188
|
|
|
167
189
|
async def action_quit(self) -> None:
|
|
168
|
-
if self.
|
|
169
|
-
self.
|
|
190
|
+
if self.idle:
|
|
191
|
+
self.controls.finish()
|
|
170
192
|
else:
|
|
171
193
|
self.show("Runs are still going. Ctrl-C stops them.")
|
|
172
194
|
|
|
@@ -185,6 +207,15 @@ class RunApp(App[None]):
|
|
|
185
207
|
if (run := self.cursor_run()) is not None:
|
|
186
208
|
self.controls.toggle(run)
|
|
187
209
|
|
|
210
|
+
def action_hide_failed(self) -> None:
|
|
211
|
+
self.hiding = not self.hiding
|
|
212
|
+
self.redraw(self.query_one(DataTable).cursor_row)
|
|
213
|
+
self.show("Failed runs hidden." if self.hiding else "Failed runs shown.")
|
|
214
|
+
|
|
215
|
+
def action_retry(self) -> None:
|
|
216
|
+
if (run := self.cursor_run()) is not None and run in self.failed:
|
|
217
|
+
self.controls.retry(run)
|
|
218
|
+
|
|
188
219
|
def action_kill_run(self) -> None:
|
|
189
220
|
if (run := self.cursor_run()) is not None:
|
|
190
221
|
self.controls.kill(run)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|