labnotes 0.0.100__tar.gz → 0.0.101__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.100 → labnotes-0.0.101}/PKG-INFO +5 -1
- {labnotes-0.0.100 → labnotes-0.0.101}/README.md +4 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/context.md +2 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/context_guide.py +6 -2
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/create_experiment.py +4 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/create_note.py +2 -0
- labnotes-0.0.101/src/labnotes/default_config_text.py +10 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/init.py +2 -1
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/models.py +2 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/note_path.py +1 -1
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/read_config.py +10 -3
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/read_path.py +19 -4
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/prepare_experiment.py +4 -0
- labnotes-0.0.101/src/labnotes/tables/experiment_toml.py +5 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/open_tables.py +20 -6
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/read_blocks.py +4 -2
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/read_experiment.py +6 -3
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/read_file.py +4 -2
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/read_log.py +4 -2
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/read_run.py +4 -2
- labnotes-0.0.100/src/labnotes/default_config_text.py +0 -7
- {labnotes-0.0.100 → labnotes-0.0.101}/.gitignore +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/CHANGELOG.md +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/MIGRATIONS.md +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/pyproject.toml +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/__main__.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/__init__.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/control_run.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/daemon_app.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/daemon_client.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/daemon_running.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/daemon_server.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/describe_status.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/ensure_daemon.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/exit_when_idle.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/remote_start.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/run_daemon.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/run_logs.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/serve_daemon.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/socket_path.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/stop_runs.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/watch_daemon.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon/watch_status.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/daemon_command.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/format_started.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/load_run.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/log.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/log_path.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/modes.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/run_command.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/run_control.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/run_output.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/run_status.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/run_experiment.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/save_note.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/server.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/group_cells.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/invocation_columns.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/invocation_rows.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/run_screen.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/testing-conventions.toml +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/tests/integration/conftest.py +0 -0
- {labnotes-0.0.100 → labnotes-0.0.101}/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.101
|
|
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>
|
|
@@ -49,6 +49,10 @@ names: the experiments path must use `{experiment}`, and the runs path both.
|
|
|
49
49
|
labnotes reads the file from the working folder, or from the path given with
|
|
50
50
|
`-c`.
|
|
51
51
|
|
|
52
|
+
When `experiment.toml` sits next to `.labnotes.toml`, the root is the one
|
|
53
|
+
experiment, named after its folder. Paths then drop `{experiment}`: files
|
|
54
|
+
default to `files/` and runs to `runs/{run}`.
|
|
55
|
+
|
|
52
56
|
`labnotes experiments` reads three tables from those paths:
|
|
53
57
|
|
|
54
58
|
- `experiments`: `name`, and `config`, the experiment's `experiment.toml` as
|
|
@@ -25,6 +25,10 @@ names: the experiments path must use `{experiment}`, and the runs path both.
|
|
|
25
25
|
labnotes reads the file from the working folder, or from the path given with
|
|
26
26
|
`-c`.
|
|
27
27
|
|
|
28
|
+
When `experiment.toml` sits next to `.labnotes.toml`, the root is the one
|
|
29
|
+
experiment, named after its folder. Paths then drop `{experiment}`: files
|
|
30
|
+
default to `files/` and runs to `runs/{run}`.
|
|
31
|
+
|
|
28
32
|
`labnotes experiments` reads three tables from those paths:
|
|
29
33
|
|
|
30
34
|
- `experiments`: `name`, and `config`, the experiment's `experiment.toml` as
|
|
@@ -15,6 +15,8 @@ path = "{experiment}/runs/{run}" # one folder per run
|
|
|
15
15
|
|
|
16
16
|
Paths are relative to the root. `{experiment}` and `{run}` stand for folder names; the experiments and files paths use `{experiment}`, the runs path both. A project laid out differently keeps its folders and changes the paths: look at where its experiments, notes and runs live, then write the templates that match.
|
|
17
17
|
|
|
18
|
+
When `experiment.toml` sits next to `.labnotes.toml`, the root is the one experiment, named after its folder. Paths then drop `{experiment}`: files default to `files/`, runs to `runs/{run}`, and `[experiments] path` is rejected.
|
|
19
|
+
|
|
18
20
|
`labnotes experiments` reads three tables from those paths:
|
|
19
21
|
|
|
20
22
|
- `experiments`: `name`, and `config`, the experiment's `experiment.toml` as
|
|
@@ -17,8 +17,12 @@ def context_guide(config: LabnotesConfig | None, cwd: Path) -> str:
|
|
|
17
17
|
project = (
|
|
18
18
|
f"Config: {config.path}\n"
|
|
19
19
|
f"Root: {config.root}\n"
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
+ (
|
|
21
|
+
f"Experiment: {config.experiment} (the root is the one experiment)\n"
|
|
22
|
+
if config.experiment
|
|
23
|
+
else f"Experiments: {config.experiments}\n"
|
|
24
|
+
)
|
|
25
|
+
+ f"Files: {config.files}\n"
|
|
22
26
|
f"Runs: {config.runs}"
|
|
23
27
|
)
|
|
24
28
|
return f"labnotes {version('labnotes')}\n\n{project}\n\n{guide}"
|
|
@@ -18,6 +18,10 @@ def create_experiment(config: LabnotesConfig, name: str | None) -> Path:
|
|
|
18
18
|
def folder(candidate: str) -> Path:
|
|
19
19
|
return config.root / config.experiments.replace("{experiment}", candidate)
|
|
20
20
|
|
|
21
|
+
if config.experiment is not None:
|
|
22
|
+
raise ValueError(
|
|
23
|
+
f"{config.root} is the experiment {config.experiment}; it can't hold others."
|
|
24
|
+
)
|
|
21
25
|
if name is None:
|
|
22
26
|
name = new_run_id(lambda candidate: folder(candidate).exists())
|
|
23
27
|
if name == "new":
|
|
@@ -8,6 +8,8 @@ def create_note(config: LabnotesConfig, experiment: str) -> Path | None:
|
|
|
8
8
|
def folder(template: str) -> Path:
|
|
9
9
|
return config.root / template.replace("{experiment}", experiment)
|
|
10
10
|
|
|
11
|
+
if config.experiment not in (None, experiment):
|
|
12
|
+
return None
|
|
11
13
|
if experiment.startswith(".") or not folder(config.experiments).is_dir():
|
|
12
14
|
return None
|
|
13
15
|
notes = folder(file_prefix(config.files))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from labnotes.read_path import DEFAULT_PATHS, SINGLE_PATHS
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def default_config_text(*, single: bool = False) -> str:
|
|
5
|
+
paths = SINGLE_PATHS if single else DEFAULT_PATHS
|
|
6
|
+
return "\n".join(
|
|
7
|
+
f'[{table}]\npath = "{template}"\n'
|
|
8
|
+
for table, template in paths.items()
|
|
9
|
+
if template
|
|
10
|
+
)
|
|
@@ -11,7 +11,8 @@ def init() -> None:
|
|
|
11
11
|
path = Path.cwd() / CONFIG_FILE
|
|
12
12
|
try:
|
|
13
13
|
with path.open("x", encoding="utf-8") as file:
|
|
14
|
-
|
|
14
|
+
single = (path.parent / "experiment.toml").is_file()
|
|
15
|
+
file.write(default_config_text(single=single))
|
|
15
16
|
except FileExistsError as error:
|
|
16
17
|
raise click.ClickException(f"{path} already exists.") from error
|
|
17
18
|
click.echo(f"Wrote {path}")
|
|
@@ -10,7 +10,7 @@ def note_path(config: LabnotesConfig, experiment: str, note: str) -> Path | None
|
|
|
10
10
|
if ".." in PurePosixPath(relative).parts or not note.endswith(".md"):
|
|
11
11
|
return None
|
|
12
12
|
found = path_pattern(config.files).fullmatch(relative)
|
|
13
|
-
if found is None or found["experiment"] != experiment:
|
|
13
|
+
if found is None or (config.experiment or found["experiment"]) != experiment:
|
|
14
14
|
return None
|
|
15
15
|
path = config.root / relative
|
|
16
16
|
return path if path.is_relative_to(config.root) and path.is_file() else None
|
|
@@ -18,8 +18,11 @@ def read_config(path: Path) -> LabnotesConfig:
|
|
|
18
18
|
unknown = sorted(values.keys() - DEFAULT_PATHS.keys())
|
|
19
19
|
if unknown:
|
|
20
20
|
raise ValueError(f"{path}: unknown tables {', '.join(unknown)}")
|
|
21
|
+
root = path.resolve().parent
|
|
22
|
+
single = (root / "experiment.toml").is_file()
|
|
21
23
|
paths = {
|
|
22
|
-
table: read_path(path, table, values.get(table, {}))
|
|
24
|
+
table: read_path(path, table, values.get(table, {}), single=single)
|
|
25
|
+
for table in DEFAULT_PATHS
|
|
23
26
|
}
|
|
24
27
|
experiments = values.get("experiments", {})
|
|
25
28
|
mode = experiments.get("mode", DEFAULT_MODE)
|
|
@@ -30,7 +33,11 @@ def read_config(path: Path) -> LabnotesConfig:
|
|
|
30
33
|
raise ValueError(
|
|
31
34
|
f"{path}: [experiments] concurrency must be a positive integer"
|
|
32
35
|
)
|
|
33
|
-
path = path.resolve()
|
|
34
36
|
return LabnotesConfig(
|
|
35
|
-
path
|
|
37
|
+
path.resolve(),
|
|
38
|
+
root,
|
|
39
|
+
**paths,
|
|
40
|
+
mode=mode,
|
|
41
|
+
concurrency=concurrency,
|
|
42
|
+
experiment=root.name if single else None,
|
|
36
43
|
)
|
|
@@ -6,31 +6,46 @@ DEFAULT_PATHS = {
|
|
|
6
6
|
"files": "{experiment}/files/",
|
|
7
7
|
"runs": "{experiment}/runs/{run}",
|
|
8
8
|
}
|
|
9
|
+
# One experiment at the config's folder, so no {experiment} to fill.
|
|
10
|
+
SINGLE_PATHS = {"experiments": "", "files": "files/", "runs": "runs/{run}"}
|
|
9
11
|
PLACEHOLDERS = {
|
|
10
12
|
"experiments": {"experiment"},
|
|
11
13
|
"files": {"experiment"},
|
|
12
14
|
"runs": {"experiment", "run"},
|
|
13
15
|
}
|
|
16
|
+
# experiments is missing: one experiment has no folder to point at.
|
|
17
|
+
SINGLE_PLACEHOLDERS: dict[str, set[str]] = {"files": set(), "runs": {"run"}}
|
|
14
18
|
# Keys other readers own, so the path check lets them through.
|
|
15
19
|
OTHER_KEYS = {"experiments": {"mode", "concurrency"}}
|
|
16
20
|
PLACEHOLDER = re.compile(r"\{([^}]*)\}")
|
|
21
|
+
SINGLE = "experiment.toml sits next to .labnotes.toml, so this folder is the experiment"
|
|
17
22
|
|
|
18
23
|
|
|
19
|
-
def read_path(config: Path, table: str, section: object) -> str:
|
|
24
|
+
def read_path(config: Path, table: str, section: object, single: bool = False) -> str:
|
|
20
25
|
if not isinstance(section, dict):
|
|
21
26
|
raise ValueError(f"{config}: {table} must be a table, like [{table}]")
|
|
22
27
|
unknown = sorted(section.keys() - {"path", *OTHER_KEYS.get(table, ())})
|
|
23
28
|
if unknown:
|
|
24
29
|
raise ValueError(f"{config}: unknown keys in [{table}]: {', '.join(unknown)}")
|
|
25
|
-
|
|
30
|
+
defaults = SINGLE_PATHS if single else DEFAULT_PATHS
|
|
31
|
+
if single and table not in SINGLE_PLACEHOLDERS:
|
|
32
|
+
if "path" in section:
|
|
33
|
+
raise ValueError(f"{config}: [experiments] path doesn't apply; {SINGLE}")
|
|
34
|
+
return SINGLE_PATHS[table]
|
|
35
|
+
template = section.get("path", defaults[table])
|
|
26
36
|
if not isinstance(template, str):
|
|
27
37
|
raise ValueError(f"{config}: [{table}] path must be a string")
|
|
28
38
|
if PurePosixPath(template).is_absolute() or template.startswith("~"):
|
|
29
39
|
raise ValueError(
|
|
30
40
|
f"{config}: [{table}] path must be relative to the config's folder"
|
|
31
41
|
)
|
|
32
|
-
|
|
33
|
-
if
|
|
42
|
+
used = set(PLACEHOLDER.findall(template))
|
|
43
|
+
if single and "experiment" in used:
|
|
44
|
+
raise ValueError(f"{config}: [{table}] path can't use {{experiment}}; {SINGLE}")
|
|
45
|
+
required = SINGLE_PLACEHOLDERS[table] if single else PLACEHOLDERS[table]
|
|
46
|
+
if used != required:
|
|
34
47
|
expected = " and ".join(f"{{{name}}}" for name in sorted(required))
|
|
48
|
+
if not expected:
|
|
49
|
+
raise ValueError(f"{config}: [{table}] path can't use placeholders")
|
|
35
50
|
raise ValueError(f"{config}: [{table}] path must use exactly {expected}")
|
|
36
51
|
return template
|
|
@@ -11,6 +11,10 @@ async def prepare_experiment(
|
|
|
11
11
|
experiment_config: Path | None,
|
|
12
12
|
concurrency: int | None,
|
|
13
13
|
) -> tuple[ExperimentConfig, int]:
|
|
14
|
+
if config.experiment not in (None, name):
|
|
15
|
+
raise ValueError(
|
|
16
|
+
f"No experiment {name}; this project is the experiment {config.experiment}."
|
|
17
|
+
)
|
|
14
18
|
if experiment_config is None:
|
|
15
19
|
folder = config.experiments.replace("{experiment}", name)
|
|
16
20
|
experiment_config = config.root / folder / "experiment.toml"
|
|
@@ -3,6 +3,7 @@ from functools import partial
|
|
|
3
3
|
from dirsql import DirSQL, Table
|
|
4
4
|
|
|
5
5
|
from labnotes.models import LabnotesConfig
|
|
6
|
+
from labnotes.tables.experiment_toml import experiment_toml
|
|
6
7
|
from labnotes.tables.path_glob import path_glob
|
|
7
8
|
from labnotes.tables.read_blocks import read_blocks
|
|
8
9
|
from labnotes.tables.read_experiment import read_experiment
|
|
@@ -66,31 +67,44 @@ def open_tables(config: LabnotesConfig) -> DirSQL:
|
|
|
66
67
|
name="files",
|
|
67
68
|
ddl=FILES,
|
|
68
69
|
glob=path_glob(config.files),
|
|
69
|
-
on_file=partial(
|
|
70
|
+
on_file=partial(
|
|
71
|
+
read_file, root, config.files, experiment=config.experiment
|
|
72
|
+
),
|
|
70
73
|
),
|
|
71
74
|
Table(
|
|
72
75
|
name="runs",
|
|
73
76
|
ddl=RUNS,
|
|
74
77
|
glob=path_glob(f"{config.runs}/run.json"),
|
|
75
|
-
on_file=partial(
|
|
78
|
+
on_file=partial(
|
|
79
|
+
read_run, root, config.runs, experiment=config.experiment
|
|
80
|
+
),
|
|
76
81
|
),
|
|
77
82
|
Table(
|
|
78
83
|
name="logs",
|
|
79
84
|
ddl=LOGS,
|
|
80
85
|
glob=path_glob(f"{config.runs}/std*.log"),
|
|
81
|
-
on_file=partial(
|
|
86
|
+
on_file=partial(
|
|
87
|
+
read_log, root, config.runs, experiment=config.experiment
|
|
88
|
+
),
|
|
82
89
|
),
|
|
83
90
|
Table(
|
|
84
91
|
name="blocks",
|
|
85
92
|
ddl=BLOCKS,
|
|
86
93
|
glob=path_glob(f"{config.runs}/log.jsonl"),
|
|
87
|
-
on_file=partial(
|
|
94
|
+
on_file=partial(
|
|
95
|
+
read_blocks, root, config.runs, experiment=config.experiment
|
|
96
|
+
),
|
|
88
97
|
),
|
|
89
98
|
Table(
|
|
90
99
|
name="experiment_configs",
|
|
91
100
|
ddl=EXPERIMENTS,
|
|
92
|
-
glob=path_glob(
|
|
93
|
-
on_file=partial(
|
|
101
|
+
glob=path_glob(experiment_toml(config.experiments)),
|
|
102
|
+
on_file=partial(
|
|
103
|
+
read_experiment,
|
|
104
|
+
root,
|
|
105
|
+
config.experiments,
|
|
106
|
+
experiment=config.experiment,
|
|
107
|
+
),
|
|
94
108
|
),
|
|
95
109
|
],
|
|
96
110
|
# Run folders are often gitignored (DVC, large outputs); scan them anyway.
|
|
@@ -3,14 +3,16 @@ from pathlib import Path
|
|
|
3
3
|
from labnotes.tables.path_pattern import path_pattern
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def read_blocks(
|
|
6
|
+
def read_blocks(
|
|
7
|
+
root: Path, template: str, path: str, experiment: str | None = None
|
|
8
|
+
) -> list[dict[str, object]]:
|
|
7
9
|
relative = Path(path).relative_to(root).as_posix()
|
|
8
10
|
found = path_pattern(f"{template}/log.jsonl").fullmatch(relative)
|
|
9
11
|
if found is None:
|
|
10
12
|
return []
|
|
11
13
|
return [
|
|
12
14
|
{
|
|
13
|
-
"experiment": found["experiment"],
|
|
15
|
+
"experiment": experiment or found["experiment"],
|
|
14
16
|
"run": found["run"],
|
|
15
17
|
"size": Path(path).stat().st_size,
|
|
16
18
|
}
|
|
@@ -2,19 +2,22 @@ import json
|
|
|
2
2
|
import tomllib
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
+
from labnotes.tables.experiment_toml import experiment_toml
|
|
5
6
|
from labnotes.tables.path_pattern import path_pattern
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
def read_experiment(
|
|
9
|
+
def read_experiment(
|
|
10
|
+
root: Path, template: str, path: str, experiment: str | None = None
|
|
11
|
+
) -> list[dict[str, object]]:
|
|
9
12
|
relative = Path(path).relative_to(root).as_posix()
|
|
10
|
-
found = path_pattern(
|
|
13
|
+
found = path_pattern(experiment_toml(template)).fullmatch(relative)
|
|
11
14
|
if found is None:
|
|
12
15
|
return []
|
|
13
16
|
source = Path(path).read_text(encoding="utf-8")
|
|
14
17
|
config = tomllib.loads(source)
|
|
15
18
|
return [
|
|
16
19
|
{
|
|
17
|
-
"experiment": found["experiment"],
|
|
20
|
+
"experiment": experiment or found["experiment"],
|
|
18
21
|
"config": json.dumps(config),
|
|
19
22
|
"source": source,
|
|
20
23
|
}
|
|
@@ -6,7 +6,9 @@ from labnotes.tables.note_text import note_text
|
|
|
6
6
|
from labnotes.tables.path_pattern import path_pattern
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def read_file(
|
|
9
|
+
def read_file(
|
|
10
|
+
root: Path, template: str, path: str, experiment: str | None = None
|
|
11
|
+
) -> list[dict[str, object]]:
|
|
10
12
|
relative = Path(path).relative_to(root).as_posix()
|
|
11
13
|
found = path_pattern(template).fullmatch(relative)
|
|
12
14
|
prefix = path_pattern(file_prefix(template).rstrip("/")).match(relative)
|
|
@@ -15,7 +17,7 @@ def read_file(root: Path, template: str, path: str) -> list[dict[str, object]]:
|
|
|
15
17
|
stat = Path(path).stat()
|
|
16
18
|
return [
|
|
17
19
|
{
|
|
18
|
-
"experiment": found["experiment"],
|
|
20
|
+
"experiment": experiment or found["experiment"],
|
|
19
21
|
"filename": relative[prefix.end() :].lstrip("/"),
|
|
20
22
|
"path": relative,
|
|
21
23
|
"ctime": iso_time(stat.st_ctime),
|
|
@@ -5,14 +5,16 @@ from labnotes.tables.path_pattern import path_pattern
|
|
|
5
5
|
STREAMS = ("stdout", "stderr")
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def read_log(
|
|
8
|
+
def read_log(
|
|
9
|
+
root: Path, template: str, path: str, experiment: str | None = None
|
|
10
|
+
) -> list[dict[str, object]]:
|
|
9
11
|
relative = Path(path).relative_to(root).as_posix()
|
|
10
12
|
found = path_pattern(f"{template}/{{stream}}.log").fullmatch(relative)
|
|
11
13
|
if found is None or found["stream"] not in STREAMS:
|
|
12
14
|
return []
|
|
13
15
|
return [
|
|
14
16
|
{
|
|
15
|
-
"experiment": found["experiment"],
|
|
17
|
+
"experiment": experiment or found["experiment"],
|
|
16
18
|
"run": found["run"],
|
|
17
19
|
"stream": found["stream"],
|
|
18
20
|
"size": Path(path).stat().st_size,
|
|
@@ -4,7 +4,9 @@ from pathlib import Path
|
|
|
4
4
|
from labnotes.tables.path_pattern import path_pattern
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def read_run(
|
|
7
|
+
def read_run(
|
|
8
|
+
root: Path, template: str, path: str, experiment: str | None = None
|
|
9
|
+
) -> list[dict[str, object]]:
|
|
8
10
|
relative = Path(path).relative_to(root).as_posix()
|
|
9
11
|
found = path_pattern(f"{template}/run.json").fullmatch(relative)
|
|
10
12
|
if found is None:
|
|
@@ -13,7 +15,7 @@ def read_run(root: Path, template: str, path: str) -> list[dict[str, object]]:
|
|
|
13
15
|
folder = relative.removesuffix("/run.json")
|
|
14
16
|
return [
|
|
15
17
|
{
|
|
16
|
-
"experiment": found["experiment"],
|
|
18
|
+
"experiment": experiment or found["experiment"],
|
|
17
19
|
"id": found["run"],
|
|
18
20
|
"folder": folder,
|
|
19
21
|
"created": run.get("created"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|