labnotes 0.0.58__tar.gz → 0.0.60__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.58 → labnotes-0.0.60}/PKG-INFO +1 -1
- labnotes-0.0.60/src/labnotes/runner/new_param_run_id.py +18 -0
- labnotes-0.0.60/src/labnotes/runner/param_run_name.py +17 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/run_experiment.py +7 -5
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/static/note-edit.js +9 -2
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tui/run_app.py +4 -4
- {labnotes-0.0.58 → labnotes-0.0.60}/.gitignore +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/CHANGELOG.md +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/MIGRATIONS.md +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/README.md +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/pyproject.toml +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/cell_progress.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/context.md +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/experiment_progress.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/init.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/models.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/modes.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/start_experiment.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/wait_for_group.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/save_note.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/server.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/stop_idle_kernels.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/templates/macros.html +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/testing-conventions.toml +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/tests/integration/conftest.py +0 -0
- {labnotes-0.0.58 → labnotes-0.0.60}/uv.lock +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
|
|
4
|
+
from labnotes.runner.new_run_id import new_run_id
|
|
5
|
+
from labnotes.runner.param_run_name import param_run_name
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def new_param_run_id(
|
|
9
|
+
params: dict[str, object],
|
|
10
|
+
choices: dict[str, list[object]],
|
|
11
|
+
taken: Callable[[str], bool],
|
|
12
|
+
) -> str:
|
|
13
|
+
name = param_run_name(params, choices)
|
|
14
|
+
if not name:
|
|
15
|
+
return new_run_id(taken)
|
|
16
|
+
while taken(candidate := f"{name}-{secrets.token_hex(2)}"):
|
|
17
|
+
pass
|
|
18
|
+
return candidate
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
UNSAFE = re.compile(r"[^A-Za-z0-9]+")
|
|
5
|
+
LIMIT = 60
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def param_run_name(params: dict[str, object], choices: dict[str, list[object]]) -> str:
|
|
9
|
+
parts = []
|
|
10
|
+
for name, values in choices.items():
|
|
11
|
+
if len(values) < 2:
|
|
12
|
+
continue
|
|
13
|
+
shared = os.path.commonprefix([str(value) for value in values])
|
|
14
|
+
cut = re.match(r".*[^A-Za-z0-9]", shared)
|
|
15
|
+
value = str(params[name])[cut.end() if cut else 0 :]
|
|
16
|
+
parts.append(UNSAFE.sub("-", value).strip("-"))
|
|
17
|
+
return "-".join(part for part in parts if part)[:LIMIT].strip("-")
|
|
@@ -12,7 +12,7 @@ from labnotes.models import (
|
|
|
12
12
|
Report,
|
|
13
13
|
)
|
|
14
14
|
from labnotes.runner.execute_run import execute_run
|
|
15
|
-
from labnotes.runner.
|
|
15
|
+
from labnotes.runner.new_param_run_id import new_param_run_id
|
|
16
16
|
from labnotes.runner.new_stamped_run_id import new_stamped_run_id
|
|
17
17
|
from labnotes.runner.param_grid import param_grid
|
|
18
18
|
from labnotes.runner.plan_runs import plan_runs
|
|
@@ -46,16 +46,18 @@ async def run_experiment(
|
|
|
46
46
|
|
|
47
47
|
group = new_stamped_run_id(taken) if experiment.mode == "adhoc" else None
|
|
48
48
|
|
|
49
|
-
def new_id() -> str:
|
|
49
|
+
def new_id(params: dict[str, object]) -> str:
|
|
50
50
|
if group is None:
|
|
51
51
|
return new_stamped_run_id(taken)
|
|
52
|
-
return f"{group}/" +
|
|
52
|
+
return f"{group}/" + new_param_run_id(
|
|
53
|
+
params, experiment.params, lambda run: taken(f"{group}/{run}")
|
|
54
|
+
)
|
|
53
55
|
|
|
54
56
|
plan: list[Entry] = []
|
|
55
57
|
for params in plan_runs(
|
|
56
58
|
param_grid(experiment.params), experiment.repeats, finished
|
|
57
59
|
):
|
|
58
|
-
run = new_id()
|
|
60
|
+
run = new_id(params)
|
|
59
61
|
planned.add(run)
|
|
60
62
|
plan.append((run, params))
|
|
61
63
|
runs = [{"run": run, "params": params} for run, params in plan]
|
|
@@ -137,7 +139,7 @@ async def run_experiment(
|
|
|
137
139
|
def retry(run: str) -> None:
|
|
138
140
|
if stopping.is_set() or run not in ended:
|
|
139
141
|
return
|
|
140
|
-
again = new_id()
|
|
142
|
+
again = new_id(ended[run])
|
|
141
143
|
planned.add(again)
|
|
142
144
|
plan.append((again, ended[run]))
|
|
143
145
|
report({"event": "retried", "run": run, "retry": again, "params": ended[run]})
|
|
@@ -4,6 +4,7 @@ if (note) {
|
|
|
4
4
|
const prose = note.querySelector(".prose");
|
|
5
5
|
const status = note.querySelector(".edit-status");
|
|
6
6
|
const editor = import(note.dataset.editor);
|
|
7
|
+
const source = JSON.parse(note.querySelector(".note-source").textContent);
|
|
7
8
|
let editing = null;
|
|
8
9
|
|
|
9
10
|
editor.then(
|
|
@@ -25,7 +26,13 @@ if (note) {
|
|
|
25
26
|
|
|
26
27
|
new EventSource(note.dataset.changesUrl).addEventListener("message", (event) => {
|
|
27
28
|
if (JSON.parse(event.data) !== note.dataset.note) return;
|
|
28
|
-
if (editing) editing.pull();
|
|
29
|
-
|
|
29
|
+
if (editing) return editing.pull();
|
|
30
|
+
// A touch or a duplicate event leaves the text alone, and reloading then
|
|
31
|
+
// would swallow a click that is opening the editor.
|
|
32
|
+
fetch(note.dataset.saveUrl, { cache: "no-store" })
|
|
33
|
+
.then((response) => response.text())
|
|
34
|
+
.then((text) => {
|
|
35
|
+
if (text !== source) location.reload();
|
|
36
|
+
});
|
|
30
37
|
});
|
|
31
38
|
}
|
|
@@ -25,6 +25,7 @@ VIEWING_ACTIONS = {"leave", "quit", "interrupt", "command_palette"}
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class RunApp(App[None]):
|
|
28
|
+
CSS = "#concurrency { dock: right; width: auto; padding: 0 1; }"
|
|
28
29
|
BINDINGS = [
|
|
29
30
|
Binding("ctrl+c", "interrupt", "Stop runs"),
|
|
30
31
|
Binding("n", "run_next", "Run next"),
|
|
@@ -89,6 +90,7 @@ class RunApp(App[None]):
|
|
|
89
90
|
table.add_column(column, key=column)
|
|
90
91
|
self.query_one(Input).display = False
|
|
91
92
|
self.query_one(Log).display = False
|
|
93
|
+
self.query_one(Header).mount(Static(id="concurrency"))
|
|
92
94
|
table.set_interval(0.1, self.spin)
|
|
93
95
|
|
|
94
96
|
def on_ready(self) -> None:
|
|
@@ -116,7 +118,6 @@ class RunApp(App[None]):
|
|
|
116
118
|
self.pending.add(planned["run"])
|
|
117
119
|
self.redraw(table.cursor_row)
|
|
118
120
|
self.show_concurrency(event)
|
|
119
|
-
self.show(describe_event(event))
|
|
120
121
|
match event["mode"], event["runs"]:
|
|
121
122
|
case "adhoc", [only]:
|
|
122
123
|
self.view(only["run"]) # type: ignore[index]
|
|
@@ -178,7 +179,6 @@ class RunApp(App[None]):
|
|
|
178
179
|
self.show_summary()
|
|
179
180
|
case "concurrency":
|
|
180
181
|
self.show_concurrency(event)
|
|
181
|
-
self.show(describe_event(event))
|
|
182
182
|
case "stopping":
|
|
183
183
|
for pending in self.pending:
|
|
184
184
|
self.set_cell(pending, "status", dim(SKIPPED))
|
|
@@ -217,7 +217,7 @@ class RunApp(App[None]):
|
|
|
217
217
|
|
|
218
218
|
def show_concurrency(self, event: dict[str, object]) -> None:
|
|
219
219
|
self.concurrency = int(event["concurrency"]) # type: ignore[call-overload]
|
|
220
|
-
self.
|
|
220
|
+
self.query_one("#concurrency", Static).update(f"{self.concurrency} at a time")
|
|
221
221
|
|
|
222
222
|
def tick(self) -> None:
|
|
223
223
|
for run, began in self.started.items():
|
|
@@ -330,7 +330,7 @@ class RunApp(App[None]):
|
|
|
330
330
|
self.viewing = None
|
|
331
331
|
self.query_one(Log).display = False
|
|
332
332
|
self.query_one(DataTable).display = True
|
|
333
|
-
self.sub_title =
|
|
333
|
+
self.sub_title = ""
|
|
334
334
|
self.refresh_bindings()
|
|
335
335
|
if self.idle:
|
|
336
336
|
self.show_summary()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|