labnotes 0.0.12__tar.gz → 0.0.13__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.12 → labnotes-0.0.13}/.gitignore +2 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/PKG-INFO +4 -14
- {labnotes-0.0.12 → labnotes-0.0.13}/README.md +3 -13
- labnotes-0.0.13/src/labnotes/cli.py +38 -0
- labnotes-0.0.13/src/labnotes/context.md +18 -0
- labnotes-0.0.13/src/labnotes/load_config.py +14 -0
- labnotes-0.0.12/src/labnotes/cli.py → labnotes-0.0.13/src/labnotes/server.py +6 -55
- labnotes-0.0.12/src/labnotes/context.md +0 -34
- labnotes-0.0.12/src/labnotes/dump_experiments.py +0 -15
- labnotes-0.0.12/src/labnotes/query_experiment.py +0 -24
- labnotes-0.0.12/src/labnotes/scope_runs.py +0 -14
- {labnotes-0.0.12 → labnotes-0.0.13}/CHANGELOG.md +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/MIGRATIONS.md +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/pyproject.toml +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/cell_progress.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/models.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/open_experiments.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/open_runs.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/pivot_cells.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/read_design.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/read_note.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/run_columns.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/stop_idle_kernels.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/study_progress.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/summarize_design.py +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/src/labnotes/templates/macros.html +0 -0
- {labnotes-0.0.12 → labnotes-0.0.13}/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.13
|
|
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,7 +22,7 @@ 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 server
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
labnotes reads two folders, named in a `.labnotes.toml` at the project root:
|
|
@@ -54,19 +54,9 @@ of values), `runs_per_cell`, and `completed_run`, a SQL condition over the
|
|
|
54
54
|
`-h`/`--host` and `-p`/`--port` change where it listens; `-h 0.0.0.0` (or a
|
|
55
55
|
Tailscale IP) serves beyond localhost. Every other uvicorn option passes
|
|
56
56
|
through: `--reload`, `--workers`, `--proxy-headers`, `--root-path`, the
|
|
57
|
-
`--ssl-*` set, and the rest. `labnotes --help` lists them. `--experiments-folder`
|
|
57
|
+
`--ssl-*` set, and the rest. `labnotes server --help` lists them. `--experiments-folder`
|
|
58
58
|
and `--data-folder`, or `LABNOTES_EXPERIMENTS_FOLDER` and
|
|
59
|
-
`LABNOTES_DATA_FOLDER`, override `.labnotes.toml`.
|
|
60
|
-
prints the experiments as JSON and exits.
|
|
61
|
-
|
|
62
|
-
## From the command line
|
|
63
|
-
|
|
64
|
-
Name an experiment to print what has run for it as JSON. Add SQL to query
|
|
65
|
-
that experiment's runs: `runs` holds only its runs, `main.runs` every run.
|
|
66
|
-
|
|
67
|
-
```sh
|
|
68
|
-
uvx labnotes models-effort "SELECT model, COUNT(*) AS n FROM runs GROUP BY 1"
|
|
69
|
-
```
|
|
59
|
+
`LABNOTES_DATA_FOLDER`, override `.labnotes.toml`.
|
|
70
60
|
|
|
71
61
|
## Agent skill
|
|
72
62
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Browse experiment designs, notes, and run data from a folder.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
uvx labnotes
|
|
6
|
+
uvx labnotes server
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
labnotes reads two folders, named in a `.labnotes.toml` at the project root:
|
|
@@ -35,19 +35,9 @@ of values), `runs_per_cell`, and `completed_run`, a SQL condition over the
|
|
|
35
35
|
`-h`/`--host` and `-p`/`--port` change where it listens; `-h 0.0.0.0` (or a
|
|
36
36
|
Tailscale IP) serves beyond localhost. Every other uvicorn option passes
|
|
37
37
|
through: `--reload`, `--workers`, `--proxy-headers`, `--root-path`, the
|
|
38
|
-
`--ssl-*` set, and the rest. `labnotes --help` lists them. `--experiments-folder`
|
|
38
|
+
`--ssl-*` set, and the rest. `labnotes server --help` lists them. `--experiments-folder`
|
|
39
39
|
and `--data-folder`, or `LABNOTES_EXPERIMENTS_FOLDER` and
|
|
40
|
-
`LABNOTES_DATA_FOLDER`, override `.labnotes.toml`.
|
|
41
|
-
prints the experiments as JSON and exits.
|
|
42
|
-
|
|
43
|
-
## From the command line
|
|
44
|
-
|
|
45
|
-
Name an experiment to print what has run for it as JSON. Add SQL to query
|
|
46
|
-
that experiment's runs: `runs` holds only its runs, `main.runs` every run.
|
|
47
|
-
|
|
48
|
-
```sh
|
|
49
|
-
uvx labnotes models-effort "SELECT model, COUNT(*) AS n FROM runs GROUP BY 1"
|
|
50
|
-
```
|
|
40
|
+
`LABNOTES_DATA_FOLDER`, override `.labnotes.toml`.
|
|
51
41
|
|
|
52
42
|
## Agent skill
|
|
53
43
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from labnotes.context_guide import context_guide
|
|
6
|
+
from labnotes.load_config import load_config
|
|
7
|
+
from labnotes.read_config import CONFIG_FILE
|
|
8
|
+
from labnotes.server import server
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group(
|
|
12
|
+
invoke_without_command=True,
|
|
13
|
+
help="Manage experiments in a folder. `labnotes context` prints a guide for"
|
|
14
|
+
" coding agents.",
|
|
15
|
+
)
|
|
16
|
+
@click.version_option(package_name="labnotes")
|
|
17
|
+
@click.option(
|
|
18
|
+
"-c",
|
|
19
|
+
"--config",
|
|
20
|
+
"config_path",
|
|
21
|
+
type=click.Path(path_type=Path, dir_okay=False, exists=True),
|
|
22
|
+
help=f"Project config. Default: {CONFIG_FILE} in the working folder, if there.",
|
|
23
|
+
)
|
|
24
|
+
@click.pass_context
|
|
25
|
+
def main(ctx: click.Context, config_path: Path | None) -> None:
|
|
26
|
+
if ctx.invoked_subcommand is None:
|
|
27
|
+
click.echo(ctx.get_help())
|
|
28
|
+
ctx.exit()
|
|
29
|
+
ctx.obj = config_path or Path.cwd() / CONFIG_FILE
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@main.command(help="Print a guide for coding agents.")
|
|
33
|
+
@click.pass_context
|
|
34
|
+
def context(ctx: click.Context) -> None:
|
|
35
|
+
click.echo(context_guide(load_config(ctx), Path.cwd()), nl=False)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
main.add_command(server)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## Usage
|
|
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. 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.
|
|
11
|
+
|
|
12
|
+
The run data lives in the data folder. Query it with [dirsql](https://dirsql.dev) and the data folder's `.dirsql.toml`, which declares the `runs` table:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
uvx dirsql -c data/.dirsql.toml "SELECT * FROM runs LIMIT 1" --format json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`uvx labnotes server` serves the experiments and their runs as a local site.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from labnotes.models import LabnotesConfig
|
|
6
|
+
from labnotes.read_config import read_config
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load_config(ctx: click.Context) -> LabnotesConfig | None:
|
|
10
|
+
path: Path = ctx.find_root().obj
|
|
11
|
+
try:
|
|
12
|
+
return read_config(path) if path.is_file() else None
|
|
13
|
+
except ValueError as error:
|
|
14
|
+
raise click.ClickException(str(error)) from error
|
|
@@ -1,36 +1,18 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import json
|
|
3
1
|
import os
|
|
4
2
|
from pathlib import Path
|
|
5
3
|
|
|
6
4
|
import click
|
|
7
5
|
from uvicorn.main import main as uvicorn_main
|
|
8
6
|
|
|
9
|
-
from labnotes.
|
|
10
|
-
from labnotes.
|
|
11
|
-
from labnotes.query_experiment import query_experiment
|
|
12
|
-
from labnotes.read_config import CONFIG_FILE, read_config
|
|
7
|
+
from labnotes.load_config import load_config
|
|
8
|
+
from labnotes.read_config import CONFIG_FILE
|
|
13
9
|
from labnotes.serve_app import DATA_FOLDER, EXPERIMENTS_FOLDER, KERNEL_IDLE_MINUTES
|
|
14
10
|
|
|
15
11
|
# labnotes owns these; the rest of uvicorn's options pass straight through.
|
|
16
12
|
OWN_OPTIONS = {"app", "factory", "app_dir", "version", "host", "port"}
|
|
17
13
|
|
|
18
14
|
|
|
19
|
-
@click.command(
|
|
20
|
-
help="Browse experiments and their run data. With EXPERIMENT, print what has"
|
|
21
|
-
" run as JSON; with SQL too, query that experiment's runs. `labnotes context`"
|
|
22
|
-
" prints a guide for coding agents."
|
|
23
|
-
)
|
|
24
|
-
@click.version_option(package_name="labnotes")
|
|
25
|
-
@click.argument("experiment", required=False)
|
|
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
|
-
)
|
|
15
|
+
@click.command(help="Serve the experiments and their run data as a local site.")
|
|
34
16
|
@click.option(
|
|
35
17
|
"--experiments-folder",
|
|
36
18
|
type=click.Path(path_type=Path),
|
|
@@ -51,12 +33,6 @@ OWN_OPTIONS = {"app", "factory", "app_dir", "version", "host", "port"}
|
|
|
51
33
|
envvar=KERNEL_IDLE_MINUTES,
|
|
52
34
|
help="Stop a note's Python kernel after this long without a run.",
|
|
53
35
|
)
|
|
54
|
-
@click.option(
|
|
55
|
-
"--json",
|
|
56
|
-
"as_json",
|
|
57
|
-
is_flag=True,
|
|
58
|
-
help="Print experiments as JSON instead of serving.",
|
|
59
|
-
)
|
|
60
36
|
@click.option(
|
|
61
37
|
"-h",
|
|
62
38
|
"--host",
|
|
@@ -66,26 +42,14 @@ OWN_OPTIONS = {"app", "factory", "app_dir", "version", "host", "port"}
|
|
|
66
42
|
)
|
|
67
43
|
@click.option("-p", "--port", type=int, default=8000, show_default=True)
|
|
68
44
|
@click.pass_context
|
|
69
|
-
def
|
|
45
|
+
def server(
|
|
70
46
|
ctx: click.Context,
|
|
71
|
-
experiment: str | None,
|
|
72
|
-
sql: str | None,
|
|
73
|
-
config_path: Path | None,
|
|
74
47
|
experiments_folder: Path | None,
|
|
75
48
|
data_folder: Path | None,
|
|
76
49
|
kernel_idle_minutes: int,
|
|
77
|
-
as_json: bool,
|
|
78
50
|
**server_options: object,
|
|
79
51
|
) -> None:
|
|
80
|
-
|
|
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
|
|
86
|
-
if experiment == "context" and sql is None:
|
|
87
|
-
click.echo(context_guide(config, cwd), nl=False)
|
|
88
|
-
return
|
|
52
|
+
config = load_config(ctx)
|
|
89
53
|
if config is not None:
|
|
90
54
|
experiments_folder = experiments_folder or config.experiments_folder
|
|
91
55
|
data_folder = data_folder or config.data_folder
|
|
@@ -93,19 +57,6 @@ def main(
|
|
|
93
57
|
raise click.UsageError(
|
|
94
58
|
f"--experiments-folder and --data-folder are required without a {CONFIG_FILE}."
|
|
95
59
|
)
|
|
96
|
-
if experiment is not None:
|
|
97
|
-
try:
|
|
98
|
-
result = asyncio.run(
|
|
99
|
-
query_experiment(experiments_folder, data_folder, experiment, sql)
|
|
100
|
-
)
|
|
101
|
-
except (LookupError, RuntimeError) as error:
|
|
102
|
-
raise click.ClickException(str(error)) from error
|
|
103
|
-
click.echo(json.dumps(result, indent=2))
|
|
104
|
-
return
|
|
105
|
-
if as_json:
|
|
106
|
-
experiments = asyncio.run(dump_experiments(experiments_folder, data_folder))
|
|
107
|
-
click.echo(json.dumps(experiments, indent=2))
|
|
108
|
-
return
|
|
109
60
|
# Through the environment so --reload and --workers child processes see them.
|
|
110
61
|
os.environ[EXPERIMENTS_FOLDER] = str(experiments_folder.resolve())
|
|
111
62
|
os.environ[DATA_FOLDER] = str(data_folder.resolve())
|
|
@@ -118,4 +69,4 @@ def main(
|
|
|
118
69
|
)
|
|
119
70
|
|
|
120
71
|
|
|
121
|
-
|
|
72
|
+
server.params += [p for p in uvicorn_main.params if p.name not in OWN_OPTIONS]
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
## Usage
|
|
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. 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.
|
|
11
|
-
|
|
12
|
-
```sh
|
|
13
|
-
uvx labnotes --json
|
|
14
|
-
```
|
|
15
|
-
|
|
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.
|
|
17
|
-
|
|
18
|
-
### One experiment
|
|
19
|
-
|
|
20
|
-
Name an experiment to see what has run for it, as JSON:
|
|
21
|
-
|
|
22
|
-
```sh
|
|
23
|
-
uvx labnotes models-effort
|
|
24
|
-
```
|
|
25
|
-
|
|
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:
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
uvx labnotes models-effort "SELECT model, effort, COUNT(*) AS n FROM runs GROUP BY 1, 2"
|
|
30
|
-
```
|
|
31
|
-
|
|
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`.
|
|
33
|
-
|
|
34
|
-
An unknown experiment, an experiment without a `study`, or bad SQL prints an error to stderr and exits `1`. An experiment named `context` cannot be queried this way.
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
from labnotes.list_experiments import list_experiments
|
|
4
|
-
from labnotes.open_experiments import open_experiments
|
|
5
|
-
from labnotes.open_runs import open_runs
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
async def dump_experiments(
|
|
9
|
-
experiments_folder: Path, data_folder: Path
|
|
10
|
-
) -> list[dict[str, object]]:
|
|
11
|
-
experiments = open_experiments(experiments_folder)
|
|
12
|
-
runs = open_runs(data_folder)
|
|
13
|
-
await experiments.ready()
|
|
14
|
-
await runs.ready()
|
|
15
|
-
return await list_experiments(experiments, runs)
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
from labnotes.list_experiments import list_experiments
|
|
4
|
-
from labnotes.open_experiments import open_experiments
|
|
5
|
-
from labnotes.open_runs import open_runs
|
|
6
|
-
from labnotes.scope_runs import scope_runs
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
async def query_experiment(
|
|
10
|
-
experiments_folder: Path, data_folder: Path, name: str, sql: str | None
|
|
11
|
-
) -> object:
|
|
12
|
-
experiments = open_experiments(experiments_folder)
|
|
13
|
-
runs = open_runs(data_folder)
|
|
14
|
-
await experiments.ready()
|
|
15
|
-
await runs.ready()
|
|
16
|
-
listed = await list_experiments(experiments, runs)
|
|
17
|
-
experiment = next((row for row in listed if row["name"] == name), None)
|
|
18
|
-
if experiment is None:
|
|
19
|
-
raise LookupError(f"no experiment named {name!r}")
|
|
20
|
-
if sql is None:
|
|
21
|
-
return experiment
|
|
22
|
-
if experiment["study"] is None:
|
|
23
|
-
raise LookupError(f"{name!r} has no study in its design.yaml")
|
|
24
|
-
return await runs.query(scope_runs(sql, str(experiment["study"])))
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
from labnotes.sql_quote import quote_string
|
|
4
|
-
|
|
5
|
-
LEADING_WITH = re.compile(r"\s*WITH(\s+RECURSIVE)?\s+", re.IGNORECASE)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def scope_runs(sql: str, study: str) -> str:
|
|
9
|
-
runs = f"runs AS (SELECT * FROM main.runs WHERE study = {quote_string(study)})"
|
|
10
|
-
leading_with = LEADING_WITH.match(sql)
|
|
11
|
-
if leading_with is None:
|
|
12
|
-
return f"WITH {runs} {sql}"
|
|
13
|
-
recursive = " RECURSIVE" if leading_with.group(1) else ""
|
|
14
|
-
return f"WITH{recursive} {runs}, {sql[leading_with.end() :]}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|