labnotes 0.2.15__tar.gz → 0.2.16__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.2.15 → labnotes-0.2.16}/PKG-INFO +1 -1
- labnotes-0.2.16/conftest.py +9 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/run_experiment.py +27 -18
- labnotes-0.2.16/src/labnotes/runner/run_state.py +62 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/start_experiment.py +18 -2
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/run_app.py +26 -4
- {labnotes-0.2.15 → labnotes-0.2.16}/.gitignore +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/CHANGELOG.md +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/MIGRATIONS.md +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/README.md +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/pyproject.toml +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/__init__.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/__main__.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/cli.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/command_line_group.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/config_migration.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/context.md +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/copy_experiment.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/create_app.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/create_note.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/data_html.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/experiment_definition.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/experiments.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/format_started.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/format_value.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/get_state.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/grid_cell.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/init.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/is_non_negative_number.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/latest_log.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/load_config.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/load_run.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/log.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/log_path.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/memory_interval.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/models.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/note_path.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/output_html.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/param_names.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/parse_param_value.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/parse_params.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/parse_subset.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/pivot_runs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/read_config.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/read_path.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/retries.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/run_columns.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/run_output.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/cgroup_memory.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/check_args.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/check_hook_placeholders.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/check_subset.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/claim_run.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/command_flags.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/container_cgroup.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/container_memory.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/continue_run.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/docker_output.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/fill_dirs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/fill_experiment_env.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/finalize_lost_runs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/first_invocation.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/flag_name.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/group_args.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/hold_experiment.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/in_subset.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/invocation_hook.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/invocation_meta.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/override_flags.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/placeholders.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/project_snapshot.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/record_memory.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/run_git.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/run_locked.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/untracked_diff.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runs_view_command.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/save_note.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/server.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/set_state.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/state_path.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/list_resumable_runs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_memory.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_run_file.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/runs_columns.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/check_view_response.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/format_cell.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/guard_view.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/load_view.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/native_rows.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/run_panel.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/runs_browser.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/runs_view.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/table_columns.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/table_rows.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/view_column.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/view_pane.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/view_process.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/view_switcher.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/process_alive.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/tree_usage.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/__init__.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/answer.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/build.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/changed.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/experiment.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/respond.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/serve.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view_specs.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/testing-conventions.toml +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/tests/conftest.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/tests/e2e/conftest.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/tests/integration/conftest.py +0 -0
- {labnotes-0.2.15 → labnotes-0.2.16}/uv.lock +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@pytest.fixture(autouse=True)
|
|
5
|
+
def state_home(
|
|
6
|
+
tmp_path_factory: pytest.TempPathFactory, monkeypatch: pytest.MonkeyPatch
|
|
7
|
+
) -> None:
|
|
8
|
+
# Run journals live under XDG_STATE_HOME; keep tests out of the real one.
|
|
9
|
+
monkeypatch.setenv("XDG_STATE_HOME", str(tmp_path_factory.mktemp("state")))
|
|
@@ -30,6 +30,7 @@ from labnotes.runner.plan_runs import plan_runs
|
|
|
30
30
|
from labnotes.runner.project_snapshot import ProjectSnapshot
|
|
31
31
|
from labnotes.runner.run_folder import run_folder
|
|
32
32
|
from labnotes.runner.run_locked import run_locked
|
|
33
|
+
from labnotes.runner.run_state import RunState
|
|
33
34
|
from labnotes.runner.signal_tree import signal_tree
|
|
34
35
|
from labnotes.state_path import STATE_ENV
|
|
35
36
|
from labnotes.tables.experiment_run_ids import experiment_run_ids
|
|
@@ -55,7 +56,15 @@ async def run_experiment(
|
|
|
55
56
|
snapshot: ProjectSnapshot | None = None,
|
|
56
57
|
command_line: str | None = None,
|
|
57
58
|
run_id: str | None = None,
|
|
59
|
+
run_state: RunState | None = None,
|
|
58
60
|
) -> list[dict[str, object]]:
|
|
61
|
+
state = run_state or RunState.create(config, name)
|
|
62
|
+
forward = report
|
|
63
|
+
|
|
64
|
+
def emit(event: dict[str, object]) -> None:
|
|
65
|
+
state.record(event)
|
|
66
|
+
forward(event)
|
|
67
|
+
|
|
59
68
|
command_root = snapshot.root if snapshot is not None else config.root
|
|
60
69
|
inherited = dict(os.environ) if environment is None else environment
|
|
61
70
|
tables = open_tables(config)
|
|
@@ -161,7 +170,7 @@ async def run_experiment(
|
|
|
161
170
|
if experiment.meta is not None:
|
|
162
171
|
meta = await invocation_meta(command_root, experiment.meta, hook_env["meta"])
|
|
163
172
|
runs = [{"run": run, "params": params} for run, params in plan]
|
|
164
|
-
|
|
173
|
+
emit(
|
|
165
174
|
{
|
|
166
175
|
"event": "planned",
|
|
167
176
|
"title": experiment.name or name,
|
|
@@ -170,7 +179,7 @@ async def run_experiment(
|
|
|
170
179
|
}
|
|
171
180
|
)
|
|
172
181
|
for row in held:
|
|
173
|
-
|
|
182
|
+
emit({"event": "held", "run": str(row["id"])})
|
|
174
183
|
running: dict[str, set[asyncio.subprocess.Process]] = {}
|
|
175
184
|
trees: dict[str, set[psutil.Process]] = {}
|
|
176
185
|
stopped: set[str] = set()
|
|
@@ -189,14 +198,14 @@ async def run_experiment(
|
|
|
189
198
|
plan.clear()
|
|
190
199
|
if not stopping.is_set():
|
|
191
200
|
stopping.set()
|
|
192
|
-
|
|
201
|
+
emit({"event": "stopping"})
|
|
193
202
|
signal_runs(signal.SIGTERM)
|
|
194
203
|
if waiting:
|
|
195
204
|
for run in list(waiting):
|
|
196
205
|
cancel_retry(run)
|
|
197
206
|
fill()
|
|
198
207
|
elif any(running.values()):
|
|
199
|
-
|
|
208
|
+
emit({"event": "killing"})
|
|
200
209
|
signal_runs(signal.SIGKILL)
|
|
201
210
|
|
|
202
211
|
def kill(run: str) -> None:
|
|
@@ -207,11 +216,11 @@ async def run_experiment(
|
|
|
207
216
|
if not running.get(run):
|
|
208
217
|
return
|
|
209
218
|
if run in stopped:
|
|
210
|
-
|
|
219
|
+
emit({"event": "killing run", "run": run})
|
|
211
220
|
signal_run(run, signal.SIGKILL)
|
|
212
221
|
else:
|
|
213
222
|
stopped.add(run)
|
|
214
|
-
|
|
223
|
+
emit({"event": "stopping run", "run": run})
|
|
215
224
|
signal_run(run, signal.SIGTERM)
|
|
216
225
|
|
|
217
226
|
ended: dict[str, dict[str, object]] = {}
|
|
@@ -227,13 +236,13 @@ async def run_experiment(
|
|
|
227
236
|
parents[again] = run
|
|
228
237
|
attempts[again] = attempt
|
|
229
238
|
plan.append((again, ended[run]))
|
|
230
|
-
|
|
239
|
+
emit({"event": "retried", "run": run, "retry": again, "params": ended[run]})
|
|
231
240
|
fill()
|
|
232
241
|
|
|
233
242
|
def retry_later(run: str) -> None:
|
|
234
243
|
tried = attempts.get(run, 0)
|
|
235
244
|
if tried >= experiment.retries:
|
|
236
|
-
|
|
245
|
+
emit({"event": "gave up", "run": run, "retries": tried})
|
|
237
246
|
return
|
|
238
247
|
seconds = retry_seconds * 2**tried
|
|
239
248
|
|
|
@@ -242,7 +251,7 @@ async def run_experiment(
|
|
|
242
251
|
requeue(run, tried + 1)
|
|
243
252
|
|
|
244
253
|
waiting[run] = asyncio.get_running_loop().call_later(seconds, due)
|
|
245
|
-
|
|
254
|
+
emit(
|
|
246
255
|
{
|
|
247
256
|
"event": "retrying",
|
|
248
257
|
"run": run,
|
|
@@ -255,7 +264,7 @@ async def run_experiment(
|
|
|
255
264
|
def cancel_retry(run: str) -> None:
|
|
256
265
|
waiting.pop(run).cancel()
|
|
257
266
|
release(run)
|
|
258
|
-
|
|
267
|
+
emit({"event": "retry cancelled", "run": run})
|
|
259
268
|
|
|
260
269
|
limit = concurrency
|
|
261
270
|
live: set[asyncio.Task[None]] = set()
|
|
@@ -270,7 +279,7 @@ async def run_experiment(
|
|
|
270
279
|
live.add(task)
|
|
271
280
|
started.append(task)
|
|
272
281
|
if not (live or waiting):
|
|
273
|
-
|
|
282
|
+
emit({"event": "idle"})
|
|
274
283
|
if finishing:
|
|
275
284
|
done.set()
|
|
276
285
|
|
|
@@ -287,19 +296,19 @@ async def run_experiment(
|
|
|
287
296
|
def set_concurrency(count: int) -> None:
|
|
288
297
|
nonlocal limit
|
|
289
298
|
limit = max(1, count)
|
|
290
|
-
|
|
299
|
+
emit({"event": "concurrency", "concurrency": limit})
|
|
291
300
|
fill()
|
|
292
301
|
|
|
293
302
|
async def start(run: str, params: dict[str, object]) -> None:
|
|
294
303
|
folder = target_folder or run_folder(config.root, config.runs, name, run)
|
|
295
304
|
folder.mkdir(parents=True, exist_ok=True)
|
|
296
305
|
if not claim(run):
|
|
297
|
-
|
|
306
|
+
emit({"event": "held", "run": run})
|
|
298
307
|
return
|
|
299
308
|
if snapshot is not None and snapshot.sha is not None:
|
|
300
309
|
(folder / "git-sha.txt").write_text(snapshot.sha + "\n")
|
|
301
310
|
(folder / "git-diff.patch").write_bytes(snapshot.diff or b"")
|
|
302
|
-
|
|
311
|
+
emit({"event": "started", "run": run, "params": params, "folder": folder})
|
|
303
312
|
# {invocation_dir} stays for configs written before runs were flat.
|
|
304
313
|
dirs = dict.fromkeys(PLACEHOLDERS, str(folder.resolve()))
|
|
305
314
|
running[run] = set()
|
|
@@ -337,7 +346,7 @@ async def run_experiment(
|
|
|
337
346
|
command_line=command_line,
|
|
338
347
|
)
|
|
339
348
|
del running[run], trees[run]
|
|
340
|
-
|
|
349
|
+
emit({"event": "ended", "run": run, **result})
|
|
341
350
|
ended[run] = params
|
|
342
351
|
results[run] = {"run": run, **result}
|
|
343
352
|
if result["exit_code"] == RETRY_EXIT_CODE and not (
|
|
@@ -349,7 +358,7 @@ async def run_experiment(
|
|
|
349
358
|
|
|
350
359
|
async def teardown() -> None:
|
|
351
360
|
if experiment.teardown is not None:
|
|
352
|
-
|
|
361
|
+
emit({"event": "tearing down"})
|
|
353
362
|
await invocation_hook(
|
|
354
363
|
command_root,
|
|
355
364
|
"teardown",
|
|
@@ -366,7 +375,7 @@ async def run_experiment(
|
|
|
366
375
|
# Like vitest's globalSetup: a failed setup still gets its teardown.
|
|
367
376
|
try:
|
|
368
377
|
if experiment.setup is not None:
|
|
369
|
-
|
|
378
|
+
emit({"event": "setting up"})
|
|
370
379
|
await invocation_hook(
|
|
371
380
|
command_root,
|
|
372
381
|
"setup",
|
|
@@ -380,7 +389,7 @@ async def run_experiment(
|
|
|
380
389
|
try:
|
|
381
390
|
await teardown()
|
|
382
391
|
except Exception as failure: # noqa: BLE001
|
|
383
|
-
|
|
392
|
+
emit({"event": "teardown failed", "error": str(failure)})
|
|
384
393
|
raise
|
|
385
394
|
finally:
|
|
386
395
|
for run in list(locks):
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Persist one invocation's run TUI events in the user's state folder."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from uuid import uuid4
|
|
9
|
+
|
|
10
|
+
from labnotes.models import LabnotesConfig
|
|
11
|
+
from labnotes.runner.run_folder import run_folder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def journal_folder(runs: Path) -> Path:
|
|
15
|
+
"""Keep journals out of the runs folder, where scripts expect only runs."""
|
|
16
|
+
home = os.environ.get("XDG_STATE_HOME") or Path.home() / ".local" / "state"
|
|
17
|
+
key = hashlib.sha256(str(runs.resolve()).encode()).hexdigest()[:16]
|
|
18
|
+
return Path(home) / "labnotes" / "run-state" / key
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RunState:
|
|
22
|
+
"""Append complete events and replay them from a disk cursor."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, path: Path) -> None:
|
|
25
|
+
self.path = path
|
|
26
|
+
self.offset = 0
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def create(cls, config: LabnotesConfig, experiment: str) -> "RunState":
|
|
30
|
+
"""Choose a journal unique to this invocation without touching disk yet."""
|
|
31
|
+
runs = run_folder(config.root, config.runs, experiment, "_").parent
|
|
32
|
+
stamp = time.strftime("%Y%m%d-%H%M%S", time.gmtime())
|
|
33
|
+
return cls(
|
|
34
|
+
journal_folder(runs) / f"{stamp}-{os.getpid()}-{uuid4().hex[:8]}.jsonl"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def record(self, event: dict[str, object]) -> None:
|
|
38
|
+
"""Write one event before callers display it."""
|
|
39
|
+
at = time.time()
|
|
40
|
+
stored = {**event, "at": at}
|
|
41
|
+
if event["event"] == "retrying":
|
|
42
|
+
stored["retry_at"] = at + float(str(event["seconds"]))
|
|
43
|
+
data = (json.dumps(stored, default=str) + "\n").encode()
|
|
44
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
self.path.touch(mode=0o600, exist_ok=True)
|
|
46
|
+
with self.path.open("ab") as file:
|
|
47
|
+
file.write(data)
|
|
48
|
+
|
|
49
|
+
def read(self) -> list[dict[str, object]]:
|
|
50
|
+
"""Return only complete new events."""
|
|
51
|
+
try:
|
|
52
|
+
with self.path.open("rb") as file:
|
|
53
|
+
file.seek(self.offset)
|
|
54
|
+
data = file.read()
|
|
55
|
+
except FileNotFoundError:
|
|
56
|
+
return []
|
|
57
|
+
try:
|
|
58
|
+
end = data.rindex(b"\n") + 1
|
|
59
|
+
except ValueError:
|
|
60
|
+
return []
|
|
61
|
+
self.offset += end
|
|
62
|
+
return [json.loads(line) for line in data[:end].splitlines()]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
from labnotes.models import LabnotesConfig, Listen, Report, Start
|
|
@@ -5,6 +6,18 @@ from labnotes.runner.finalize_lost_runs import finalize_lost_runs
|
|
|
5
6
|
from labnotes.runner.prepare_experiment import prepare_experiment
|
|
6
7
|
from labnotes.runner.project_snapshot import project_snapshot
|
|
7
8
|
from labnotes.runner.run_experiment import run_experiment
|
|
9
|
+
from labnotes.runner.run_state import RunState
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class ExperimentStart:
|
|
14
|
+
"""Keep a run invocation's journal path with its scheduler callback."""
|
|
15
|
+
|
|
16
|
+
run_state: RunState
|
|
17
|
+
start: Start
|
|
18
|
+
|
|
19
|
+
async def __call__(self, report: Report, listen: Listen) -> list[dict[str, object]]:
|
|
20
|
+
return await self.start(report, listen)
|
|
8
21
|
|
|
9
22
|
|
|
10
23
|
def start_experiment(
|
|
@@ -19,7 +32,9 @@ def start_experiment(
|
|
|
19
32
|
teardown: str | None,
|
|
20
33
|
command_line: str | None = None,
|
|
21
34
|
run_id: str | None = None,
|
|
22
|
-
) ->
|
|
35
|
+
) -> ExperimentStart:
|
|
36
|
+
state = RunState.create(config, name)
|
|
37
|
+
|
|
23
38
|
async def start(report: Report, listen: Listen) -> list[dict[str, object]]:
|
|
24
39
|
experiment, limit = await prepare_experiment(
|
|
25
40
|
config, name, experiment_config, concurrency, only, args, setup, teardown
|
|
@@ -39,6 +54,7 @@ def start_experiment(
|
|
|
39
54
|
snapshot=snapshot,
|
|
40
55
|
command_line=command_line,
|
|
41
56
|
run_id=run_id,
|
|
57
|
+
run_state=state,
|
|
42
58
|
)
|
|
43
59
|
|
|
44
|
-
return start
|
|
60
|
+
return ExperimentStart(state, start)
|
|
@@ -21,6 +21,7 @@ from labnotes.models import Controls, LabnotesConfig, Start
|
|
|
21
21
|
from labnotes.retries import RETRY_EXIT_CODE
|
|
22
22
|
from labnotes.runner.describe_event import describe_event
|
|
23
23
|
from labnotes.runner.param_args import param_args
|
|
24
|
+
from labnotes.runner.start_experiment import ExperimentStart
|
|
24
25
|
from labnotes.runner.stop_then_kill import GRACE_SECONDS, stop_then_kill
|
|
25
26
|
from labnotes.tables.open_tables import open_tables
|
|
26
27
|
from labnotes.tui.config import RUN_COLUMNS
|
|
@@ -119,7 +120,11 @@ class RunLiveView(Widget):
|
|
|
119
120
|
case "started":
|
|
120
121
|
self.idle = False
|
|
121
122
|
self.pending.discard(run)
|
|
122
|
-
self.started[run] =
|
|
123
|
+
self.started[run] = (
|
|
124
|
+
time.monotonic() - max(0.0, time.time() - float(str(event["at"])))
|
|
125
|
+
if "at" in event
|
|
126
|
+
else time.monotonic()
|
|
127
|
+
)
|
|
123
128
|
self.folders[run] = Path(str(event["folder"]))
|
|
124
129
|
args = " ".join(param_args(event["params"])) # type: ignore[arg-type]
|
|
125
130
|
shown = {
|
|
@@ -146,7 +151,11 @@ class RunLiveView(Widget):
|
|
|
146
151
|
self.unmeasured[run] = None
|
|
147
152
|
self.measure()
|
|
148
153
|
case "retrying":
|
|
149
|
-
self.waiting[run] = time.monotonic() +
|
|
154
|
+
self.waiting[run] = time.monotonic() + (
|
|
155
|
+
max(0.0, float(str(event["retry_at"])) - time.time())
|
|
156
|
+
if "retry_at" in event
|
|
157
|
+
else float(str(event["seconds"]))
|
|
158
|
+
)
|
|
150
159
|
self.tick()
|
|
151
160
|
self.show(describe_event(event))
|
|
152
161
|
case "gave up":
|
|
@@ -368,6 +377,8 @@ class RunApp(ViewSwitcher, App[None]):
|
|
|
368
377
|
self.shown: Experiment | None = None
|
|
369
378
|
self.left: dict[str, Widget] = {}
|
|
370
379
|
self.controls = self.body.controls
|
|
380
|
+
self.run_state = start.run_state if isinstance(start, ExperimentStart) else None
|
|
381
|
+
self.watching_state = False
|
|
371
382
|
|
|
372
383
|
def compose(self) -> ComposeResult:
|
|
373
384
|
yield Header()
|
|
@@ -456,8 +467,19 @@ class RunApp(ViewSwitcher, App[None]):
|
|
|
456
467
|
stop_then_kill(self.stop_runs, signal.SIGHUP)
|
|
457
468
|
|
|
458
469
|
def show_event(self, event: dict[str, object]) -> None:
|
|
459
|
-
if self.
|
|
460
|
-
self.
|
|
470
|
+
if self.run_state is None:
|
|
471
|
+
if self.body_ready:
|
|
472
|
+
self.body.show_event(event)
|
|
473
|
+
return
|
|
474
|
+
if not self.watching_state:
|
|
475
|
+
self.watching_state = True
|
|
476
|
+
self.set_interval(0.25, self.refresh_state)
|
|
477
|
+
self.refresh_state()
|
|
478
|
+
|
|
479
|
+
def refresh_state(self) -> None:
|
|
480
|
+
if self.run_state is not None and self.body_ready:
|
|
481
|
+
for event in self.run_state.read():
|
|
482
|
+
self.body.show_event(event)
|
|
461
483
|
|
|
462
484
|
def stop_runs(self) -> None:
|
|
463
485
|
if self.body_ready and self.body.idle:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|