labnotes 0.0.127__tar.gz → 0.0.129__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.127 → labnotes-0.0.129}/PKG-INFO +1 -1
- labnotes-0.0.129/src/labnotes/compose_experiment.py +9 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/context.md +16 -4
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/daemon_app.py +1 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/remote_start.py +4 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/models.py +1 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/execute_run.py +2 -1
- labnotes-0.0.129/src/labnotes/runner/fill_experiment_env.py +31 -0
- labnotes-0.0.129/src/labnotes/runner/fill_ids.py +8 -0
- labnotes-0.0.129/src/labnotes/runner/hook_ids.py +14 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/invocation_hook.py +4 -1
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/invocation_meta.py +4 -1
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/read_experiment_config.py +10 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/run_experiment.py +55 -6
- labnotes-0.0.127/src/labnotes/compose_experiment.py +0 -8
- {labnotes-0.0.127 → labnotes-0.0.129}/.gitignore +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/CHANGELOG.md +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/MIGRATIONS.md +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/README.md +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/pyproject.toml +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/__main__.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/create_note.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/__init__.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/control_run.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/daemon_client.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/daemon_running.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/daemon_server.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/describe_status.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/ensure_daemon.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/exit_when_idle.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/finalize_lost_runs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/last_stderr.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/run_daemon.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/run_logs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/serve_daemon.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/socket_path.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/stop_runs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/watch_daemon.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon/watch_status.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/daemon_command.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/format_started.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/grid_cell.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/init.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/is_non_negative_number.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/load_run.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/log.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/log_path.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/memory_interval.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/modes.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/note_path.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/parse_subset.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/pivot_runs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/retries.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/run_command.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/run_control.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/run_output.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/run_status.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/cgroup_memory.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/check_args.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/check_subset.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/container_cgroup.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/container_memory.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/docker_output.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/flag_name.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/in_subset.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/record_memory.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/runs_view_command.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/save_note.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/server.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/read_memory.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/read_run_file.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/runs_columns.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/check_view_response.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/format_cell.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/group_cells.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/invocation_columns.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/invocation_rows.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/native_rows.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/run_screen.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/runs_view.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/view_column.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/tui/view_process.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/utils/process_alive.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/utils/tree_usage.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/view/__init__.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/view/answer.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/view/build.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/view/respond.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/view/serve.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/testing-conventions.toml +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/tests/integration/conftest.py +0 -0
- {labnotes-0.0.127 → labnotes-0.0.129}/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.129
|
|
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>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
def compose_experiment(
|
|
2
|
+
base: dict[str, object], layer: dict[str, object]
|
|
3
|
+
) -> dict[str, object]:
|
|
4
|
+
composed = {**base, **layer}
|
|
5
|
+
for key in ("params", "env"):
|
|
6
|
+
base_values, layer_values = base.get(key), layer.get(key)
|
|
7
|
+
if isinstance(base_values, dict) and isinstance(layer_values, dict):
|
|
8
|
+
composed[key] = {**base_values, **layer_values}
|
|
9
|
+
return composed
|
|
@@ -15,7 +15,7 @@ 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. An `[experiment]` table in `.labnotes.toml` holds the same keys as `experiment.toml` and is the base each `experiment.toml` layers over (top-level keys replace, `params` merge by name). With no `[experiments] path`, it also makes the root the one experiment.
|
|
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. An `[experiment]` table in `.labnotes.toml` holds the same keys as `experiment.toml` and is the base each `experiment.toml` layers over (top-level keys replace, `params` and `env` merge by name). With no `[experiments] path`, it also makes the root the one experiment.
|
|
19
19
|
|
|
20
20
|
`labnotes experiments` reads three tables from those paths:
|
|
21
21
|
|
|
@@ -72,12 +72,15 @@ Each run gets a new run folder. In `matrix` mode it is named for the UTC time it
|
|
|
72
72
|
its folder the same way. One that plans more gets one folder named that way, and its runs nest
|
|
73
73
|
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
|
|
74
74
|
`{run_dir}` replaced by the run folder's absolute path. Pass it to the
|
|
75
|
-
script so it writes its outputs there.
|
|
75
|
+
script so it writes its outputs there. `{run_id}` becomes the run id, and
|
|
76
|
+
`{invocation_id}` an id every run of one `run` command shares: the run id when
|
|
77
|
+
it plans one run, else the adhoc invocation folder's name, or in `matrix` mode a
|
|
78
|
+
stamped id that names no folder. All three are shell-quoted.
|
|
76
79
|
Each param is appended as `--name value`, with the name as written (use
|
|
77
80
|
`source-language` for a `--source-language` flag), booleans as 0 or 1, and lists
|
|
78
81
|
and tables as JSON. labnotes writes `run.json` (params, `command`, created,
|
|
79
82
|
ended, exit code, `ended_by`, `signal`), `stdout.log` and `stderr.log` to the
|
|
80
|
-
run folder. `command` is the shell command the run executed, with
|
|
83
|
+
run folder. `command` is the shell command the run executed, with the placeholders
|
|
81
84
|
and the params filled in, written before the run starts, so a killed run has it
|
|
82
85
|
and a later edit to `experiment.toml` doesn't hide what a run did. `ended_by` is `"timeout"`, `"stop"` (labnotes stopped it),
|
|
83
86
|
`"signal"` (a signal labnotes didn't send, like the out-of-memory killer),
|
|
@@ -93,7 +96,16 @@ the value from the start, even if the code changed while they waited.
|
|
|
93
96
|
commands run once per `run` from the root: setup before the first run starts,
|
|
94
97
|
teardown after the last one ends, even if runs failed, were stopped, or setup
|
|
95
98
|
failed (so teardown must cope with a partial setup). A failing setup starts no
|
|
96
|
-
run; a failing teardown fails the invocation.
|
|
99
|
+
run; a failing teardown fails the invocation. Both get `{invocation_id}`, and
|
|
100
|
+
`{run_id}` only when one run is planned; otherwise `{run_id}` there fails the
|
|
101
|
+
invocation before anything starts.
|
|
102
|
+
`[env]` in `experiment.toml` sets string environment values for `command`,
|
|
103
|
+
`meta`, `setup`, and `teardown`. `[experiment.env]` in `.labnotes.toml`
|
|
104
|
+
provides shared values; experiment values replace them by name. Variables in
|
|
105
|
+
the environment that starts `labnotes experiments NAME run` win over both,
|
|
106
|
+
including inline assignments. Values can use raw `{run_dir}`, `{run_id}` and
|
|
107
|
+
`{invocation_id}` placeholders. In hooks and meta, `{run_id}` and `{run_dir}`
|
|
108
|
+
require exactly one planned run. `LABNOTES_*` names are reserved for labnotes.
|
|
97
109
|
A run counts when it exited 0 and its `run.json` doesn't hold
|
|
98
110
|
`"excluded": true`, so excluding a run by hand means adding that key; the next
|
|
99
111
|
`run` replaces it. Other keys you add to `run.json` are kept and show in `run`.
|
|
@@ -109,6 +109,7 @@ def daemon_app(config: LabnotesConfig) -> Starlette:
|
|
|
109
109
|
body.get("timeout"), # type: ignore[arg-type]
|
|
110
110
|
only=body.get("only"), # type: ignore[arg-type]
|
|
111
111
|
args=body.get("args"), # type: ignore[arg-type]
|
|
112
|
+
environment=body.get("environment"), # type: ignore[arg-type]
|
|
112
113
|
)
|
|
113
114
|
# Clients hear why the session ended; the daemon keeps serving.
|
|
114
115
|
except Exception as failure: # noqa: BLE001
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import dataclasses
|
|
3
|
+
import os
|
|
3
4
|
from collections.abc import Callable
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
@@ -21,6 +22,8 @@ def remote_start(
|
|
|
21
22
|
setup: str | None = None,
|
|
22
23
|
teardown: str | None = None,
|
|
23
24
|
) -> Start:
|
|
25
|
+
environment = dict(os.environ)
|
|
26
|
+
|
|
24
27
|
async def start(report: Report, listen: Listen) -> list[dict[str, object]]:
|
|
25
28
|
try:
|
|
26
29
|
return await follow(report, listen)
|
|
@@ -54,6 +57,7 @@ def remote_start(
|
|
|
54
57
|
"args": args,
|
|
55
58
|
"setup": setup,
|
|
56
59
|
"teardown": teardown,
|
|
60
|
+
"environment": environment,
|
|
57
61
|
},
|
|
58
62
|
)
|
|
59
63
|
if reply.status_code != 202:
|
|
@@ -34,6 +34,7 @@ async def execute_run(
|
|
|
34
34
|
args: Sequence[str] = (),
|
|
35
35
|
*,
|
|
36
36
|
memory_interval: float,
|
|
37
|
+
environment: dict[str, str] | None = None,
|
|
37
38
|
) -> dict[str, object]:
|
|
38
39
|
resolved = shell_command(command, folder, params, args)
|
|
39
40
|
run: dict[str, object] = {
|
|
@@ -64,7 +65,7 @@ async def execute_run(
|
|
|
64
65
|
env={
|
|
65
66
|
"FORCE_COLOR": "1",
|
|
66
67
|
"CLICOLOR_FORCE": "1",
|
|
67
|
-
**os.environ,
|
|
68
|
+
**(os.environ if environment is None else environment),
|
|
68
69
|
LOG_ENV: str(folder / "log.jsonl"),
|
|
69
70
|
RUN_ENV: str(folder.resolve()),
|
|
70
71
|
},
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
|
|
3
|
+
from labnotes.runner.fill_ids import fill_ids
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def fill_experiment_env(
|
|
7
|
+
configured: Mapping[str, str],
|
|
8
|
+
inherited: Mapping[str, str],
|
|
9
|
+
ids: Mapping[str, str],
|
|
10
|
+
*,
|
|
11
|
+
hook: str | None = None,
|
|
12
|
+
runs: int = 1,
|
|
13
|
+
) -> dict[str, str]:
|
|
14
|
+
caller = {
|
|
15
|
+
key: value
|
|
16
|
+
for key, value in inherited.items()
|
|
17
|
+
if not key.startswith("LABNOTES_")
|
|
18
|
+
}
|
|
19
|
+
filled: dict[str, str] = {}
|
|
20
|
+
for key, value in configured.items():
|
|
21
|
+
if key in caller:
|
|
22
|
+
continue
|
|
23
|
+
for name in ("run_dir", "run_id"):
|
|
24
|
+
if f"{{{name}}}" in value and name not in ids:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
f"{hook} env.{key} uses {{{name}}}, but this invocation plans "
|
|
27
|
+
f"{runs} runs. {{{name}}} needs exactly one; "
|
|
28
|
+
"use {invocation_id} instead."
|
|
29
|
+
)
|
|
30
|
+
filled[key] = fill_ids(value, ids, quote=False)
|
|
31
|
+
return {**filled, **caller}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import shlex
|
|
2
|
+
from collections.abc import Mapping
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def fill_ids(command: str, ids: Mapping[str, str], *, quote: bool = True) -> str:
|
|
6
|
+
for name, value in ids.items():
|
|
7
|
+
command = command.replace(f"{{{name}}}", shlex.quote(value) if quote else value)
|
|
8
|
+
return command
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
def hook_ids(
|
|
2
|
+
hooks: dict[str, str | None], invocation: str, runs: list[str]
|
|
3
|
+
) -> dict[str, str]:
|
|
4
|
+
match runs:
|
|
5
|
+
case [run]:
|
|
6
|
+
return {"run_id": run, "invocation_id": invocation}
|
|
7
|
+
for hook, command in hooks.items():
|
|
8
|
+
if command is not None and "{run_id}" in command:
|
|
9
|
+
raise ValueError(
|
|
10
|
+
f"{hook} command {command} uses {{run_id}}, but this invocation plans "
|
|
11
|
+
f"{len(runs)} runs. {{run_id}} needs exactly one; "
|
|
12
|
+
"use {invocation_id} instead."
|
|
13
|
+
)
|
|
14
|
+
return {"invocation_id": invocation}
|
|
@@ -2,12 +2,15 @@ import asyncio
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
async def invocation_hook(
|
|
5
|
+
async def invocation_hook(
|
|
6
|
+
root: Path, hook: str, command: str, environment: dict[str, str] | None = None
|
|
7
|
+
) -> None:
|
|
6
8
|
process = await asyncio.create_subprocess_shell(
|
|
7
9
|
command,
|
|
8
10
|
cwd=root,
|
|
9
11
|
stdout=asyncio.subprocess.DEVNULL,
|
|
10
12
|
stderr=asyncio.subprocess.PIPE,
|
|
13
|
+
env=environment,
|
|
11
14
|
)
|
|
12
15
|
_, stderr = await process.communicate()
|
|
13
16
|
if process.returncode != 0:
|
|
@@ -3,12 +3,15 @@ import json
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
async def invocation_meta(
|
|
6
|
+
async def invocation_meta(
|
|
7
|
+
root: Path, command: str, environment: dict[str, str] | None = None
|
|
8
|
+
) -> dict[str, object]:
|
|
7
9
|
process = await asyncio.create_subprocess_shell(
|
|
8
10
|
command,
|
|
9
11
|
cwd=root,
|
|
10
12
|
stdout=asyncio.subprocess.PIPE,
|
|
11
13
|
stderr=asyncio.subprocess.PIPE,
|
|
14
|
+
env=environment,
|
|
12
15
|
)
|
|
13
16
|
stdout, stderr = await process.communicate()
|
|
14
17
|
detail = stderr.decode(errors="replace").strip()
|
|
@@ -26,6 +26,7 @@ SETTINGS = {
|
|
|
26
26
|
"views",
|
|
27
27
|
"setup",
|
|
28
28
|
"teardown",
|
|
29
|
+
"env",
|
|
29
30
|
}
|
|
30
31
|
HOOKS = ("setup", "teardown")
|
|
31
32
|
VIEWS = {"runs"}
|
|
@@ -88,6 +89,14 @@ def read_experiment_config(
|
|
|
88
89
|
run = values.get(hook)
|
|
89
90
|
if run is not None and (not isinstance(run, str) or not run.strip()):
|
|
90
91
|
raise ValueError(f"{path}: {hook} must be a string")
|
|
92
|
+
env = values.get("env", {})
|
|
93
|
+
if not isinstance(env, dict):
|
|
94
|
+
raise ValueError(f"{path}: env must be a table, like [env]")
|
|
95
|
+
for key, value in env.items():
|
|
96
|
+
if key.startswith("LABNOTES_"):
|
|
97
|
+
raise ValueError(f"{path}: env.{key} is reserved for labnotes")
|
|
98
|
+
if not isinstance(value, str):
|
|
99
|
+
raise ValueError(f"{path}: env.{key} must be a string")
|
|
91
100
|
views = values.get("views", {})
|
|
92
101
|
if not isinstance(views, dict):
|
|
93
102
|
raise ValueError(f"{path}: views must be a table, like [views]")
|
|
@@ -110,4 +119,5 @@ def read_experiment_config(
|
|
|
110
119
|
memory_interval,
|
|
111
120
|
values.get("setup"),
|
|
112
121
|
values.get("teardown"),
|
|
122
|
+
env,
|
|
113
123
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
|
+
import os
|
|
3
4
|
import signal
|
|
4
5
|
|
|
5
6
|
import psutil
|
|
@@ -13,6 +14,9 @@ from labnotes.models import (
|
|
|
13
14
|
)
|
|
14
15
|
from labnotes.retries import RETRY_EXIT_CODE, RETRY_SECONDS
|
|
15
16
|
from labnotes.runner.execute_run import execute_run
|
|
17
|
+
from labnotes.runner.fill_experiment_env import fill_experiment_env
|
|
18
|
+
from labnotes.runner.fill_ids import fill_ids
|
|
19
|
+
from labnotes.runner.hook_ids import hook_ids
|
|
16
20
|
from labnotes.runner.in_subset import in_subset
|
|
17
21
|
from labnotes.runner.invocation_hook import invocation_hook
|
|
18
22
|
from labnotes.runner.invocation_meta import invocation_meta
|
|
@@ -40,10 +44,9 @@ async def run_experiment(
|
|
|
40
44
|
retry_seconds: float = RETRY_SECONDS,
|
|
41
45
|
only: dict[str, str] | None = None,
|
|
42
46
|
args: list[str] | None = None,
|
|
47
|
+
environment: dict[str, str] | None = None,
|
|
43
48
|
) -> list[dict[str, object]]:
|
|
44
|
-
|
|
45
|
-
if experiment.meta is not None:
|
|
46
|
-
meta = await invocation_meta(config.root, experiment.meta)
|
|
49
|
+
inherited = dict(os.environ) if environment is None else environment
|
|
47
50
|
tables = open_tables(config)
|
|
48
51
|
existing = await experiment_run_ids(tables, name)
|
|
49
52
|
finished: list[dict[str, object]] = []
|
|
@@ -77,6 +80,36 @@ async def run_experiment(
|
|
|
77
80
|
run = new_id(params)
|
|
78
81
|
planned.add(run)
|
|
79
82
|
plan.append((run, params))
|
|
83
|
+
hook_run_dir: str | None
|
|
84
|
+
match plan:
|
|
85
|
+
case [(invocation, _)]:
|
|
86
|
+
hook_run_dir = str(
|
|
87
|
+
run_folder(config.root, config.runs, name, invocation).resolve()
|
|
88
|
+
)
|
|
89
|
+
case _:
|
|
90
|
+
invocation = group or new_stamped_run_id(taken)
|
|
91
|
+
hook_run_dir = None
|
|
92
|
+
planned.add(invocation)
|
|
93
|
+
hooks = {
|
|
94
|
+
"setup": experiment.setup,
|
|
95
|
+
"teardown": experiment.teardown,
|
|
96
|
+
"meta": experiment.meta,
|
|
97
|
+
}
|
|
98
|
+
ids = hook_ids(hooks, invocation, [run for run, _ in plan])
|
|
99
|
+
if hook_run_dir is not None:
|
|
100
|
+
ids["run_dir"] = hook_run_dir
|
|
101
|
+
hook_env = {
|
|
102
|
+
hook: fill_experiment_env(
|
|
103
|
+
experiment.env, inherited, ids, hook=hook, runs=len(plan)
|
|
104
|
+
)
|
|
105
|
+
for hook, command in hooks.items()
|
|
106
|
+
if command is not None
|
|
107
|
+
}
|
|
108
|
+
meta = None
|
|
109
|
+
if experiment.meta is not None:
|
|
110
|
+
meta = await invocation_meta(
|
|
111
|
+
config.root, fill_ids(experiment.meta, ids), hook_env["meta"]
|
|
112
|
+
)
|
|
80
113
|
runs = [{"run": run, "params": params} for run, params in plan]
|
|
81
114
|
report(
|
|
82
115
|
{
|
|
@@ -212,7 +245,7 @@ async def run_experiment(
|
|
|
212
245
|
result = await execute_run(
|
|
213
246
|
config.root,
|
|
214
247
|
folder,
|
|
215
|
-
experiment.command,
|
|
248
|
+
fill_ids(experiment.command, {"run_id": run, "invocation_id": invocation}),
|
|
216
249
|
params,
|
|
217
250
|
running[run],
|
|
218
251
|
stopping,
|
|
@@ -224,6 +257,15 @@ async def run_experiment(
|
|
|
224
257
|
meta=meta,
|
|
225
258
|
args=args or (),
|
|
226
259
|
memory_interval=experiment.memory_interval,
|
|
260
|
+
environment=fill_experiment_env(
|
|
261
|
+
experiment.env,
|
|
262
|
+
inherited,
|
|
263
|
+
{
|
|
264
|
+
"run_dir": str(folder.resolve()),
|
|
265
|
+
"run_id": run,
|
|
266
|
+
"invocation_id": invocation,
|
|
267
|
+
},
|
|
268
|
+
),
|
|
227
269
|
)
|
|
228
270
|
del running[run], trees[run]
|
|
229
271
|
report({"event": "ended", "run": run, **result})
|
|
@@ -237,14 +279,21 @@ async def run_experiment(
|
|
|
237
279
|
async def teardown() -> None:
|
|
238
280
|
if experiment.teardown is not None:
|
|
239
281
|
report({"event": "tearing down"})
|
|
240
|
-
await invocation_hook(
|
|
282
|
+
await invocation_hook(
|
|
283
|
+
config.root,
|
|
284
|
+
"teardown",
|
|
285
|
+
fill_ids(experiment.teardown, ids),
|
|
286
|
+
hook_env["teardown"],
|
|
287
|
+
)
|
|
241
288
|
|
|
242
289
|
listen(Controls(interrupt, set_concurrency, kill, finish))
|
|
243
290
|
# Like vitest's globalSetup: a failed setup still gets its teardown.
|
|
244
291
|
try:
|
|
245
292
|
if experiment.setup is not None:
|
|
246
293
|
report({"event": "setting up"})
|
|
247
|
-
await invocation_hook(
|
|
294
|
+
await invocation_hook(
|
|
295
|
+
config.root, "setup", fill_ids(experiment.setup, ids), hook_env["setup"]
|
|
296
|
+
)
|
|
248
297
|
fill()
|
|
249
298
|
await done.wait()
|
|
250
299
|
await asyncio.gather(*started)
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
def compose_experiment(
|
|
2
|
-
base: dict[str, object], layer: dict[str, object]
|
|
3
|
-
) -> dict[str, object]:
|
|
4
|
-
composed = {**base, **layer}
|
|
5
|
-
base_params, layer_params = base.get("params"), layer.get("params")
|
|
6
|
-
if isinstance(base_params, dict) and isinstance(layer_params, dict):
|
|
7
|
-
composed["params"] = {**base_params, **layer_params}
|
|
8
|
-
return composed
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|