labnotes 0.2.13__tar.gz → 0.2.14__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.13 → labnotes-0.2.14}/PKG-INFO +1 -1
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/experiments.py +9 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/describe_event.py +2 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/run_experiment.py +57 -11
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/start_experiment.py +3 -2
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/runs_browser.py +62 -2
- {labnotes-0.2.13 → labnotes-0.2.14}/testing-conventions.toml +2 -2
- {labnotes-0.2.13 → labnotes-0.2.14}/.gitignore +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/CHANGELOG.md +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/MIGRATIONS.md +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/README.md +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/pyproject.toml +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/__init__.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/__main__.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/cli.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/command_line_group.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/config_migration.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/context.md +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/copy_experiment.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/create_app.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/create_note.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/data_html.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/experiment_definition.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/format_started.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/format_value.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/get_state.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/grid_cell.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/init.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/is_non_negative_number.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/latest_log.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/load_config.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/load_run.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/log.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/log_path.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/memory_interval.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/models.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/note_path.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/output_html.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/param_names.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/parse_param_value.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/parse_params.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/parse_subset.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/pivot_runs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/read_config.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/read_path.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/retries.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/run_columns.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/run_output.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/cgroup_memory.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/check_args.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/check_hook_placeholders.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/check_subset.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/claim_run.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/command_flags.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/container_cgroup.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/container_memory.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/continue_run.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/docker_output.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/fill_dirs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/fill_experiment_env.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/finalize_lost_runs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/first_invocation.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/flag_name.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/group_args.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/hold_experiment.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/in_subset.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/invocation_hook.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/invocation_meta.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/override_flags.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/placeholders.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/project_snapshot.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/record_memory.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/run_git.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/run_locked.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/untracked_diff.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/runs_view_command.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/save_note.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/server.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/set_state.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/state_path.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/list_resumable_runs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/read_memory.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/read_run_file.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/runs_columns.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/check_view_response.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/format_cell.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/guard_view.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/load_view.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/native_rows.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/run_panel.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/runs_view.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/table_columns.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/table_rows.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/view_column.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/view_pane.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/view_process.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/tui/view_switcher.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/utils/process_alive.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/utils/tree_usage.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/view/__init__.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/view/answer.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/view/build.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/view/changed.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/view/experiment.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/view/respond.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/view/serve.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/view_specs.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/tests/conftest.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/tests/e2e/conftest.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/tests/integration/conftest.py +0 -0
- {labnotes-0.2.13 → labnotes-0.2.14}/uv.lock +0 -0
|
@@ -57,6 +57,7 @@ class PassthroughCommand(click.Command):
|
|
|
57
57
|
help="With run: how many runs at once. Overrides experiment.toml and"
|
|
58
58
|
" .labnotes.toml; 1 if none sets it.",
|
|
59
59
|
)
|
|
60
|
+
@click.option("--run-id", help="With run: restart only this failed run by id.")
|
|
60
61
|
@click.option(
|
|
61
62
|
"-e",
|
|
62
63
|
"--experiment-config",
|
|
@@ -114,6 +115,7 @@ def experiments(
|
|
|
114
115
|
action: str | None,
|
|
115
116
|
output_format: str | None,
|
|
116
117
|
concurrency: int | None,
|
|
118
|
+
run_id: str | None,
|
|
117
119
|
experiment_config: Path | None,
|
|
118
120
|
timeout: int | None,
|
|
119
121
|
setup: str | None,
|
|
@@ -150,6 +152,10 @@ def experiments(
|
|
|
150
152
|
click.echo(f"Created {path / 'experiment.toml'}")
|
|
151
153
|
return
|
|
152
154
|
case str(), "run":
|
|
155
|
+
if run_id is not None and (ctx.args or passthrough is not None):
|
|
156
|
+
raise click.UsageError(
|
|
157
|
+
"--run-id uses the run's recorded params and -- ARGS."
|
|
158
|
+
)
|
|
153
159
|
try:
|
|
154
160
|
only = parse_subset(ctx.args)
|
|
155
161
|
except ValueError as error:
|
|
@@ -165,6 +171,7 @@ def experiments(
|
|
|
165
171
|
setup,
|
|
166
172
|
teardown,
|
|
167
173
|
ctx.meta.get(COMMAND_LINE),
|
|
174
|
+
run_id,
|
|
168
175
|
)
|
|
169
176
|
try:
|
|
170
177
|
if sys.stdout.isatty() and sys.stderr.isatty():
|
|
@@ -185,6 +192,8 @@ def experiments(
|
|
|
185
192
|
return
|
|
186
193
|
if passthrough is not None:
|
|
187
194
|
raise click.UsageError("-- ARGS needs run.")
|
|
195
|
+
if run_id is not None:
|
|
196
|
+
raise click.UsageError("--run-id needs run.")
|
|
188
197
|
if ctx.args:
|
|
189
198
|
raise click.UsageError(f"Unexpected {ctx.args[0]}. --PARAM=VALUE needs run.")
|
|
190
199
|
if action not in (None, "runs"):
|
|
@@ -20,6 +20,8 @@ def describe_event(event: dict[str, object]) -> str:
|
|
|
20
20
|
return f"Stopping {event['run']}."
|
|
21
21
|
case "killing run":
|
|
22
22
|
return f"Killing {event['run']}."
|
|
23
|
+
case "held":
|
|
24
|
+
return f"{event['run']} held by another run."
|
|
23
25
|
case "retried" if event["retry"] == event["run"]:
|
|
24
26
|
return f"{event['run']} retried in its folder."
|
|
25
27
|
case "retried":
|
|
@@ -16,7 +16,7 @@ from labnotes.models import (
|
|
|
16
16
|
)
|
|
17
17
|
from labnotes.retries import RETRY_EXIT_CODE, RETRY_SECONDS
|
|
18
18
|
from labnotes.runner.check_hook_placeholders import check_hook_placeholders
|
|
19
|
-
from labnotes.runner.claim_run import claim_run
|
|
19
|
+
from labnotes.runner.claim_run import LOCK_FILE, claim_run
|
|
20
20
|
from labnotes.runner.execute_run import execute_run
|
|
21
21
|
from labnotes.runner.fill_experiment_env import fill_experiment_env
|
|
22
22
|
from labnotes.runner.fill_dirs import fill_dirs
|
|
@@ -29,6 +29,7 @@ from labnotes.runner.placeholders import PLACEHOLDERS
|
|
|
29
29
|
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
|
+
from labnotes.runner.run_locked import run_locked
|
|
32
33
|
from labnotes.runner.signal_tree import signal_tree
|
|
33
34
|
from labnotes.state_path import STATE_ENV
|
|
34
35
|
from labnotes.tables.experiment_run_ids import experiment_run_ids
|
|
@@ -53,22 +54,41 @@ async def run_experiment(
|
|
|
53
54
|
environment: dict[str, str] | None = None,
|
|
54
55
|
snapshot: ProjectSnapshot | None = None,
|
|
55
56
|
command_line: str | None = None,
|
|
57
|
+
run_id: str | None = None,
|
|
56
58
|
) -> list[dict[str, object]]:
|
|
57
59
|
command_root = snapshot.root if snapshot is not None else config.root
|
|
58
60
|
inherited = dict(os.environ) if environment is None else environment
|
|
59
61
|
tables = open_tables(config)
|
|
60
62
|
existing = await experiment_run_ids(tables, name)
|
|
61
63
|
counted = await list_experiment_runs(tables, name, experiment.counts)
|
|
62
|
-
|
|
64
|
+
held = [
|
|
65
|
+
r
|
|
66
|
+
for r in counted
|
|
67
|
+
if not r["counted"]
|
|
68
|
+
and r.get("folder") is not None
|
|
69
|
+
and (config.root / str(r["folder"]) / LOCK_FILE).exists()
|
|
70
|
+
and run_locked(config.root / str(r["folder"]))
|
|
71
|
+
]
|
|
72
|
+
held_ids = {str(r["id"]) for r in held}
|
|
73
|
+
finished = [
|
|
74
|
+
json.loads(str(r["params"]))
|
|
75
|
+
for r in counted
|
|
76
|
+
if r["counted"] or str(r.get("id")) in held_ids
|
|
77
|
+
]
|
|
63
78
|
planned: set[str] = set()
|
|
64
79
|
|
|
65
80
|
def taken(run: str) -> bool:
|
|
66
81
|
return run in planned or run in existing
|
|
67
82
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
if run_id is None:
|
|
84
|
+
grid = [
|
|
85
|
+
cell
|
|
86
|
+
for cell in param_grid(experiment.params)
|
|
87
|
+
if in_subset(cell, only or {})
|
|
88
|
+
]
|
|
89
|
+
cells = plan_runs(grid, experiment.repeats, finished)
|
|
90
|
+
else:
|
|
91
|
+
cells = []
|
|
72
92
|
|
|
73
93
|
locks: dict[str, int] = {}
|
|
74
94
|
|
|
@@ -81,8 +101,7 @@ async def run_experiment(
|
|
|
81
101
|
return True
|
|
82
102
|
|
|
83
103
|
def release(run: str) -> None:
|
|
84
|
-
|
|
85
|
-
os.close(locks.pop(run))
|
|
104
|
+
os.close(locks.pop(run))
|
|
86
105
|
|
|
87
106
|
resumable: list[Entry] = []
|
|
88
107
|
if experiment.continues:
|
|
@@ -91,6 +110,30 @@ async def run_experiment(
|
|
|
91
110
|
for row in await list_resumable_runs(tables, name, experiment.counts)
|
|
92
111
|
]
|
|
93
112
|
plan: list[Entry] = []
|
|
113
|
+
target_folder: Path | None = None
|
|
114
|
+
if run_id is not None:
|
|
115
|
+
selected = next((row for row in counted if row["id"] == run_id), None)
|
|
116
|
+
if selected is None:
|
|
117
|
+
raise ValueError(f"No run {run_id} in {name}.")
|
|
118
|
+
if selected["ended"] is None or (
|
|
119
|
+
selected["exit_code"] == 0 and not selected["error"]
|
|
120
|
+
):
|
|
121
|
+
raise ValueError(f"Run {run_id} has not failed.")
|
|
122
|
+
source = config.root / str(selected["folder"])
|
|
123
|
+
record = json.loads((source / "run.json").read_text())
|
|
124
|
+
args = record.get("args", [])
|
|
125
|
+
params = json.loads(str(selected["params"]))
|
|
126
|
+
lock = claim_run(source)
|
|
127
|
+
if lock is None:
|
|
128
|
+
raise ValueError(f"Run {run_id} is held by another run.")
|
|
129
|
+
locks[run_id] = lock
|
|
130
|
+
if experiment.continues:
|
|
131
|
+
target_folder = source
|
|
132
|
+
plan.append((run_id, params))
|
|
133
|
+
else:
|
|
134
|
+
new_id = new_stamped_run_id(taken)
|
|
135
|
+
planned.add(new_id)
|
|
136
|
+
plan.append((new_id, params))
|
|
94
137
|
for params in cells:
|
|
95
138
|
resumed = next(
|
|
96
139
|
(
|
|
@@ -126,6 +169,8 @@ async def run_experiment(
|
|
|
126
169
|
"concurrency": concurrency,
|
|
127
170
|
}
|
|
128
171
|
)
|
|
172
|
+
for row in held:
|
|
173
|
+
report({"event": "held", "run": str(row["id"])})
|
|
129
174
|
running: dict[str, set[asyncio.subprocess.Process]] = {}
|
|
130
175
|
trees: dict[str, set[psutil.Process]] = {}
|
|
131
176
|
stopped: set[str] = set()
|
|
@@ -246,10 +291,11 @@ async def run_experiment(
|
|
|
246
291
|
fill()
|
|
247
292
|
|
|
248
293
|
async def start(run: str, params: dict[str, object]) -> None:
|
|
249
|
-
folder = run_folder(config.root, config.runs, name, run)
|
|
294
|
+
folder = target_folder or run_folder(config.root, config.runs, name, run)
|
|
250
295
|
folder.mkdir(parents=True, exist_ok=True)
|
|
251
|
-
if
|
|
252
|
-
|
|
296
|
+
if not claim(run):
|
|
297
|
+
report({"event": "held", "run": run})
|
|
298
|
+
return
|
|
253
299
|
if snapshot is not None and snapshot.sha is not None:
|
|
254
300
|
(folder / "git-sha.txt").write_text(snapshot.sha + "\n")
|
|
255
301
|
(folder / "git-diff.patch").write_bytes(snapshot.diff or b"")
|
|
@@ -2,7 +2,6 @@ from pathlib import Path
|
|
|
2
2
|
|
|
3
3
|
from labnotes.models import LabnotesConfig, Listen, Report, Start
|
|
4
4
|
from labnotes.runner.finalize_lost_runs import finalize_lost_runs
|
|
5
|
-
from labnotes.runner.hold_experiment import hold_experiment
|
|
6
5
|
from labnotes.runner.prepare_experiment import prepare_experiment
|
|
7
6
|
from labnotes.runner.project_snapshot import project_snapshot
|
|
8
7
|
from labnotes.runner.run_experiment import run_experiment
|
|
@@ -19,13 +18,14 @@ def start_experiment(
|
|
|
19
18
|
setup: str | None,
|
|
20
19
|
teardown: str | None,
|
|
21
20
|
command_line: str | None = None,
|
|
21
|
+
run_id: str | None = None,
|
|
22
22
|
) -> Start:
|
|
23
23
|
async def start(report: Report, listen: Listen) -> list[dict[str, object]]:
|
|
24
24
|
experiment, limit = await prepare_experiment(
|
|
25
25
|
config, name, experiment_config, concurrency, only, args, setup, teardown
|
|
26
26
|
)
|
|
27
27
|
await finalize_lost_runs(config)
|
|
28
|
-
with project_snapshot(config.root) as snapshot
|
|
28
|
+
with project_snapshot(config.root) as snapshot:
|
|
29
29
|
return await run_experiment(
|
|
30
30
|
config,
|
|
31
31
|
name,
|
|
@@ -38,6 +38,7 @@ def start_experiment(
|
|
|
38
38
|
args=args,
|
|
39
39
|
snapshot=snapshot,
|
|
40
40
|
command_line=command_line,
|
|
41
|
+
run_id=run_id,
|
|
41
42
|
)
|
|
42
43
|
|
|
43
44
|
return start
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
1
4
|
from collections.abc import Mapping
|
|
2
5
|
from pathlib import Path
|
|
3
|
-
from typing import Any
|
|
6
|
+
from typing import Any, cast
|
|
4
7
|
|
|
5
8
|
from textual.app import ComposeResult
|
|
6
9
|
from textual.binding import Binding
|
|
@@ -8,6 +11,8 @@ from textual.widget import Widget
|
|
|
8
11
|
from textual.widgets import DataTable, Static
|
|
9
12
|
|
|
10
13
|
from labnotes.runs_view_command import runs_view_command
|
|
14
|
+
from labnotes.models import LabnotesConfig
|
|
15
|
+
from labnotes.runner.claim_run import claim_run
|
|
11
16
|
from labnotes.tui.fill_table import fill_table
|
|
12
17
|
from labnotes.tui.run_output import RunOutput
|
|
13
18
|
from labnotes.tui.run_panel import RunPanel
|
|
@@ -30,6 +35,7 @@ class RunsBrowser(Widget):
|
|
|
30
35
|
BINDINGS = [
|
|
31
36
|
Binding("enter", "open_row", "Open", priority=True),
|
|
32
37
|
Binding("escape", "close_run", "Back"),
|
|
38
|
+
Binding("r", "restart_run", "Restart", priority=True),
|
|
33
39
|
]
|
|
34
40
|
|
|
35
41
|
def __init__(self) -> None:
|
|
@@ -38,6 +44,7 @@ class RunsBrowser(Widget):
|
|
|
38
44
|
self.view: RunsView | None = None
|
|
39
45
|
self.experiment = Experiment(Path(), "", ())
|
|
40
46
|
self.viewing: str | None = None
|
|
47
|
+
self.restart_status = ""
|
|
41
48
|
|
|
42
49
|
def compose(self) -> ComposeResult:
|
|
43
50
|
yield RunsTable(id="runs", cursor_type="row")
|
|
@@ -48,6 +55,8 @@ class RunsBrowser(Widget):
|
|
|
48
55
|
return self.viewing is None
|
|
49
56
|
if action == "close_run":
|
|
50
57
|
return self.viewing is not None
|
|
58
|
+
if action == "restart_run":
|
|
59
|
+
return self.viewing is None
|
|
51
60
|
return True
|
|
52
61
|
|
|
53
62
|
def on_labnotes_changed(self, message: Changed) -> None:
|
|
@@ -103,11 +112,62 @@ class RunsBrowser(Widget):
|
|
|
103
112
|
self.show_status()
|
|
104
113
|
|
|
105
114
|
def show_status(self) -> None:
|
|
106
|
-
text = "" if self.view is None else self.view.status()
|
|
115
|
+
text = self.restart_status or ("" if self.view is None else self.view.status())
|
|
107
116
|
status = self.query_one("#view-status", Static)
|
|
108
117
|
status.update(text)
|
|
109
118
|
status.display = bool(text) and self.viewing is None
|
|
110
119
|
|
|
120
|
+
def action_restart_run(self) -> None:
|
|
121
|
+
table = self.query_one(DataTable)
|
|
122
|
+
if not table.row_count:
|
|
123
|
+
return
|
|
124
|
+
folder = str(
|
|
125
|
+
table.coordinate_to_cell_key(table.cursor_coordinate).row_key.value
|
|
126
|
+
)
|
|
127
|
+
row = next(row for row in self.experiment.runs if str(row["folder"]) == folder)
|
|
128
|
+
run_id = str(row["id"])
|
|
129
|
+
if row.get("ended") is None or (
|
|
130
|
+
row.get("exit_code") == 0 and not row.get("error")
|
|
131
|
+
):
|
|
132
|
+
self.restart_status = f"{run_id} has not failed."
|
|
133
|
+
self.show_status()
|
|
134
|
+
return
|
|
135
|
+
lock = claim_run(self.experiment.root / folder)
|
|
136
|
+
if lock is None:
|
|
137
|
+
self.restart_status = f"{run_id} is held by another run."
|
|
138
|
+
self.show_status()
|
|
139
|
+
return
|
|
140
|
+
os.close(lock)
|
|
141
|
+
self.restart_status = f"Restarting {run_id}..."
|
|
142
|
+
self.show_status()
|
|
143
|
+
self.run_worker(self.restart(run_id), exit_on_error=False)
|
|
144
|
+
|
|
145
|
+
async def restart(self, run_id: str) -> None:
|
|
146
|
+
config = cast(LabnotesConfig, getattr(self.app, "config"))
|
|
147
|
+
process = await asyncio.create_subprocess_exec(
|
|
148
|
+
sys.executable,
|
|
149
|
+
"-m",
|
|
150
|
+
"labnotes",
|
|
151
|
+
"-c",
|
|
152
|
+
str(config.path),
|
|
153
|
+
"experiments",
|
|
154
|
+
self.experiment.name,
|
|
155
|
+
"run",
|
|
156
|
+
"--run-id",
|
|
157
|
+
run_id,
|
|
158
|
+
stdout=asyncio.subprocess.PIPE,
|
|
159
|
+
stderr=asyncio.subprocess.PIPE,
|
|
160
|
+
)
|
|
161
|
+
stdout, stderr = await process.communicate()
|
|
162
|
+
if process.returncode == 0:
|
|
163
|
+
self.restart_status = (
|
|
164
|
+
f"Restarted {run_id}. Select the run to view its output."
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
detail = stderr.decode().strip() or stdout.decode().strip()
|
|
168
|
+
self.restart_status = f"Could not restart {run_id}: {detail}"
|
|
169
|
+
self.show_status()
|
|
170
|
+
|
|
111
171
|
async def action_open_row(self) -> None:
|
|
112
172
|
table = self.query_one(DataTable)
|
|
113
173
|
if not table.row_count:
|
|
@@ -19,7 +19,7 @@ reason = "WorkerState members are singletons, so is equals =="
|
|
|
19
19
|
[[python.exempt]]
|
|
20
20
|
path = "src/labnotes/runner/run_experiment.py"
|
|
21
21
|
rules = ["mutation"]
|
|
22
|
-
lines = [
|
|
22
|
+
lines = [235, 343]
|
|
23
23
|
reason = "a retry is only scheduled below the budget and each one adds one, so tried never passes retries: >= equals ==; both sides are small ints, which CPython caches, so is equals =="
|
|
24
24
|
|
|
25
25
|
[[python.exempt]]
|
|
@@ -31,7 +31,7 @@ reason = "exit_code is non-negative here; when bit 7 is set, - 128 equals ^ 128,
|
|
|
31
31
|
[[python.exempt]]
|
|
32
32
|
path = "src/labnotes/experiments.py"
|
|
33
33
|
rules = ["mutation"]
|
|
34
|
-
lines = [
|
|
34
|
+
lines = [213]
|
|
35
35
|
reason = "click hands back the \"json\" choice literal itself, so is not equals !="
|
|
36
36
|
|
|
37
37
|
[[python.exempt]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|