labnotes 0.0.125__tar.gz → 0.0.126__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.125 → labnotes-0.0.126}/PKG-INFO +1 -1
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/context.md +5 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/daemon_app.py +2 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/remote_start.py +4 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/experiments.py +16 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/models.py +2 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/describe_event.py +6 -0
- labnotes-0.0.126/src/labnotes/runner/invocation_hook.py +16 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/prepare_experiment.py +7 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/read_experiment_config.py +9 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/run_experiment.py +21 -3
- {labnotes-0.0.125 → labnotes-0.0.126}/.gitignore +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/CHANGELOG.md +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/MIGRATIONS.md +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/README.md +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/pyproject.toml +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/__main__.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/create_note.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/__init__.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/control_run.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/daemon_client.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/daemon_running.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/daemon_server.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/describe_status.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/ensure_daemon.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/exit_when_idle.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/finalize_lost_runs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/last_stderr.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/run_daemon.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/run_logs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/serve_daemon.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/socket_path.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/stop_runs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/watch_daemon.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/watch_status.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon_command.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/format_started.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/grid_cell.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/init.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/is_non_negative_number.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/load_run.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/log.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/log_path.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/memory_interval.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/modes.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/note_path.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/parse_subset.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/pivot_runs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/retries.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/run_command.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/run_control.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/run_output.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/run_status.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/check_args.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/check_subset.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/flag_name.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/in_subset.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/invocation_meta.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/record_memory.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runs_view_command.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/save_note.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/server.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_memory.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_run_file.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/runs_columns.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/check_view_response.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/format_cell.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/group_cells.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/invocation_columns.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/invocation_rows.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/native_rows.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/run_screen.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/runs_view.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/view_column.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/view_process.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/process_alive.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/tree_usage.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/view/__init__.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/view/answer.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/view/build.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/view/respond.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/view/serve.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/testing-conventions.toml +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/tests/integration/conftest.py +0 -0
- {labnotes-0.0.125 → labnotes-0.0.126}/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.126
|
|
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>
|
|
@@ -89,6 +89,11 @@ command that prints one JSON object, such as the git commit. labnotes runs it
|
|
|
89
89
|
once from the root when `run` starts and copies it into every run's `run.json`
|
|
90
90
|
as `meta`. If it fails or prints anything else, no run starts. Queued runs keep
|
|
91
91
|
the value from the start, even if the code changed while they waited.
|
|
92
|
+
`setup` and `teardown` (same places; `--setup`/`--teardown` override) are shell
|
|
93
|
+
commands run once per `run` from the root: setup before the first run starts,
|
|
94
|
+
teardown after the last one ends, even if runs failed, were stopped, or setup
|
|
95
|
+
failed (so teardown must cope with a partial setup). A failing setup starts no
|
|
96
|
+
run; a failing teardown fails the invocation.
|
|
92
97
|
A run counts when it exited 0 and its `run.json` doesn't hold
|
|
93
98
|
`"excluded": true`, so excluding a run by hand means adding that key; the next
|
|
94
99
|
`run` replaces it. Other keys you add to `run.json` are kept and show in `run`.
|
|
@@ -80,6 +80,8 @@ def daemon_app(config: LabnotesConfig) -> Starlette:
|
|
|
80
80
|
body.get("concurrency"), # type: ignore[arg-type]
|
|
81
81
|
body.get("only"), # type: ignore[arg-type]
|
|
82
82
|
body.get("args"), # type: ignore[arg-type]
|
|
83
|
+
body.get("setup"), # type: ignore[arg-type]
|
|
84
|
+
body.get("teardown"), # type: ignore[arg-type]
|
|
83
85
|
)
|
|
84
86
|
except ValueError as error:
|
|
85
87
|
return refuse(400, str(error))
|
|
@@ -18,6 +18,8 @@ def remote_start(
|
|
|
18
18
|
timeout: float | None = None,
|
|
19
19
|
only: dict[str, str] | None = None,
|
|
20
20
|
args: list[str] | None = None,
|
|
21
|
+
setup: str | None = None,
|
|
22
|
+
teardown: str | None = None,
|
|
21
23
|
) -> Start:
|
|
22
24
|
async def start(report: Report, listen: Listen) -> list[dict[str, object]]:
|
|
23
25
|
try:
|
|
@@ -50,6 +52,8 @@ def remote_start(
|
|
|
50
52
|
"timeout": timeout,
|
|
51
53
|
"only": only,
|
|
52
54
|
"args": args,
|
|
55
|
+
"setup": setup,
|
|
56
|
+
"teardown": teardown,
|
|
53
57
|
},
|
|
54
58
|
)
|
|
55
59
|
if reply.status_code != 202:
|
|
@@ -66,6 +66,18 @@ class PassthroughCommand(click.Command):
|
|
|
66
66
|
help="With run: stop any run still going after this many seconds."
|
|
67
67
|
" Unset, runs have no time limit.",
|
|
68
68
|
)
|
|
69
|
+
@click.option(
|
|
70
|
+
"--setup",
|
|
71
|
+
metavar="COMMAND",
|
|
72
|
+
help="With run: shell command to run once before any run starts."
|
|
73
|
+
" Overrides experiment.toml and .labnotes.toml.",
|
|
74
|
+
)
|
|
75
|
+
@click.option(
|
|
76
|
+
"--teardown",
|
|
77
|
+
metavar="COMMAND",
|
|
78
|
+
help="With run: shell command to run once after every run ends."
|
|
79
|
+
" Overrides experiment.toml and .labnotes.toml.",
|
|
80
|
+
)
|
|
69
81
|
@click.pass_context
|
|
70
82
|
def experiments(
|
|
71
83
|
ctx: click.Context,
|
|
@@ -75,6 +87,8 @@ def experiments(
|
|
|
75
87
|
concurrency: int | None,
|
|
76
88
|
experiment_config: Path | None,
|
|
77
89
|
timeout: int | None,
|
|
90
|
+
setup: str | None,
|
|
91
|
+
teardown: str | None,
|
|
78
92
|
) -> None:
|
|
79
93
|
passthrough: list[str] | None = ctx.meta.get(PASSTHROUGH)
|
|
80
94
|
config = load_config(ctx)
|
|
@@ -103,6 +117,8 @@ def experiments(
|
|
|
103
117
|
timeout,
|
|
104
118
|
only,
|
|
105
119
|
passthrough or None,
|
|
120
|
+
setup,
|
|
121
|
+
teardown,
|
|
106
122
|
)
|
|
107
123
|
try:
|
|
108
124
|
if sys.stdout.isatty() and sys.stderr.isatty():
|
|
@@ -33,6 +33,12 @@ def describe_event(event: dict[str, object]) -> str:
|
|
|
33
33
|
return "No runs left."
|
|
34
34
|
case "concurrency":
|
|
35
35
|
return f"{event['concurrency']} at a time."
|
|
36
|
+
case "setting up":
|
|
37
|
+
return "Setting up."
|
|
38
|
+
case "tearing down":
|
|
39
|
+
return "Tearing down."
|
|
40
|
+
case "teardown failed":
|
|
41
|
+
return str(event["error"])
|
|
36
42
|
case "stopping":
|
|
37
43
|
return "Stopping runs. Ctrl-C again to kill them."
|
|
38
44
|
case _:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def invocation_hook(root: Path, hook: str, command: str) -> None:
|
|
6
|
+
process = await asyncio.create_subprocess_shell(
|
|
7
|
+
command,
|
|
8
|
+
cwd=root,
|
|
9
|
+
stdout=asyncio.subprocess.DEVNULL,
|
|
10
|
+
stderr=asyncio.subprocess.PIPE,
|
|
11
|
+
)
|
|
12
|
+
_, stderr = await process.communicate()
|
|
13
|
+
if process.returncode != 0:
|
|
14
|
+
message = f"{hook} command {command} exited {process.returncode}"
|
|
15
|
+
detail = stderr.decode(errors="replace").strip()
|
|
16
|
+
raise ValueError(f"{message}: {detail}" if detail else message)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import dataclasses
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
from labnotes.models import ExperimentConfig, LabnotesConfig
|
|
@@ -14,6 +15,8 @@ async def prepare_experiment(
|
|
|
14
15
|
concurrency: int | None,
|
|
15
16
|
only: dict[str, str] | None = None,
|
|
16
17
|
args: list[str] | None = None,
|
|
18
|
+
setup: str | None = None,
|
|
19
|
+
teardown: str | None = None,
|
|
17
20
|
) -> tuple[ExperimentConfig, int]:
|
|
18
21
|
if config.experiment not in (None, name):
|
|
19
22
|
raise ValueError(
|
|
@@ -33,6 +36,10 @@ async def prepare_experiment(
|
|
|
33
36
|
config.retries,
|
|
34
37
|
config.defaults,
|
|
35
38
|
)
|
|
39
|
+
if setup is not None:
|
|
40
|
+
experiment = dataclasses.replace(experiment, setup=setup)
|
|
41
|
+
if teardown is not None:
|
|
42
|
+
experiment = dataclasses.replace(experiment, teardown=teardown)
|
|
36
43
|
if only:
|
|
37
44
|
check_subset(experiment.params, only)
|
|
38
45
|
if args:
|
|
@@ -24,7 +24,10 @@ SETTINGS = {
|
|
|
24
24
|
"meta",
|
|
25
25
|
"memory_interval",
|
|
26
26
|
"views",
|
|
27
|
+
"setup",
|
|
28
|
+
"teardown",
|
|
27
29
|
}
|
|
30
|
+
HOOKS = ("setup", "teardown")
|
|
28
31
|
VIEWS = {"runs"}
|
|
29
32
|
|
|
30
33
|
|
|
@@ -81,6 +84,10 @@ def read_experiment_config(
|
|
|
81
84
|
memory_interval = values.get("memory_interval", DEFAULT_MEMORY_INTERVAL)
|
|
82
85
|
if not is_non_negative_number(memory_interval):
|
|
83
86
|
raise ValueError(f"{path}: memory_interval must be 0 or more seconds")
|
|
87
|
+
for hook in HOOKS:
|
|
88
|
+
run = values.get(hook)
|
|
89
|
+
if run is not None and (not isinstance(run, str) or not run.strip()):
|
|
90
|
+
raise ValueError(f"{path}: {hook} must be a string")
|
|
84
91
|
views = values.get("views", {})
|
|
85
92
|
if not isinstance(views, dict):
|
|
86
93
|
raise ValueError(f"{path}: views must be a table, like [views]")
|
|
@@ -101,4 +108,6 @@ def read_experiment_config(
|
|
|
101
108
|
retries,
|
|
102
109
|
meta,
|
|
103
110
|
memory_interval,
|
|
111
|
+
values.get("setup"),
|
|
112
|
+
values.get("teardown"),
|
|
104
113
|
)
|
|
@@ -14,6 +14,7 @@ from labnotes.models import (
|
|
|
14
14
|
from labnotes.retries import RETRY_EXIT_CODE, RETRY_SECONDS
|
|
15
15
|
from labnotes.runner.execute_run import execute_run
|
|
16
16
|
from labnotes.runner.in_subset import in_subset
|
|
17
|
+
from labnotes.runner.invocation_hook import invocation_hook
|
|
17
18
|
from labnotes.runner.invocation_meta import invocation_meta
|
|
18
19
|
from labnotes.runner.new_param_run_id import new_param_run_id
|
|
19
20
|
from labnotes.runner.new_stamped_run_id import new_stamped_run_id
|
|
@@ -233,8 +234,25 @@ async def run_experiment(
|
|
|
233
234
|
):
|
|
234
235
|
retry_later(run)
|
|
235
236
|
|
|
237
|
+
async def teardown() -> None:
|
|
238
|
+
if experiment.teardown is not None:
|
|
239
|
+
report({"event": "tearing down"})
|
|
240
|
+
await invocation_hook(config.root, "teardown", experiment.teardown)
|
|
241
|
+
|
|
236
242
|
listen(Controls(interrupt, set_concurrency, kill, finish))
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
243
|
+
# Like vitest's globalSetup: a failed setup still gets its teardown.
|
|
244
|
+
try:
|
|
245
|
+
if experiment.setup is not None:
|
|
246
|
+
report({"event": "setting up"})
|
|
247
|
+
await invocation_hook(config.root, "setup", experiment.setup)
|
|
248
|
+
fill()
|
|
249
|
+
await done.wait()
|
|
250
|
+
await asyncio.gather(*started)
|
|
251
|
+
except BaseException:
|
|
252
|
+
try:
|
|
253
|
+
await teardown()
|
|
254
|
+
except Exception as failure: # noqa: BLE001
|
|
255
|
+
report({"event": "teardown failed", "error": str(failure)})
|
|
256
|
+
raise
|
|
257
|
+
await teardown()
|
|
240
258
|
return results
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|