labnotes 0.0.11__tar.gz → 0.0.12__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.11 → labnotes-0.0.12}/PKG-INFO +19 -8
- {labnotes-0.0.11 → labnotes-0.0.12}/README.md +18 -7
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/cli.py +22 -2
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/context.md +11 -4
- labnotes-0.0.12/src/labnotes/context_guide.py +22 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/models.py +8 -0
- labnotes-0.0.12/src/labnotes/read_config.py +23 -0
- labnotes-0.0.11/src/labnotes/context_guide.py +0 -7
- {labnotes-0.0.11 → labnotes-0.0.12}/.gitignore +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/CHANGELOG.md +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/MIGRATIONS.md +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/pyproject.toml +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/cell_progress.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/dump_experiments.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/open_experiments.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/open_runs.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/pivot_cells.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/query_experiment.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/read_design.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/read_note.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/run_columns.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/scope_runs.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/stop_idle_kernels.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/study_progress.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/summarize_design.py +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/src/labnotes/templates/macros.html +0 -0
- {labnotes-0.0.11 → labnotes-0.0.12}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: labnotes
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.12
|
|
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>
|
|
@@ -22,12 +22,23 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
Browse experiment designs, notes, and run data from a folder.
|
|
23
23
|
|
|
24
24
|
```sh
|
|
25
|
-
uvx labnotes
|
|
25
|
+
uvx labnotes
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
labnotes reads two folders, named in a `.labnotes.toml` at the project root:
|
|
29
|
+
|
|
30
|
+
```toml
|
|
31
|
+
experiments = "experiments"
|
|
32
|
+
data = "data"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Both keys are optional and default to the values above. Paths are relative to
|
|
36
|
+
`.labnotes.toml`, or absolute, or start with `~`. labnotes reads the file from
|
|
37
|
+
the working folder, or from the path given with `-c`.
|
|
38
|
+
|
|
39
|
+
- `experiments` holds one subfolder per experiment, each with a
|
|
29
40
|
`design.yaml` (with a `study` key) and any number of markdown notes.
|
|
30
|
-
-
|
|
41
|
+
- `data` holds the run data and a `.dirsql.toml` that declares a
|
|
31
42
|
`runs` table with a `study` column. An experiment's runs are the rows whose
|
|
32
43
|
`study` matches its design's.
|
|
33
44
|
|
|
@@ -43,8 +54,9 @@ of values), `runs_per_cell`, and `completed_run`, a SQL condition over the
|
|
|
43
54
|
`-h`/`--host` and `-p`/`--port` change where it listens; `-h 0.0.0.0` (or a
|
|
44
55
|
Tailscale IP) serves beyond localhost. Every other uvicorn option passes
|
|
45
56
|
through: `--reload`, `--workers`, `--proxy-headers`, `--root-path`, the
|
|
46
|
-
`--ssl-*` set, and the rest. `labnotes --help` lists them.
|
|
47
|
-
|
|
57
|
+
`--ssl-*` set, and the rest. `labnotes --help` lists them. `--experiments-folder`
|
|
58
|
+
and `--data-folder`, or `LABNOTES_EXPERIMENTS_FOLDER` and
|
|
59
|
+
`LABNOTES_DATA_FOLDER`, override `.labnotes.toml`. `--json`
|
|
48
60
|
prints the experiments as JSON and exits.
|
|
49
61
|
|
|
50
62
|
## From the command line
|
|
@@ -53,8 +65,7 @@ Name an experiment to print what has run for it as JSON. Add SQL to query
|
|
|
53
65
|
that experiment's runs: `runs` holds only its runs, `main.runs` every run.
|
|
54
66
|
|
|
55
67
|
```sh
|
|
56
|
-
uvx labnotes models-effort "SELECT model, COUNT(*) AS n FROM runs GROUP BY 1"
|
|
57
|
-
--experiments-folder experiments --data-folder data
|
|
68
|
+
uvx labnotes models-effort "SELECT model, COUNT(*) AS n FROM runs GROUP BY 1"
|
|
58
69
|
```
|
|
59
70
|
|
|
60
71
|
## Agent skill
|
|
@@ -3,12 +3,23 @@
|
|
|
3
3
|
Browse experiment designs, notes, and run data from a folder.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
uvx labnotes
|
|
6
|
+
uvx labnotes
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
labnotes reads two folders, named in a `.labnotes.toml` at the project root:
|
|
10
|
+
|
|
11
|
+
```toml
|
|
12
|
+
experiments = "experiments"
|
|
13
|
+
data = "data"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Both keys are optional and default to the values above. Paths are relative to
|
|
17
|
+
`.labnotes.toml`, or absolute, or start with `~`. labnotes reads the file from
|
|
18
|
+
the working folder, or from the path given with `-c`.
|
|
19
|
+
|
|
20
|
+
- `experiments` holds one subfolder per experiment, each with a
|
|
10
21
|
`design.yaml` (with a `study` key) and any number of markdown notes.
|
|
11
|
-
-
|
|
22
|
+
- `data` holds the run data and a `.dirsql.toml` that declares a
|
|
12
23
|
`runs` table with a `study` column. An experiment's runs are the rows whose
|
|
13
24
|
`study` matches its design's.
|
|
14
25
|
|
|
@@ -24,8 +35,9 @@ of values), `runs_per_cell`, and `completed_run`, a SQL condition over the
|
|
|
24
35
|
`-h`/`--host` and `-p`/`--port` change where it listens; `-h 0.0.0.0` (or a
|
|
25
36
|
Tailscale IP) serves beyond localhost. Every other uvicorn option passes
|
|
26
37
|
through: `--reload`, `--workers`, `--proxy-headers`, `--root-path`, the
|
|
27
|
-
`--ssl-*` set, and the rest. `labnotes --help` lists them.
|
|
28
|
-
|
|
38
|
+
`--ssl-*` set, and the rest. `labnotes --help` lists them. `--experiments-folder`
|
|
39
|
+
and `--data-folder`, or `LABNOTES_EXPERIMENTS_FOLDER` and
|
|
40
|
+
`LABNOTES_DATA_FOLDER`, override `.labnotes.toml`. `--json`
|
|
29
41
|
prints the experiments as JSON and exits.
|
|
30
42
|
|
|
31
43
|
## From the command line
|
|
@@ -34,8 +46,7 @@ Name an experiment to print what has run for it as JSON. Add SQL to query
|
|
|
34
46
|
that experiment's runs: `runs` holds only its runs, `main.runs` every run.
|
|
35
47
|
|
|
36
48
|
```sh
|
|
37
|
-
uvx labnotes models-effort "SELECT model, COUNT(*) AS n FROM runs GROUP BY 1"
|
|
38
|
-
--experiments-folder experiments --data-folder data
|
|
49
|
+
uvx labnotes models-effort "SELECT model, COUNT(*) AS n FROM runs GROUP BY 1"
|
|
39
50
|
```
|
|
40
51
|
|
|
41
52
|
## Agent skill
|
|
@@ -9,6 +9,7 @@ from uvicorn.main import main as uvicorn_main
|
|
|
9
9
|
from labnotes.context_guide import context_guide
|
|
10
10
|
from labnotes.dump_experiments import dump_experiments
|
|
11
11
|
from labnotes.query_experiment import query_experiment
|
|
12
|
+
from labnotes.read_config import CONFIG_FILE, read_config
|
|
12
13
|
from labnotes.serve_app import DATA_FOLDER, EXPERIMENTS_FOLDER, KERNEL_IDLE_MINUTES
|
|
13
14
|
|
|
14
15
|
# labnotes owns these; the rest of uvicorn's options pass straight through.
|
|
@@ -23,6 +24,13 @@ OWN_OPTIONS = {"app", "factory", "app_dir", "version", "host", "port"}
|
|
|
23
24
|
@click.version_option(package_name="labnotes")
|
|
24
25
|
@click.argument("experiment", required=False)
|
|
25
26
|
@click.argument("sql", required=False)
|
|
27
|
+
@click.option(
|
|
28
|
+
"-c",
|
|
29
|
+
"--config",
|
|
30
|
+
"config_path",
|
|
31
|
+
type=click.Path(path_type=Path, dir_okay=False, exists=True),
|
|
32
|
+
help=f"Project config. Default: {CONFIG_FILE} in the working folder, if there.",
|
|
33
|
+
)
|
|
26
34
|
@click.option(
|
|
27
35
|
"--experiments-folder",
|
|
28
36
|
type=click.Path(path_type=Path),
|
|
@@ -62,17 +70,29 @@ def main(
|
|
|
62
70
|
ctx: click.Context,
|
|
63
71
|
experiment: str | None,
|
|
64
72
|
sql: str | None,
|
|
73
|
+
config_path: Path | None,
|
|
65
74
|
experiments_folder: Path | None,
|
|
66
75
|
data_folder: Path | None,
|
|
67
76
|
kernel_idle_minutes: int,
|
|
68
77
|
as_json: bool,
|
|
69
78
|
**server_options: object,
|
|
70
79
|
) -> None:
|
|
80
|
+
cwd = Path.cwd()
|
|
81
|
+
config_path = config_path or cwd / CONFIG_FILE
|
|
82
|
+
try:
|
|
83
|
+
config = read_config(config_path) if config_path.is_file() else None
|
|
84
|
+
except ValueError as error:
|
|
85
|
+
raise click.ClickException(str(error)) from error
|
|
71
86
|
if experiment == "context" and sql is None:
|
|
72
|
-
click.echo(context_guide(), nl=False)
|
|
87
|
+
click.echo(context_guide(config, cwd), nl=False)
|
|
73
88
|
return
|
|
89
|
+
if config is not None:
|
|
90
|
+
experiments_folder = experiments_folder or config.experiments_folder
|
|
91
|
+
data_folder = data_folder or config.data_folder
|
|
74
92
|
if experiments_folder is None or data_folder is None:
|
|
75
|
-
raise click.UsageError(
|
|
93
|
+
raise click.UsageError(
|
|
94
|
+
f"--experiments-folder and --data-folder are required without a {CONFIG_FILE}."
|
|
95
|
+
)
|
|
76
96
|
if experiment is not None:
|
|
77
97
|
try:
|
|
78
98
|
result = asyncio.run(
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
## Usage
|
|
2
2
|
|
|
3
|
-
labnotes reads two folders. The experiments folder has one subfolder per experiment, each with a `design.yaml` and markdown notes. The data folder holds run data and a `.dirsql.toml` declaring a `runs` table with a `study` column.
|
|
3
|
+
labnotes reads two folders. The experiments folder has one subfolder per experiment, each with a `design.yaml` and markdown notes. The data folder holds run data and a `.dirsql.toml` declaring a `runs` table with a `study` column. A project that uses labnotes has a `.labnotes.toml` at its root; labnotes reads it from the working folder, so run labnotes from the project root, or pass `-c PATH`. The lines above this guide name the one found and the folders it resolves to, or say there is none. Its keys are optional:
|
|
4
|
+
|
|
5
|
+
```toml
|
|
6
|
+
experiments = "experiments" # the default
|
|
7
|
+
data = "data" # relative to .labnotes.toml, absolute, or ~
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`--experiments-folder` and `--data-folder`, or `LABNOTES_EXPERIMENTS_FOLDER` and `LABNOTES_DATA_FOLDER`, override either key. Without a `.labnotes.toml`, pass both.
|
|
4
11
|
|
|
5
12
|
```sh
|
|
6
|
-
uvx labnotes --
|
|
13
|
+
uvx labnotes --json
|
|
7
14
|
```
|
|
8
15
|
|
|
9
16
|
`--json` lists every experiment: its name, study, the design's fixed and varied conditions, the target run count, and how many runs have run and completed, overall and per cell.
|
|
@@ -13,13 +20,13 @@ uvx labnotes --experiments-folder experiments --data-folder data --json
|
|
|
13
20
|
Name an experiment to see what has run for it, as JSON:
|
|
14
21
|
|
|
15
22
|
```sh
|
|
16
|
-
uvx labnotes models-effort
|
|
23
|
+
uvx labnotes models-effort
|
|
17
24
|
```
|
|
18
25
|
|
|
19
26
|
Add SQL to query its runs. `runs` holds only that experiment's runs, matched on the study in its `design.yaml`; `main.runs` holds every run in the data folder. The query goes through [dirsql](https://dirsql.dev), so it is SQLite and read-only:
|
|
20
27
|
|
|
21
28
|
```sh
|
|
22
|
-
uvx labnotes models-effort "SELECT model, effort, COUNT(*) AS n FROM runs GROUP BY 1, 2"
|
|
29
|
+
uvx labnotes models-effort "SELECT model, effort, COUNT(*) AS n FROM runs GROUP BY 1, 2"
|
|
23
30
|
```
|
|
24
31
|
|
|
25
32
|
stdout is one JSON array of row objects. The columns are whatever the data folder's `.dirsql.toml` declares; `SELECT * FROM runs LIMIT 1` shows them. A run counts as completed when the design's `completed_run` SQL expression is true; the one-experiment JSON prints it, so reuse it in a `WHERE`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
from importlib.resources import files
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from labnotes.read_config import CONFIG_FILE
|
|
6
|
+
from labnotes.models import LabnotesConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def context_guide(config: LabnotesConfig | None, cwd: Path) -> str:
|
|
10
|
+
guide = files("labnotes").joinpath("context.md").read_text(encoding="utf-8")
|
|
11
|
+
if config is None:
|
|
12
|
+
project = (
|
|
13
|
+
f"No {CONFIG_FILE} in {cwd}. If this project uses labnotes, run it"
|
|
14
|
+
f" from the folder that holds {CONFIG_FILE}, or pass -c PATH."
|
|
15
|
+
)
|
|
16
|
+
else:
|
|
17
|
+
project = (
|
|
18
|
+
f"Config: {config.path}\n"
|
|
19
|
+
f"Experiments folder: {config.experiments_folder}\n"
|
|
20
|
+
f"Data folder: {config.data_folder}"
|
|
21
|
+
)
|
|
22
|
+
return f"labnotes {version('labnotes')}\n\n{project}\n\n{guide}"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
@dataclass(frozen=True)
|
|
@@ -7,3 +8,10 @@ class RunView:
|
|
|
7
8
|
descending: bool = False
|
|
8
9
|
status: str | None = None
|
|
9
10
|
filters: dict[str, str] = field(default_factory=dict)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class LabnotesConfig:
|
|
15
|
+
path: Path
|
|
16
|
+
experiments_folder: Path
|
|
17
|
+
data_folder: Path
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import tomllib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from labnotes.models import LabnotesConfig
|
|
5
|
+
|
|
6
|
+
CONFIG_FILE = ".labnotes.toml"
|
|
7
|
+
DEFAULTS = {"experiments": "experiments", "data": "data"}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def read_config(path: Path) -> LabnotesConfig:
|
|
11
|
+
with path.open("rb") as file:
|
|
12
|
+
values = tomllib.load(file)
|
|
13
|
+
unknown = sorted(values.keys() - DEFAULTS.keys())
|
|
14
|
+
if unknown:
|
|
15
|
+
raise ValueError(f"{path}: unknown keys {', '.join(unknown)}")
|
|
16
|
+
folder = path.resolve().parent
|
|
17
|
+
folders = {}
|
|
18
|
+
for key, default in DEFAULTS.items():
|
|
19
|
+
value = values.get(key, default)
|
|
20
|
+
if not isinstance(value, str):
|
|
21
|
+
raise ValueError(f"{path}: {key} must be a string")
|
|
22
|
+
folders[key] = folder / Path(value).expanduser()
|
|
23
|
+
return LabnotesConfig(path.resolve(), folders["experiments"], folders["data"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|