labnotes 0.0.81__tar.gz → 0.0.83__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.81 → labnotes-0.0.83}/PKG-INFO +1 -1
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/context.md +2 -2
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/group_runs.py +6 -1
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/list_runs.py +4 -1
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/load_experiment.py +2 -0
- labnotes-0.0.83/src/labnotes/modes.py +2 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/read_config.py +1 -1
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/read_experiment_config.py +1 -1
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/run_experiment.py +1 -1
- labnotes-0.0.83/src/labnotes/static/elapsed.js +18 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/static/labnotes.css +2 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/summarize_config.py +2 -2
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/templates/experiment.html +4 -3
- {labnotes-0.0.81 → labnotes-0.0.83}/testing-conventions.toml +2 -2
- {labnotes-0.0.81 → labnotes-0.0.83}/tests/integration/conftest.py +21 -1
- labnotes-0.0.81/src/labnotes/modes.py +0 -2
- {labnotes-0.0.81 → labnotes-0.0.83}/.gitignore +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/CHANGELOG.md +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/MIGRATIONS.md +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/README.md +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/pyproject.toml +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/__main__.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/create_note.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/__init__.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/control_run.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/daemon_app.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/daemon_client.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/daemon_running.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/daemon_server.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/describe_status.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/ensure_daemon.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/exit_when_idle.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/remote_start.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/run_daemon.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/run_logs.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/serve_daemon.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/socket_path.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/stop_runs.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/watch_daemon.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon/watch_status.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/daemon_command.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/format_started.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/init.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/load_run.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/models.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/note_path.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/run_command.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/run_control.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/run_output.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/run_status.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/save_note.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/server.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.0.81 → labnotes-0.0.83}/uv.lock +0 -0
|
@@ -58,11 +58,11 @@ and as `display_name` in `experiments`. The folder name stays the key, so
|
|
|
58
58
|
renaming the experiment means editing `name`.
|
|
59
59
|
Each combination of `[params]` runs `repeats` times (1 if omitted), round-robin, on every
|
|
60
60
|
`run`, whatever is already on disk. That is `mode = "adhoc"`, the default.
|
|
61
|
-
`mode = "
|
|
61
|
+
`mode = "matrix"` counts what is there: only runs that count fill a combination,
|
|
62
62
|
so running again fills in what failed or never ran. `mode` under
|
|
63
63
|
`[experiments]` in `.labnotes.toml` sets it for every experiment that doesn't
|
|
64
64
|
set its own.
|
|
65
|
-
Each run gets a new run folder. In `
|
|
65
|
+
Each run gets a new run folder. In `matrix` mode it is named for the UTC time it was planned
|
|
66
66
|
(`runs/20261020-000429-brave-fox`). In `adhoc` mode a `run` command that plans one run names
|
|
67
67
|
its folder the same way. One that plans more gets one folder named that way, and its runs nest
|
|
68
68
|
inside it (`runs/20261020-000429-brave-fox/quiet-river`); the run id is that path under `runs/`. Either way, folder listings sort by start. `command` runs in a shell from the root, with
|
|
@@ -25,7 +25,12 @@ def group_runs(
|
|
|
25
25
|
"total": totals.get(name, len(members)),
|
|
26
26
|
"shared": shared_params(members, params),
|
|
27
27
|
"passed": sum(1 for run in members if run["labnotes_completed"]),
|
|
28
|
-
"failed": sum(
|
|
28
|
+
"failed": sum(
|
|
29
|
+
1
|
|
30
|
+
for run in members
|
|
31
|
+
if not run["labnotes_completed"] and not run["labnotes_unfinished"]
|
|
32
|
+
),
|
|
33
|
+
"unfinished": sum(1 for run in members if run["labnotes_unfinished"]),
|
|
29
34
|
}
|
|
30
35
|
for name, members in sorted(groups.items(), reverse=True)
|
|
31
36
|
]
|
|
@@ -3,6 +3,8 @@ from dirsql import DirSQL
|
|
|
3
3
|
from labnotes.models import RunView
|
|
4
4
|
from labnotes.sql_quote import quote_identifier, quote_string
|
|
5
5
|
|
|
6
|
+
UNFINISHED = "(ended IS NULL AND exit_code IS NULL AND error IS NULL)"
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
async def list_runs(
|
|
8
10
|
runs: DirSQL, source: str, completed_run: str, view: RunView
|
|
@@ -30,6 +32,7 @@ async def list_runs(
|
|
|
30
32
|
else:
|
|
31
33
|
order = "created DESC, id DESC"
|
|
32
34
|
return await runs.query(
|
|
33
|
-
f"SELECT ({completed_run}) AS labnotes_completed,
|
|
35
|
+
f"SELECT ({completed_run}) AS labnotes_completed, {UNFINISHED} AS labnotes_unfinished,"
|
|
36
|
+
f" * FROM {source}"
|
|
34
37
|
f"{filtered} ORDER BY {order}"
|
|
35
38
|
)
|
|
@@ -8,6 +8,7 @@ from dirsql import DirSQL
|
|
|
8
8
|
from labnotes.check_counts import check_counts
|
|
9
9
|
from labnotes.count_groups import count_groups
|
|
10
10
|
from labnotes.count_runs import count_runs
|
|
11
|
+
from labnotes.daemon.daemon_running import daemon_running
|
|
11
12
|
from labnotes.experiment_notes import experiment_notes
|
|
12
13
|
from labnotes.experiment_runs import experiment_runs
|
|
13
14
|
from labnotes.format_value import format_value
|
|
@@ -82,6 +83,7 @@ async def load_experiment(
|
|
|
82
83
|
for note in notes
|
|
83
84
|
],
|
|
84
85
|
"runs_list": runs,
|
|
86
|
+
"runs_live": await daemon_running(root),
|
|
85
87
|
"run_groups": groups,
|
|
86
88
|
"run_columns": shown,
|
|
87
89
|
"run_view": view,
|
|
@@ -24,7 +24,7 @@ def read_config(path: Path) -> LabnotesConfig:
|
|
|
24
24
|
experiments = values.get("experiments", {})
|
|
25
25
|
mode = experiments.get("mode", DEFAULT_MODE)
|
|
26
26
|
if mode not in MODES:
|
|
27
|
-
raise ValueError(f"{path}: [experiments] mode must be
|
|
27
|
+
raise ValueError(f"{path}: [experiments] mode must be matrix or adhoc")
|
|
28
28
|
concurrency = experiments.get("concurrency", 1)
|
|
29
29
|
if not is_positive_int(concurrency):
|
|
30
30
|
raise ValueError(
|
|
@@ -44,7 +44,7 @@ def read_experiment_config(
|
|
|
44
44
|
raise ValueError(f"{path}: counts must be a SQL condition")
|
|
45
45
|
mode = values.get("mode", mode)
|
|
46
46
|
if mode not in MODES:
|
|
47
|
-
raise ValueError(f"{path}: mode must be
|
|
47
|
+
raise ValueError(f"{path}: mode must be matrix or adhoc")
|
|
48
48
|
concurrency = values.get("concurrency", concurrency)
|
|
49
49
|
if not is_positive_int(concurrency):
|
|
50
50
|
raise ValueError(f"{path}: concurrency must be a positive integer")
|
|
@@ -37,7 +37,7 @@ async def run_experiment(
|
|
|
37
37
|
existing = await experiment_run_ids(tables, name)
|
|
38
38
|
finished: list[dict[str, object]] = []
|
|
39
39
|
match experiment.mode:
|
|
40
|
-
case "
|
|
40
|
+
case "matrix":
|
|
41
41
|
counted = await list_experiment_runs(tables, name, experiment.counts)
|
|
42
42
|
finished = [json.loads(str(r["params"])) for r in counted if r["counted"]]
|
|
43
43
|
planned: set[str] = set()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const format = (seconds) => {
|
|
2
|
+
const minutes = Math.floor(seconds / 60);
|
|
3
|
+
const hours = Math.floor(minutes / 60);
|
|
4
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
5
|
+
if (hours) return `${hours}h ${pad(minutes % 60)}m`;
|
|
6
|
+
if (minutes) return `${minutes}m ${pad(seconds % 60)}s`;
|
|
7
|
+
return `${seconds}s`;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const tick = () => {
|
|
11
|
+
for (const cell of document.querySelectorAll(".elapsed[data-since]")) {
|
|
12
|
+
const seconds = Math.max(0, Math.floor((Date.now() - Date.parse(cell.dataset.since)) / 1000));
|
|
13
|
+
cell.textContent = format(seconds);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
tick();
|
|
18
|
+
setInterval(tick, 1000);
|
|
@@ -359,6 +359,8 @@ h3 {
|
|
|
359
359
|
.status { font-size: 0.8rem; font-weight: 700; }
|
|
360
360
|
.status.good { color: var(--good); }
|
|
361
361
|
.status.bad { color: var(--bad); }
|
|
362
|
+
.status.interrupted, .group-row .interrupted { color: var(--ink-3); }
|
|
363
|
+
.status.running, .group-row .running { color: var(--accent); }
|
|
362
364
|
.counts-error { color: var(--bad); }
|
|
363
365
|
|
|
364
366
|
/* Index view toggle */
|
|
@@ -15,13 +15,13 @@ def summarize_config(config: dict[str, object] | None, mode: str) -> dict[str, o
|
|
|
15
15
|
cells = prod(len(values) for values in varies.values())
|
|
16
16
|
command = config.get("command")
|
|
17
17
|
counts = config.get("counts")
|
|
18
|
-
|
|
18
|
+
matrix = config.get("mode", mode) == "matrix"
|
|
19
19
|
return {
|
|
20
20
|
"fixed": {},
|
|
21
21
|
"varies": varies,
|
|
22
22
|
"cell_count": cells,
|
|
23
23
|
"runs_per_cell": repeats,
|
|
24
24
|
"completed_run": counts if isinstance(counts, str) else DEFAULT_COUNTS,
|
|
25
|
-
"mode": "
|
|
25
|
+
"mode": "matrix" if matrix else "adhoc",
|
|
26
26
|
"other": {} if command is None else {"command": command},
|
|
27
27
|
}
|
|
@@ -82,8 +82,8 @@
|
|
|
82
82
|
</thead>
|
|
83
83
|
{% macro run_rows(run) %}
|
|
84
84
|
<tr class="run-row">
|
|
85
|
-
<td
|
|
86
|
-
{% for column in e.run_columns %}{% if column == 'id' %}<td class="mono"><a class="run-link" href="{{ url_for('run', name=e.name, run=run.id) }}">{{ run.id.partition('/')[2] or run.id if nested else run.id }}</a></td>{% if nested %}<td></td>{% endif %}{% else %}<td class="{{ 'mono' if loop.first }}{{ 'num' if column == 'duration' }}">{{ run[column] | duration if column == 'duration' else (run[column] | started if column == 'started' else run[column] | value) }}</td>{% endif %}{% endfor %}
|
|
85
|
+
<td>{% if run.labnotes_unfinished and e.runs_live %}<span class="status running" title="running">◐</span>{% elif run.labnotes_unfinished %}<span class="status interrupted" title="interrupted">–</span>{% else %}<span class="status {{ 'good' if run.labnotes_completed else 'bad' }}" title="{{ 'complete' if run.labnotes_completed else 'not complete' }}">{{ '✓' if run.labnotes_completed else '✕' }}</span>{% endif %}</td>
|
|
86
|
+
{% for column in e.run_columns %}{% if column == 'id' %}<td class="mono"><a class="run-link" href="{{ url_for('run', name=e.name, run=run.id) }}">{{ run.id.partition('/')[2] or run.id if nested else run.id }}</a></td>{% if nested %}<td></td>{% endif %}{% else %}<td class="{{ 'mono' if loop.first }}{{ 'num' if column == 'duration' }}">{% if column == 'duration' and run.labnotes_unfinished and e.runs_live %}<span class="elapsed" data-since="{{ run.created }}">—</span>{% else %}{{ run[column] | duration if column == 'duration' else (run[column] | started if column == 'started' else run[column] | value) }}{% endif %}</td>{% endif %}{% endfor %}
|
|
87
87
|
</tr>
|
|
88
88
|
{% endmacro %}
|
|
89
89
|
{% if nested %}
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
{% else %}
|
|
94
94
|
<tbody class="group{{ ' open' if narrowed }}" data-name="{{ group.name }}">
|
|
95
95
|
<tr class="group-row">
|
|
96
|
-
<td class="num" title="{{ group.passed }} passed, {{ group.failed }} failed"><span class="good">{{ group.passed }}✓</span> <span class="bad">{{ group.failed }}✕</span
|
|
96
|
+
<td class="num" title="{{ group.passed }} passed, {{ group.failed }} failed{% if group.unfinished %}, {{ group.unfinished }} {{ 'running' if e.runs_live else 'interrupted' }}{% endif %}"><span class="good">{{ group.passed }}✓</span> <span class="bad">{{ group.failed }}✕</span>{% if group.unfinished %} <span class="{{ 'running' if e.runs_live else 'interrupted' }}">{{ group.unfinished }}{{ '◐' if e.runs_live else '–' }}</span>{% endif %}</td>
|
|
97
97
|
<td class="mono"><button type="button" class="group-toggle" aria-expanded="{{ 'true' if narrowed else 'false' }}">{{ group.name }}</button></td>
|
|
98
98
|
<td class="num">{{ group.runs | length }}{% if narrowed %} of {{ group.total }}{% endif %}</td>
|
|
99
99
|
{% for column in e.run_columns[1:] %}<td>{% if column == 'started' %}{{ group.started | started if group.started }}{% elif column in group.shared %}{{ group.shared[column] | value }}{% endif %}</td>{% endfor %}
|
|
@@ -122,4 +122,5 @@
|
|
|
122
122
|
<script src="{{ asset('experiment.js') }}" defer></script>
|
|
123
123
|
<script src="{{ asset('local-time.js') }}" defer></script>
|
|
124
124
|
<script src="{{ asset('row-link.js') }}" defer></script>
|
|
125
|
+
<script src="{{ asset('elapsed.js') }}" defer></script>
|
|
125
126
|
{% endblock %}
|
|
@@ -14,13 +14,13 @@ reason = "last_stream is always the same str object taken from STREAMS, so is no
|
|
|
14
14
|
path = "src/labnotes/runner/run_experiment.py"
|
|
15
15
|
rules = ["mutation"]
|
|
16
16
|
lines = [47]
|
|
17
|
-
reason = "mode is validated to
|
|
17
|
+
reason = "mode is validated to matrix or adhoc; \"matrix\" <= \"adhoc\" is false like ==, and both are interned literals so is equals =="
|
|
18
18
|
|
|
19
19
|
[[python.exempt]]
|
|
20
20
|
path = "src/labnotes/load_experiment.py"
|
|
21
21
|
rules = ["mutation"]
|
|
22
22
|
lines = [59]
|
|
23
|
-
reason = "summarize_config returns mode
|
|
23
|
+
reason = "summarize_config returns mode matrix or adhoc; \"matrix\" <= \"adhoc\" is false like ==, and both are interned literals so is equals =="
|
|
24
24
|
|
|
25
25
|
[[python.exempt]]
|
|
26
26
|
path = "src/labnotes/daemon/exit_when_idle.py"
|
|
@@ -10,8 +10,10 @@ from pathlib import Path
|
|
|
10
10
|
import pytest
|
|
11
11
|
from playwright.async_api import Page, async_playwright
|
|
12
12
|
|
|
13
|
+
from labnotes.daemon.socket_path import socket_path
|
|
14
|
+
|
|
13
15
|
CONFIG = (
|
|
14
|
-
'name = "Alpha study"\ncommand = "go"\nrepeats = 2\nmode = "
|
|
16
|
+
'name = "Alpha study"\ncommand = "go"\nrepeats = 2\nmode = "matrix"\n'
|
|
15
17
|
'[params]\nsource-language = ["en", "fr"]\n'
|
|
16
18
|
)
|
|
17
19
|
|
|
@@ -43,6 +45,24 @@ def free_port() -> int:
|
|
|
43
45
|
return int(sock.getsockname()[1])
|
|
44
46
|
|
|
45
47
|
|
|
48
|
+
def daemon(root: Path) -> Iterator[None]:
|
|
49
|
+
labnotes = Path(sys.executable).parent / "labnotes"
|
|
50
|
+
process = subprocess.Popen(
|
|
51
|
+
[labnotes, "-c", root / ".labnotes.toml", "daemon"],
|
|
52
|
+
stdout=subprocess.DEVNULL,
|
|
53
|
+
stderr=subprocess.DEVNULL,
|
|
54
|
+
)
|
|
55
|
+
try:
|
|
56
|
+
for _ in range(100):
|
|
57
|
+
if socket_path(root).exists():
|
|
58
|
+
break
|
|
59
|
+
time.sleep(0.1)
|
|
60
|
+
yield
|
|
61
|
+
finally:
|
|
62
|
+
process.terminate()
|
|
63
|
+
process.wait()
|
|
64
|
+
|
|
65
|
+
|
|
46
66
|
def serve(root: Path) -> Iterator[str]:
|
|
47
67
|
port = free_port()
|
|
48
68
|
labnotes = Path(sys.executable).parent / "labnotes"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|