labnotes 0.0.33__tar.gz → 0.0.35__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.33 → labnotes-0.0.35}/PKG-INFO +1 -2
- {labnotes-0.0.33 → labnotes-0.0.35}/pyproject.toml +0 -1
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/context.md +5 -1
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/models.py +1 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/execute_run.py +2 -0
- labnotes-0.0.35/src/labnotes/runner/hold_folder.py +15 -0
- labnotes-0.0.35/src/labnotes/runner/new_invocation_id.py +6 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/read_experiment_config.py +6 -2
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/run_experiment.py +15 -3
- labnotes-0.0.35/src/labnotes/runner/runs_parent.py +7 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/start_experiment.py +7 -8
- labnotes-0.0.35/testing-conventions.toml +5 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/uv.lock +0 -11
- labnotes-0.0.33/src/labnotes/runner/lock_path.py +0 -7
- {labnotes-0.0.33 → labnotes-0.0.35}/.gitignore +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/CHANGELOG.md +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/MIGRATIONS.md +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/README.md +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/cell_progress.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/experiment_progress.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/init.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/pivot_cells.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/wait_for_group.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/server.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/stop_idle_kernels.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/templates/macros.html +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.0.33 → labnotes-0.0.35}/src/labnotes/tui/run_tui.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: labnotes
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.35
|
|
4
4
|
Summary: Browse experiment designs, notes, and run data from a folder.
|
|
5
5
|
Project-URL: Repository, https://github.com/thekevinscott/labnotes
|
|
6
6
|
Author-email: Kevin Scott <me@thekevinscott.com>
|
|
@@ -8,7 +8,6 @@ License-Expression: MIT
|
|
|
8
8
|
Requires-Python: >=3.12
|
|
9
9
|
Requires-Dist: click>=8.5.0
|
|
10
10
|
Requires-Dist: dirsql>=0.4.110
|
|
11
|
-
Requires-Dist: filelock>=4.2.0
|
|
12
11
|
Requires-Dist: haikunator>=2.1.0
|
|
13
12
|
Requires-Dist: ipykernel>=7.4.0
|
|
14
13
|
Requires-Dist: jinja2>=3.1.6
|
|
@@ -59,13 +59,17 @@ renaming the experiment means editing `name`.
|
|
|
59
59
|
Each combination of `[params]` runs `repeats` times, round-robin. Only runs
|
|
60
60
|
that count fill a combination, so running again fills in what failed or never
|
|
61
61
|
ran.
|
|
62
|
+
`mode = "adhoc"` skips that: every `run` runs each combination `repeats` times
|
|
63
|
+
again, whatever is already on disk. The default is `mode = "fill"`.
|
|
62
64
|
Each run gets a new run folder. `command` runs in a shell from the root, with
|
|
63
65
|
`{run_dir}` replaced by the run folder's absolute path. Pass it to the
|
|
64
66
|
script so it writes its outputs there.
|
|
65
67
|
Each param is appended as `--name value`, with the name as written (use
|
|
66
68
|
`source-language` for a `--source-language` flag), booleans as 0 or 1, and lists
|
|
67
69
|
and tables as JSON. labnotes writes `run.json` (params, created, ended, exit
|
|
68
|
-
code
|
|
70
|
+
code, and `invocation_id`, shared by every run one `run` command started;
|
|
71
|
+
query it as `run ->> 'invocation_id'`), `stdout.log` and `stderr.log` to the
|
|
72
|
+
run folder.
|
|
69
73
|
A run counts when it exited 0 and its `run.json` doesn't hold
|
|
70
74
|
`"excluded": true`, so excluding a run by hand means adding that key; the next
|
|
71
75
|
`run` replaces it. Other keys you add to `run.json` are kept and show in `run`.
|
|
@@ -15,9 +15,11 @@ async def execute_run(
|
|
|
15
15
|
params: dict[str, object],
|
|
16
16
|
running: set[asyncio.subprocess.Process],
|
|
17
17
|
stopping: asyncio.Event,
|
|
18
|
+
invocation: str,
|
|
18
19
|
) -> dict[str, object]:
|
|
19
20
|
run: dict[str, object] = {
|
|
20
21
|
"params": params,
|
|
22
|
+
"invocation_id": invocation,
|
|
21
23
|
"created": iso_time(time.time()),
|
|
22
24
|
"ended": None,
|
|
23
25
|
"exit_code": None,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import fcntl
|
|
2
|
+
import os
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@contextmanager
|
|
9
|
+
def hold_folder(folder: Path) -> Iterator[None]:
|
|
10
|
+
descriptor = os.open(folder, os.O_RDONLY)
|
|
11
|
+
try:
|
|
12
|
+
fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
13
|
+
yield
|
|
14
|
+
finally:
|
|
15
|
+
os.close(descriptor)
|
|
@@ -5,7 +5,8 @@ from pathlib import Path
|
|
|
5
5
|
from labnotes.default_counts import DEFAULT_COUNTS
|
|
6
6
|
from labnotes.models import ExperimentConfig
|
|
7
7
|
|
|
8
|
-
SETTINGS = {"name", "command", "repeats", "params", "counts"}
|
|
8
|
+
SETTINGS = {"name", "command", "repeats", "params", "counts", "mode"}
|
|
9
|
+
MODES = ("fill", "adhoc")
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def read_experiment_config(path: Path) -> ExperimentConfig:
|
|
@@ -39,4 +40,7 @@ def read_experiment_config(path: Path) -> ExperimentConfig:
|
|
|
39
40
|
counts = values.get("counts", DEFAULT_COUNTS)
|
|
40
41
|
if not isinstance(counts, str) or not counts.strip():
|
|
41
42
|
raise ValueError(f"{path}: counts must be a SQL condition")
|
|
42
|
-
|
|
43
|
+
mode = values.get("mode", "fill")
|
|
44
|
+
if mode not in MODES:
|
|
45
|
+
raise ValueError(f"{path}: mode must be fill or adhoc")
|
|
46
|
+
return ExperimentConfig(command, repeats, params, name, counts, mode)
|
|
@@ -12,6 +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.new_invocation_id import new_invocation_id
|
|
15
16
|
from labnotes.runner.new_run_id import new_run_id
|
|
16
17
|
from labnotes.runner.param_grid import param_grid
|
|
17
18
|
from labnotes.runner.plan_runs import plan_runs
|
|
@@ -30,8 +31,12 @@ async def run_experiment(
|
|
|
30
31
|
report: Report,
|
|
31
32
|
listen: Listen,
|
|
32
33
|
) -> list[dict[str, object]]:
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
finished: list[dict[str, object]] = []
|
|
35
|
+
match experiment.mode:
|
|
36
|
+
case "fill":
|
|
37
|
+
tables = open_tables(config)
|
|
38
|
+
existing = await list_experiment_runs(tables, name, experiment.counts)
|
|
39
|
+
finished = [json.loads(str(r["params"])) for r in existing if r["counted"]]
|
|
35
40
|
planned: set[str] = set()
|
|
36
41
|
|
|
37
42
|
def taken(run: str) -> bool:
|
|
@@ -39,6 +44,7 @@ async def run_experiment(
|
|
|
39
44
|
run in planned or run_folder(config.root, config.runs, name, run).exists()
|
|
40
45
|
)
|
|
41
46
|
|
|
47
|
+
invocation = new_invocation_id()
|
|
42
48
|
plan: list[Entry] = []
|
|
43
49
|
for params in plan_runs(
|
|
44
50
|
param_grid(experiment.params), experiment.repeats, finished
|
|
@@ -163,7 +169,13 @@ async def run_experiment(
|
|
|
163
169
|
report({"event": "started", "run": run, "params": params})
|
|
164
170
|
running[run] = set()
|
|
165
171
|
result = await execute_run(
|
|
166
|
-
config.root,
|
|
172
|
+
config.root,
|
|
173
|
+
folder,
|
|
174
|
+
experiment.command,
|
|
175
|
+
params,
|
|
176
|
+
running[run],
|
|
177
|
+
stopping,
|
|
178
|
+
invocation,
|
|
167
179
|
)
|
|
168
180
|
del running[run]
|
|
169
181
|
report({"event": "ended", "run": run, **result})
|
|
@@ -2,12 +2,11 @@ from collections.abc import Callable
|
|
|
2
2
|
from functools import partial
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from filelock import FileLock, Timeout
|
|
6
|
-
|
|
7
5
|
from labnotes.models import LabnotesConfig, Start
|
|
8
|
-
from labnotes.runner.
|
|
6
|
+
from labnotes.runner.hold_folder import hold_folder
|
|
9
7
|
from labnotes.runner.read_experiment_config import read_experiment_config
|
|
10
8
|
from labnotes.runner.run_experiment import run_experiment
|
|
9
|
+
from labnotes.runner.runs_parent import runs_parent
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
def start_experiment(
|
|
@@ -23,11 +22,11 @@ def start_experiment(
|
|
|
23
22
|
if not experiment_config.is_file():
|
|
24
23
|
raise ValueError(f"No experiment.toml for {name} at {experiment_config}.")
|
|
25
24
|
experiment = read_experiment_config(experiment_config)
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
runs = runs_parent(config.root, config.runs, name)
|
|
26
|
+
runs.mkdir(parents=True, exist_ok=True)
|
|
28
27
|
try:
|
|
29
|
-
with
|
|
28
|
+
with hold_folder(runs):
|
|
30
29
|
start = partial(run_experiment, config, name, experiment, concurrency)
|
|
31
30
|
return drive(start, experiment.name or name)
|
|
32
|
-
except
|
|
33
|
-
raise ValueError(f"{name} is already running ({
|
|
31
|
+
except BlockingIOError as error:
|
|
32
|
+
raise ValueError(f"{name} is already running ({runs} is held).") from error
|
|
@@ -273,15 +273,6 @@ wheels = [
|
|
|
273
273
|
{ url = "https://files.pythonhosted.org/packages/4d/dd/8bc67e7d5ffc1d88aa5af511189dfb76dc4e1e5b808fab186146ef2663e5/executing-2.3.0-py3-none-any.whl", hash = "sha256:736e859c9f8701f11fcf516856f26f562e04776387824b43a35a1dfe21c84122", size = 29393, upload-time = "2026-10-10T14:06:02.777Z" },
|
|
274
274
|
]
|
|
275
275
|
|
|
276
|
-
[[package]]
|
|
277
|
-
name = "filelock"
|
|
278
|
-
version = "4.2.0"
|
|
279
|
-
source = { registry = "https://pypi.org/simple" }
|
|
280
|
-
sdist = { url = "https://files.pythonhosted.org/packages/f4/a9/1af41b37c3279712b22cdc63aac78a52432202b6fe1f9666a2a3d2831fb4/filelock-4.2.0.tar.gz", hash = "sha256:7a60906c75227cf04d0c273afadc8219400f11aeb13cc69591d4f6cdc6c8036e", size = 569667, upload-time = "2026-10-14T20:57:13.11Z" }
|
|
281
|
-
wheels = [
|
|
282
|
-
{ url = "https://files.pythonhosted.org/packages/8e/a3/9bc26acff301fe1aaea1cc3d82a1d57e0a34df3e1cadbfa91ac2dbcdde5c/filelock-4.2.0-py3-none-any.whl", hash = "sha256:2ff5690882e8cdb00ef31fb3d01a3094c29f30985426c59495afb1733f3b7238", size = 135032, upload-time = "2026-10-14T20:57:11.349Z" },
|
|
283
|
-
]
|
|
284
|
-
|
|
285
276
|
[[package]]
|
|
286
277
|
name = "h11"
|
|
287
278
|
version = "0.16.0"
|
|
@@ -473,7 +464,6 @@ source = { editable = "." }
|
|
|
473
464
|
dependencies = [
|
|
474
465
|
{ name = "click" },
|
|
475
466
|
{ name = "dirsql" },
|
|
476
|
-
{ name = "filelock" },
|
|
477
467
|
{ name = "haikunator" },
|
|
478
468
|
{ name = "ipykernel" },
|
|
479
469
|
{ name = "jinja2" },
|
|
@@ -497,7 +487,6 @@ dev = [
|
|
|
497
487
|
requires-dist = [
|
|
498
488
|
{ name = "click", specifier = ">=8.5.0" },
|
|
499
489
|
{ name = "dirsql", specifier = ">=0.4.110" },
|
|
500
|
-
{ name = "filelock", specifier = ">=4.2.0" },
|
|
501
490
|
{ name = "haikunator", specifier = ">=2.1.0" },
|
|
502
491
|
{ name = "ipykernel", specifier = ">=7.4.0" },
|
|
503
492
|
{ name = "jinja2", specifier = ">=3.1.6" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|