labnotes 0.1.2__tar.gz → 0.2.1__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.1.2 → labnotes-0.2.1}/PKG-INFO +1 -1
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/context.md +7 -9
- labnotes-0.2.1/src/labnotes/runner/check_args.py +19 -0
- labnotes-0.2.1/src/labnotes/runner/check_hook_placeholders.py +12 -0
- labnotes-0.2.1/src/labnotes/runner/command_flags.py +35 -0
- labnotes-0.2.1/src/labnotes/runner/fill_experiment_env.py +23 -0
- labnotes-0.2.1/src/labnotes/runner/group_args.py +33 -0
- labnotes-0.2.1/src/labnotes/runner/override_flags.py +21 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/placeholders.py +0 -1
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/run_experiment.py +15 -15
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/shell_command.py +3 -1
- labnotes-0.1.2/src/labnotes/runner/check_args.py +0 -15
- labnotes-0.1.2/src/labnotes/runner/fill_experiment_env.py +0 -34
- labnotes-0.1.2/src/labnotes/runner/hook_dirs.py +0 -21
- {labnotes-0.1.2 → labnotes-0.2.1}/.gitignore +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/CHANGELOG.md +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/MIGRATIONS.md +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/README.md +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/pyproject.toml +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/__init__.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/__main__.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/cli.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/create_app.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/create_note.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/data_html.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/experiments.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/format_started.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/format_value.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/get_state.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/grid_cell.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/init.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/is_non_negative_number.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/load_config.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/load_run.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/log.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/log_path.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/memory_interval.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/models.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/modes.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/note_path.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/output_html.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/parse_subset.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/pivot_runs.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/read_config.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/read_path.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/retries.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/run_output.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/cgroup_memory.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/check_subset.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/container_cgroup.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/container_memory.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/docker_output.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/fill_dirs.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/finalize_lost_runs.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/flag_name.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/in_subset.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/invocation_hook.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/invocation_meta.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/project_snapshot.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/record_memory.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/run_git.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/start_experiment.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/runs_view_command.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/save_note.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/server.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/set_state.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/state_path.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/read_memory.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/read_run_file.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/runs_columns.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/check_view_response.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/format_cell.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/group_cells.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/invocation_columns.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/invocation_rows.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/native_rows.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/run_screen.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/runs_view.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/view_column.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/tui/view_process.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/utils/process_alive.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/utils/tree_usage.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/view/__init__.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/view/answer.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/view/build.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/view/respond.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/view/serve.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/testing-conventions.toml +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/tests/conftest.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/tests/e2e/conftest.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/tests/integration/conftest.py +0 -0
- {labnotes-0.1.2 → labnotes-0.2.1}/uv.lock +0 -0
|
@@ -102,19 +102,17 @@ the value from the start, even if the code changed while they waited.
|
|
|
102
102
|
commands run once per `run` from the root: setup before the first run starts,
|
|
103
103
|
teardown after the last one ends, even if runs failed, were stopped, or setup
|
|
104
104
|
failed (so teardown must cope with a partial setup). A failing setup starts no
|
|
105
|
-
run; a failing teardown fails the invocation.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
several runs there is no shared folder, so `{invocation_dir}` in a hook fails.
|
|
109
|
-
`{run_dir}` is never filled in hooks; use `{invocation_dir}` instead. An
|
|
110
|
-
unavailable placeholder fails before anything starts.
|
|
105
|
+
run; a failing teardown fails the invocation. Hooks and `meta` get no folder
|
|
106
|
+
placeholders: `{run_dir}` or `{invocation_dir}` in one fails before anything
|
|
107
|
+
starts. Share values through state instead.
|
|
111
108
|
Setup can call `labnotes.set_state({"container": "abc"})`; runs and teardown read the
|
|
112
109
|
dict with `labnotes.get_state()`. Writes merge by top-level key, replacing an
|
|
113
110
|
existing value without dropping other keys. Values must be JSON serializable;
|
|
114
111
|
reading before any write returns `{}`. Runs can only read: `set_state` raises
|
|
115
112
|
`RuntimeError` there. Both functions raise outside an invocation and in `meta`.
|
|
116
113
|
Labnotes gives setup/teardown `LABNOTES_STATE` and runs `LABNOTES_RUN_STATE`,
|
|
117
|
-
pointing to `
|
|
114
|
+
pointing to `state.json` in the multi-run adhoc group folder or the lone run's
|
|
115
|
+
folder, or, without a shared folder, a temp
|
|
118
116
|
file kept through all runs and teardown and then removed. Queued runs and retries
|
|
119
117
|
read the same file after setup finishes. `meta` receives neither state variable;
|
|
120
118
|
state is never copied into `run.json`.
|
|
@@ -125,8 +123,8 @@ children and set their own hook and run variables.
|
|
|
125
123
|
provides shared values; experiment values replace them by name. Variables in
|
|
126
124
|
the environment that starts `labnotes experiments NAME run` win over both,
|
|
127
125
|
including inline assignments. Values can use raw `{run_dir}` and
|
|
128
|
-
`{invocation_dir}` placeholders.
|
|
129
|
-
|
|
126
|
+
`{invocation_dir}` placeholders. Hooks and meta get neither, so values using
|
|
127
|
+
one are left out of their environment; runs still get them.
|
|
130
128
|
`LABNOTES_*` names are reserved for labnotes.
|
|
131
129
|
A run counts when it exited 0 and its `run.json` doesn't hold
|
|
132
130
|
`"excluded": true`, so excluding a run by hand means adding that key; the next
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from collections import Counter
|
|
2
|
+
|
|
3
|
+
from labnotes.runner.command_flags import command_flags
|
|
4
|
+
from labnotes.runner.group_args import group_args
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def check_args(params: dict[str, list[object]], command: str, args: list[str]) -> None:
|
|
8
|
+
taken = Counter(flag.name for flag in command_flags(command))
|
|
9
|
+
given: set[str] = set()
|
|
10
|
+
for name in (arg.name for arg in group_args(args) if arg.name is not None):
|
|
11
|
+
if name in params:
|
|
12
|
+
raise ValueError(f"--{name} is a param. Pick it with --{name}=VALUE.")
|
|
13
|
+
if taken[name] > 1:
|
|
14
|
+
raise ValueError(
|
|
15
|
+
f"--{name} is in the command more than once, so -- can't override it."
|
|
16
|
+
)
|
|
17
|
+
if taken[name] and name in given:
|
|
18
|
+
raise ValueError(f"--{name} is in -- ARGS more than once.")
|
|
19
|
+
given.add(name)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from labnotes.runner.placeholders import PLACEHOLDERS
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def check_hook_placeholders(hooks: dict[str, str | None]) -> None:
|
|
5
|
+
for hook, command in hooks.items():
|
|
6
|
+
for name in PLACEHOLDERS:
|
|
7
|
+
if command is not None and f"{{{name}}}" in command:
|
|
8
|
+
raise ValueError(
|
|
9
|
+
f"{hook} command {command} uses {{{name}}}; setup, teardown, "
|
|
10
|
+
"and meta get no folder placeholders. "
|
|
11
|
+
"Share values with set_state() and get_state()."
|
|
12
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from itertools import zip_longest
|
|
3
|
+
from typing import NamedTuple
|
|
4
|
+
|
|
5
|
+
from labnotes.runner.flag_name import flag_name
|
|
6
|
+
|
|
7
|
+
OPERATOR = ";&|<>()"
|
|
8
|
+
WORD = re.compile(
|
|
9
|
+
rf"""[{OPERATOR}]+|(?:[^\s{OPERATOR}'"\\]|\\.|'[^']*'|"(?:[^"\\]|\\.)*")+""",
|
|
10
|
+
re.DOTALL,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CommandFlag(NamedTuple):
|
|
15
|
+
name: str
|
|
16
|
+
start: int
|
|
17
|
+
end: int
|
|
18
|
+
joined: bool
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def command_flags(command: str) -> list[CommandFlag]:
|
|
22
|
+
words = list(WORD.finditer(command))
|
|
23
|
+
flags: list[CommandFlag] = []
|
|
24
|
+
for word, after in zip_longest(words, words[1:]):
|
|
25
|
+
name = flag_name(word[0])
|
|
26
|
+
if name is None:
|
|
27
|
+
continue
|
|
28
|
+
joined = "=" in word[0]
|
|
29
|
+
end = word.end()
|
|
30
|
+
if not joined and after is not None:
|
|
31
|
+
value = after[0]
|
|
32
|
+
if value[0] not in OPERATOR and not value.startswith("-"):
|
|
33
|
+
end = after.end()
|
|
34
|
+
flags.append(CommandFlag(name, word.start(), end, joined))
|
|
35
|
+
return flags
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
|
|
3
|
+
from labnotes.runner.fill_dirs import fill_dirs
|
|
4
|
+
from labnotes.runner.placeholders import PLACEHOLDERS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def fill_experiment_env(
|
|
8
|
+
configured: Mapping[str, str],
|
|
9
|
+
inherited: Mapping[str, str],
|
|
10
|
+
dirs: Mapping[str, str],
|
|
11
|
+
) -> dict[str, str]:
|
|
12
|
+
caller = {
|
|
13
|
+
key: value
|
|
14
|
+
for key, value in inherited.items()
|
|
15
|
+
if not key.startswith("LABNOTES_")
|
|
16
|
+
}
|
|
17
|
+
filled = {
|
|
18
|
+
key: fill_dirs(value, dirs, quote=False)
|
|
19
|
+
for key, value in configured.items()
|
|
20
|
+
if key not in caller
|
|
21
|
+
and all(name in dirs for name in PLACEHOLDERS if f"{{{name}}}" in value)
|
|
22
|
+
}
|
|
23
|
+
return {**filled, **caller}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import NamedTuple
|
|
2
|
+
|
|
3
|
+
from labnotes.runner.flag_name import flag_name
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Arg(NamedTuple):
|
|
7
|
+
name: str | None
|
|
8
|
+
value: str | None
|
|
9
|
+
tokens: list[str]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def group_args(args: list[str]) -> list[Arg]:
|
|
13
|
+
grouped: list[Arg] = []
|
|
14
|
+
values = {
|
|
15
|
+
index + 1
|
|
16
|
+
for index, token in enumerate(args[:-1])
|
|
17
|
+
if flag_name(token) is not None
|
|
18
|
+
and "=" not in token
|
|
19
|
+
and not args[index + 1].startswith("-")
|
|
20
|
+
}
|
|
21
|
+
for index, token in enumerate(args):
|
|
22
|
+
name = flag_name(token)
|
|
23
|
+
if index in values:
|
|
24
|
+
continue
|
|
25
|
+
if name is None:
|
|
26
|
+
grouped.append(Arg(None, None, [token]))
|
|
27
|
+
elif "=" in token:
|
|
28
|
+
grouped.append(Arg(name, token.partition("=")[2], [token]))
|
|
29
|
+
elif index + 1 in values:
|
|
30
|
+
grouped.append(Arg(name, args[index + 1], [token, args[index + 1]]))
|
|
31
|
+
else:
|
|
32
|
+
grouped.append(Arg(name, None, [token]))
|
|
33
|
+
return grouped
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import shlex
|
|
2
|
+
|
|
3
|
+
from labnotes.runner.command_flags import command_flags
|
|
4
|
+
from labnotes.runner.group_args import group_args
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def override_flags(command: str, args: list[str]) -> tuple[str, list[str]]:
|
|
8
|
+
flags = {flag.name: flag for flag in command_flags(command)}
|
|
9
|
+
replaced: list[tuple[int, int, str]] = []
|
|
10
|
+
rest: list[str] = []
|
|
11
|
+
for arg in group_args(args):
|
|
12
|
+
flag = flags.get(arg.name) if arg.name is not None else None
|
|
13
|
+
if flag is None:
|
|
14
|
+
rest += arg.tokens
|
|
15
|
+
elif arg.value is not None:
|
|
16
|
+
separator = "=" if flag.joined else " "
|
|
17
|
+
text = f"--{arg.name}{separator}{shlex.quote(arg.value)}"
|
|
18
|
+
replaced.append((flag.start, flag.end, text))
|
|
19
|
+
for start, end, text in sorted(replaced, reverse=True):
|
|
20
|
+
command = command[:start] + text + command[end:]
|
|
21
|
+
return command, rest
|
|
@@ -16,10 +16,10 @@ from labnotes.models import (
|
|
|
16
16
|
Report,
|
|
17
17
|
)
|
|
18
18
|
from labnotes.retries import RETRY_EXIT_CODE, RETRY_SECONDS
|
|
19
|
+
from labnotes.runner.check_hook_placeholders import check_hook_placeholders
|
|
19
20
|
from labnotes.runner.execute_run import execute_run
|
|
20
21
|
from labnotes.runner.fill_experiment_env import fill_experiment_env
|
|
21
22
|
from labnotes.runner.fill_dirs import fill_dirs
|
|
22
|
-
from labnotes.runner.hook_dirs import hook_dirs
|
|
23
23
|
from labnotes.runner.in_subset import in_subset
|
|
24
24
|
from labnotes.runner.invocation_hook import invocation_hook
|
|
25
25
|
from labnotes.runner.invocation_meta import invocation_meta
|
|
@@ -101,21 +101,21 @@ async def run_experiment(
|
|
|
101
101
|
"teardown": experiment.teardown,
|
|
102
102
|
"meta": experiment.meta,
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
check_hook_placeholders(hooks)
|
|
105
105
|
hook_env = {
|
|
106
|
-
hook: fill_experiment_env(
|
|
107
|
-
experiment.env, inherited, dirs, hook=hook, runs=len(plan)
|
|
108
|
-
)
|
|
106
|
+
hook: fill_experiment_env(experiment.env, inherited, {})
|
|
109
107
|
for hook, command in hooks.items()
|
|
110
108
|
if command is not None
|
|
111
109
|
}
|
|
112
110
|
meta = None
|
|
113
111
|
if experiment.meta is not None:
|
|
114
|
-
meta = await invocation_meta(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
meta = await invocation_meta(command_root, experiment.meta, hook_env["meta"])
|
|
113
|
+
invocation_dir = group_dir
|
|
114
|
+
match run_dirs:
|
|
115
|
+
case [run_dir]:
|
|
116
|
+
invocation_dir = run_dir
|
|
117
|
+
if invocation_dir is not None:
|
|
118
|
+
Path(invocation_dir).mkdir(parents=True, exist_ok=True)
|
|
119
119
|
runs = [{"run": run, "params": params} for run, params in plan]
|
|
120
120
|
report(
|
|
121
121
|
{
|
|
@@ -248,7 +248,7 @@ async def run_experiment(
|
|
|
248
248
|
if snapshot is not None and snapshot.sha is not None:
|
|
249
249
|
(folder / "git-sha.txt").write_text(snapshot.sha + "\n")
|
|
250
250
|
(folder / "git-diff.patch").write_bytes(snapshot.diff or b"")
|
|
251
|
-
run_invocation_dir =
|
|
251
|
+
run_invocation_dir = invocation_dir or str(folder.resolve())
|
|
252
252
|
report({"event": "started", "run": run, "params": params, "folder": folder})
|
|
253
253
|
running[run] = set()
|
|
254
254
|
trees[run] = set()
|
|
@@ -303,14 +303,14 @@ async def run_experiment(
|
|
|
303
303
|
await invocation_hook(
|
|
304
304
|
command_root,
|
|
305
305
|
"teardown",
|
|
306
|
-
|
|
306
|
+
experiment.teardown,
|
|
307
307
|
hook_env["teardown"],
|
|
308
308
|
)
|
|
309
309
|
|
|
310
310
|
listen(Controls(interrupt, set_concurrency, kill, finish))
|
|
311
311
|
with (
|
|
312
|
-
nullcontext(
|
|
313
|
-
if
|
|
312
|
+
nullcontext(invocation_dir)
|
|
313
|
+
if invocation_dir is not None
|
|
314
314
|
else TemporaryDirectory(prefix="labnotes-state-")
|
|
315
315
|
) as state_dir:
|
|
316
316
|
state_file = Path(state_dir) / "state.json"
|
|
@@ -324,7 +324,7 @@ async def run_experiment(
|
|
|
324
324
|
await invocation_hook(
|
|
325
325
|
command_root,
|
|
326
326
|
"setup",
|
|
327
|
-
|
|
327
|
+
experiment.setup,
|
|
328
328
|
hook_env["setup"],
|
|
329
329
|
)
|
|
330
330
|
fill()
|
|
@@ -2,6 +2,7 @@ import shlex
|
|
|
2
2
|
from collections.abc import Sequence
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
+
from labnotes.runner.override_flags import override_flags
|
|
5
6
|
from labnotes.runner.param_args import param_args
|
|
6
7
|
|
|
7
8
|
|
|
@@ -9,4 +10,5 @@ def shell_command(
|
|
|
9
10
|
command: str, folder: Path, params: dict[str, object], args: Sequence[str] = ()
|
|
10
11
|
) -> str:
|
|
11
12
|
filled = command.replace("{run_dir}", shlex.quote(str(folder)))
|
|
12
|
-
|
|
13
|
+
overridden, rest = override_flags(filled, list(args))
|
|
14
|
+
return f"{overridden} {shlex.join([*param_args(params), *rest])}".rstrip()
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import shlex
|
|
2
|
-
|
|
3
|
-
from labnotes.runner.flag_name import flag_name
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def check_args(params: dict[str, list[object]], command: str, args: list[str]) -> None:
|
|
7
|
-
taken = {flag_name(token) for token in shlex.split(command)}
|
|
8
|
-
for arg in args:
|
|
9
|
-
name = flag_name(arg)
|
|
10
|
-
if name is None:
|
|
11
|
-
continue
|
|
12
|
-
if name in params:
|
|
13
|
-
raise ValueError(f"--{name} is a param. Pick it with --{name}=VALUE.")
|
|
14
|
-
if name in taken:
|
|
15
|
-
raise ValueError(f"--{name} is already in the command.")
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from collections.abc import Mapping
|
|
2
|
-
|
|
3
|
-
from labnotes.runner.fill_dirs import fill_dirs
|
|
4
|
-
from labnotes.runner.placeholders import HOOK_PLACEHOLDERS, PLACEHOLDERS
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def fill_experiment_env(
|
|
8
|
-
configured: Mapping[str, str],
|
|
9
|
-
inherited: Mapping[str, str],
|
|
10
|
-
dirs: Mapping[str, str],
|
|
11
|
-
*,
|
|
12
|
-
hook: str | None = None,
|
|
13
|
-
runs: int = 1,
|
|
14
|
-
) -> dict[str, str]:
|
|
15
|
-
caller = {
|
|
16
|
-
key: value
|
|
17
|
-
for key, value in inherited.items()
|
|
18
|
-
if not key.startswith("LABNOTES_")
|
|
19
|
-
}
|
|
20
|
-
filled: dict[str, str] = {}
|
|
21
|
-
for key, value in configured.items():
|
|
22
|
-
if key in caller:
|
|
23
|
-
continue
|
|
24
|
-
used = [name for name in PLACEHOLDERS if f"{{{name}}}" in value]
|
|
25
|
-
if hook is not None and any(name not in HOOK_PLACEHOLDERS for name in used):
|
|
26
|
-
continue
|
|
27
|
-
for name in used:
|
|
28
|
-
if name not in dirs:
|
|
29
|
-
raise ValueError(
|
|
30
|
-
f"{hook} env.{key} uses {{{name}}}, but this invocation plans "
|
|
31
|
-
f"{runs} runs. {{{name}}} needs exactly one run in this mode."
|
|
32
|
-
)
|
|
33
|
-
filled[key] = fill_dirs(value, dirs, quote=False)
|
|
34
|
-
return {**filled, **caller}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
def hook_dirs(
|
|
2
|
-
hooks: dict[str, str | None], invocation_dir: str | None, run_dirs: list[str]
|
|
3
|
-
) -> dict[str, str]:
|
|
4
|
-
match run_dirs:
|
|
5
|
-
case [run_dir]:
|
|
6
|
-
invocation_dir = invocation_dir or run_dir
|
|
7
|
-
available = {"invocation_dir": invocation_dir} if invocation_dir is not None else {}
|
|
8
|
-
for hook, command in hooks.items():
|
|
9
|
-
if command is None:
|
|
10
|
-
continue
|
|
11
|
-
if "{run_dir}" in command:
|
|
12
|
-
raise ValueError(
|
|
13
|
-
f"{hook} command {command} uses {{run_dir}}; "
|
|
14
|
-
"use {invocation_dir} instead."
|
|
15
|
-
)
|
|
16
|
-
if "{invocation_dir}" in command and invocation_dir is None:
|
|
17
|
-
raise ValueError(
|
|
18
|
-
f"{hook} command {command} uses {{invocation_dir}}, but this "
|
|
19
|
-
f"invocation plans {len(run_dirs)} runs and has no shared folder."
|
|
20
|
-
)
|
|
21
|
-
return available
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|