labnotes 0.2.8__tar.gz → 0.2.10__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.8 → labnotes-0.2.10}/PKG-INFO +1 -1
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/experiments.py +1 -1
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/load_experiment.py +8 -2
- labnotes-0.2.10/src/labnotes/param_names.py +9 -0
- labnotes-0.2.10/src/labnotes/run_columns.py +7 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/read_experiment_config.py +10 -4
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/browse_app.py +2 -16
- labnotes-0.2.10/src/labnotes/tui/experiment_screen.py +104 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/experiments_screen.py +2 -10
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/format_cell.py +4 -2
- labnotes-0.2.10/src/labnotes/tui/guard_view.py +35 -0
- labnotes-0.2.10/src/labnotes/tui/load_view.py +27 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/native_rows.py +3 -3
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/run_app.py +185 -60
- labnotes-0.2.10/src/labnotes/tui/run_cells.py +12 -0
- labnotes-0.2.8/src/labnotes/tui/run_screen.py → labnotes-0.2.10/src/labnotes/tui/run_panel.py +5 -8
- labnotes-0.2.10/src/labnotes/tui/run_tui.py +20 -0
- labnotes-0.2.10/src/labnotes/tui/runs_browser.py +132 -0
- labnotes-0.2.10/src/labnotes/tui/table_columns.py +8 -0
- labnotes-0.2.10/src/labnotes/tui/table_rows.py +12 -0
- labnotes-0.2.10/src/labnotes/tui/view_pane.py +54 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/view_process.py +1 -1
- labnotes-0.2.10/src/labnotes/tui/view_switcher.py +53 -0
- labnotes-0.2.10/src/labnotes/view/__init__.py +22 -0
- labnotes-0.2.10/src/labnotes/view/changed.py +19 -0
- labnotes-0.2.10/src/labnotes/view/experiment.py +20 -0
- labnotes-0.2.10/src/labnotes/view_specs.py +10 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/testing-conventions.toml +9 -15
- labnotes-0.2.8/src/labnotes/tui/experiment_screen.py +0 -154
- labnotes-0.2.8/src/labnotes/tui/group_cells.py +0 -36
- labnotes-0.2.8/src/labnotes/tui/invocation_columns.py +0 -9
- labnotes-0.2.8/src/labnotes/tui/invocation_rows.py +0 -32
- labnotes-0.2.8/src/labnotes/tui/run_cells.py +0 -22
- labnotes-0.2.8/src/labnotes/tui/run_tui.py +0 -11
- labnotes-0.2.8/src/labnotes/view/__init__.py +0 -3
- {labnotes-0.2.8 → labnotes-0.2.10}/.gitignore +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/CHANGELOG.md +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/MIGRATIONS.md +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/README.md +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/pyproject.toml +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/__init__.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/__main__.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/cli.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/command_line_group.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/config_migration.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/context.md +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/copy_experiment.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/create_app.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/create_note.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/data_html.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/experiment_definition.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/format_started.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/format_value.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/get_state.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/grid_cell.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/init.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/is_non_negative_number.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/latest_log.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/load_config.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/load_run.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/log.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/log_path.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/memory_interval.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/models.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/note_path.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/output_html.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/parse_param_value.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/parse_params.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/parse_subset.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/pivot_runs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/read_config.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/read_path.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/retries.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/run_output.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/cgroup_memory.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/check_args.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/check_hook_placeholders.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/check_subset.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/claim_run.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/command_flags.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/container_cgroup.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/container_memory.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/continue_run.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/docker_output.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/fill_dirs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/fill_experiment_env.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/finalize_lost_runs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/first_invocation.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/flag_name.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/group_args.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/hold_experiment.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/in_subset.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/invocation_hook.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/invocation_meta.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/override_flags.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/placeholders.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/project_snapshot.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/record_memory.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/run_experiment.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/run_git.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/run_locked.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/start_experiment.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/untracked_diff.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runs_view_command.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/save_note.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/server.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/set_state.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/state_path.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/list_resumable_runs.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_memory.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_run_file.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/runs_columns.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/check_view_response.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/runs_view.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/view_column.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/process_alive.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/tree_usage.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/view/answer.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/view/build.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/view/respond.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/view/serve.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/tests/conftest.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/tests/e2e/conftest.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/tests/integration/conftest.py +0 -0
- {labnotes-0.2.8 → labnotes-0.2.10}/uv.lock +0 -0
|
@@ -168,7 +168,7 @@ def experiments(
|
|
|
168
168
|
)
|
|
169
169
|
try:
|
|
170
170
|
if sys.stdout.isatty() and sys.stderr.isatty():
|
|
171
|
-
results = run_tui(start, name)
|
|
171
|
+
results = run_tui(start, name, config, experiment_config)
|
|
172
172
|
else:
|
|
173
173
|
results = run_headless(start)
|
|
174
174
|
except ValueError as error:
|
|
@@ -19,9 +19,11 @@ from labnotes.parse_run_view import parse_run_view
|
|
|
19
19
|
from labnotes.pivot_runs import pivot_runs
|
|
20
20
|
from labnotes.python_blocks import python_blocks
|
|
21
21
|
from labnotes.render_markdown import render_markdown
|
|
22
|
+
from labnotes.run_columns import run_columns
|
|
22
23
|
from labnotes.runs_view_command import runs_view_command
|
|
23
24
|
from labnotes.sort_href import sort_href
|
|
24
25
|
from labnotes.sql_quote import quote_string
|
|
26
|
+
from labnotes.view_specs import view_specs
|
|
25
27
|
from labnotes.summarize_config import summarize_config
|
|
26
28
|
from labnotes.utils.process_alive import process_alive
|
|
27
29
|
|
|
@@ -49,7 +51,7 @@ async def load_experiment(
|
|
|
49
51
|
varies = cast(dict[str, list[object]], summary["varies"])
|
|
50
52
|
source = experiment_runs(name, list(varies))
|
|
51
53
|
summary = await check_counts(tables, source, summary)
|
|
52
|
-
shown =
|
|
54
|
+
shown = run_columns(config)
|
|
53
55
|
choices = {
|
|
54
56
|
key: {format_value(value): json.dumps(value) for value in values}
|
|
55
57
|
for key, values in varies.items()
|
|
@@ -70,7 +72,9 @@ async def load_experiment(
|
|
|
70
72
|
pid, created if isinstance(created, int | float) else None
|
|
71
73
|
)
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
for run in runs:
|
|
76
|
+
run["labnotes_running"] = is_running(run)
|
|
77
|
+
runs_live = any(run["labnotes_running"] for run in runs)
|
|
74
78
|
nested = await nests_runs(tables, source)
|
|
75
79
|
groups = (
|
|
76
80
|
group_runs(runs, list(choices), await count_groups(tables, source))
|
|
@@ -92,6 +96,7 @@ async def load_experiment(
|
|
|
92
96
|
return {
|
|
93
97
|
"name": row["name"],
|
|
94
98
|
"display_name": row["display_name"],
|
|
99
|
+
"config": config,
|
|
95
100
|
"config_source": row["source"],
|
|
96
101
|
"counts_set": "counts" in (config or {}),
|
|
97
102
|
**summary,
|
|
@@ -115,6 +120,7 @@ async def load_experiment(
|
|
|
115
120
|
"run_view": view,
|
|
116
121
|
"run_choices": choices,
|
|
117
122
|
"runs_view": runs_view_command(config),
|
|
123
|
+
"views": view_specs(config),
|
|
118
124
|
"sort_hrefs": {
|
|
119
125
|
column: sort_href(view, column) for column in ["status", *shown]
|
|
120
126
|
},
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import cast
|
|
3
|
+
|
|
4
|
+
from labnotes.summarize_config import summarize_config
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def param_names(config: Mapping[str, object] | None) -> list[str]:
|
|
8
|
+
summary = summarize_config(None if config is None else dict(config))
|
|
9
|
+
return [key for key in cast(dict[str, object], summary["varies"]) if key != "id"]
|
|
@@ -30,7 +30,7 @@ SETTINGS = {
|
|
|
30
30
|
"from",
|
|
31
31
|
}
|
|
32
32
|
HOOKS = ("setup", "teardown")
|
|
33
|
-
|
|
33
|
+
MAX_VIEWS = 8
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def read_experiment_config(
|
|
@@ -107,12 +107,18 @@ def read_experiment_config(
|
|
|
107
107
|
views = values.get("views", {})
|
|
108
108
|
if not isinstance(views, dict):
|
|
109
109
|
raise ValueError(f"{path}: views must be a table, like [views]")
|
|
110
|
-
unknown = sorted(views.keys() - VIEWS)
|
|
111
|
-
if unknown:
|
|
112
|
-
raise ValueError(f"{path}: unknown keys in views: {', '.join(unknown)}")
|
|
113
110
|
runs = views.get("runs")
|
|
114
111
|
if runs is not None and (not isinstance(runs, str) or not runs.strip()):
|
|
115
112
|
raise ValueError(f"{path}: views.runs must be a command")
|
|
113
|
+
shown = {name: spec for name, spec in views.items() if name != "runs"}
|
|
114
|
+
if len(shown) > MAX_VIEWS:
|
|
115
|
+
raise ValueError(f"{path}: views holds at most {MAX_VIEWS} views besides runs")
|
|
116
|
+
for name, spec in shown.items():
|
|
117
|
+
source, _, cls = spec.rpartition(":") if isinstance(spec, str) else ("", "", "")
|
|
118
|
+
if not source.strip() or not cls.strip():
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f'{path}: views.{name} must be "path/to/file.py:ClassName"'
|
|
121
|
+
)
|
|
116
122
|
return ExperimentConfig(
|
|
117
123
|
command,
|
|
118
124
|
repeats,
|
|
@@ -5,8 +5,6 @@ from labnotes.models import LabnotesConfig
|
|
|
5
5
|
from labnotes.tables.open_tables import open_tables
|
|
6
6
|
from labnotes.tui.experiment_screen import ExperimentScreen
|
|
7
7
|
from labnotes.tui.experiments_screen import ExperimentsScreen
|
|
8
|
-
from labnotes.tui.runs_view import Request
|
|
9
|
-
from labnotes.tui.view_process import ViewProcess
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
class BrowseApp(App[BaseException | None]):
|
|
@@ -17,23 +15,11 @@ class BrowseApp(App[BaseException | None]):
|
|
|
17
15
|
self.config = config
|
|
18
16
|
self.experiment_name = name
|
|
19
17
|
self.title = config.root.name
|
|
20
|
-
self.views: dict[str, ViewProcess] = {}
|
|
21
18
|
|
|
22
19
|
def on_mount(self) -> None:
|
|
23
20
|
tables = open_tables(self.config, run_files=True)
|
|
24
|
-
self.push_screen(ExperimentsScreen(tables, self.config
|
|
21
|
+
self.push_screen(ExperimentsScreen(tables, self.config))
|
|
25
22
|
if self.experiment_name is not None:
|
|
26
23
|
self.push_screen(
|
|
27
|
-
ExperimentScreen(
|
|
28
|
-
tables, self.config, self.experiment_name, self.view_request
|
|
29
|
-
)
|
|
24
|
+
ExperimentScreen(tables, self.config, self.experiment_name)
|
|
30
25
|
)
|
|
31
|
-
|
|
32
|
-
def view_request(self, command: str) -> Request:
|
|
33
|
-
if command not in self.views:
|
|
34
|
-
self.views[command] = ViewProcess(command, self.config.root)
|
|
35
|
-
return self.views[command].request
|
|
36
|
-
|
|
37
|
-
async def on_unmount(self) -> None:
|
|
38
|
-
for process in self.views.values():
|
|
39
|
-
await process.close()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from types import MappingProxyType
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from dirsql import DirSQL
|
|
5
|
+
from starlette.datastructures import QueryParams
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.binding import Binding
|
|
8
|
+
from textual.screen import Screen
|
|
9
|
+
from textual.widget import Widget
|
|
10
|
+
from textual.widgets import ContentSwitcher, Footer, Header
|
|
11
|
+
from textual.worker import Worker, WorkerState
|
|
12
|
+
|
|
13
|
+
from labnotes.load_experiment import load_experiment
|
|
14
|
+
from labnotes.models import LabnotesConfig
|
|
15
|
+
from labnotes.tui.native_rows import native_rows
|
|
16
|
+
from labnotes.tui.reload_on_change import reload_on_change
|
|
17
|
+
from labnotes.tui.stopped_title import stopped_title
|
|
18
|
+
from labnotes.tui.view_pane import ViewPane
|
|
19
|
+
from labnotes.tui.view_switcher import ViewSwitcher
|
|
20
|
+
from labnotes.view.experiment import Experiment
|
|
21
|
+
from labnotes.watch_rows import watch_rows
|
|
22
|
+
|
|
23
|
+
WATCHED = {"runs", "experiment_configs"}
|
|
24
|
+
BUILT_IN = "labnotes.tui.runs_browser:RunsBrowser"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ExperimentScreen(ViewSwitcher, Screen[None]):
|
|
28
|
+
BINDINGS = [Binding("escape", "back", "Back")]
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
tables: DirSQL,
|
|
33
|
+
config: LabnotesConfig,
|
|
34
|
+
name: str,
|
|
35
|
+
) -> None:
|
|
36
|
+
super().__init__()
|
|
37
|
+
self.tables = tables
|
|
38
|
+
self.config = config
|
|
39
|
+
self.experiment_name = name
|
|
40
|
+
self.found = False
|
|
41
|
+
self.shown: Experiment | None = None
|
|
42
|
+
self.left: dict[str, Widget] = {}
|
|
43
|
+
|
|
44
|
+
def compose(self) -> ComposeResult:
|
|
45
|
+
yield Header()
|
|
46
|
+
with ContentSwitcher(initial="view-1"):
|
|
47
|
+
yield ViewPane("runs", BUILT_IN, self.config.root, id="view-1")
|
|
48
|
+
yield Footer()
|
|
49
|
+
|
|
50
|
+
def on_mount(self) -> None:
|
|
51
|
+
self.sub_title = self.experiment_name
|
|
52
|
+
self.run_worker(
|
|
53
|
+
reload_on_change(
|
|
54
|
+
watch_rows(self.tables, WATCHED, self.experiment_name),
|
|
55
|
+
self.load,
|
|
56
|
+
self.stopped,
|
|
57
|
+
),
|
|
58
|
+
exit_on_error=False,
|
|
59
|
+
)
|
|
60
|
+
self.run_worker(self.watch_files(), exit_on_error=False)
|
|
61
|
+
|
|
62
|
+
def on_worker_state_changed(self, event: Worker.StateChanged) -> None:
|
|
63
|
+
if event.state == WorkerState.ERROR:
|
|
64
|
+
self.app.exit(event.worker.error)
|
|
65
|
+
|
|
66
|
+
def stopped(self, error: BaseException | None) -> None:
|
|
67
|
+
self.sub_title = stopped_title(self.sub_title, error)
|
|
68
|
+
|
|
69
|
+
async def load(self) -> None:
|
|
70
|
+
loaded: dict[str, Any] | None = await load_experiment(
|
|
71
|
+
self.tables,
|
|
72
|
+
self.config.root,
|
|
73
|
+
self.experiment_name,
|
|
74
|
+
QueryParams(),
|
|
75
|
+
)
|
|
76
|
+
if loaded is None and not self.found:
|
|
77
|
+
raise ValueError(f"No experiment named {self.experiment_name}.")
|
|
78
|
+
if loaded is None:
|
|
79
|
+
self.sub_title = f"{self.experiment_name} was removed."
|
|
80
|
+
self.shown = None
|
|
81
|
+
self.deliver(None)
|
|
82
|
+
return
|
|
83
|
+
if not self.found:
|
|
84
|
+
await self.add_views(loaded["views"], self.config.root)
|
|
85
|
+
self.found = True
|
|
86
|
+
self.sub_title = str(loaded["display_name"])
|
|
87
|
+
self.shown = Experiment(
|
|
88
|
+
self.config.root,
|
|
89
|
+
self.experiment_name,
|
|
90
|
+
native_rows(loaded),
|
|
91
|
+
MappingProxyType(loaded["config"] or {}),
|
|
92
|
+
)
|
|
93
|
+
self.deliver(self.shown)
|
|
94
|
+
|
|
95
|
+
async def watch_files(self) -> None:
|
|
96
|
+
async for row in watch_rows(self.tables, {"run_files"}, self.experiment_name):
|
|
97
|
+
if self.shown is not None:
|
|
98
|
+
self.deliver(self.shown, frozenset({str(row["path"])}))
|
|
99
|
+
|
|
100
|
+
def action_back(self) -> None:
|
|
101
|
+
if self.query_one(ContentSwitcher).current == "view-1":
|
|
102
|
+
self.app.pop_screen()
|
|
103
|
+
else:
|
|
104
|
+
self.action_show_view(1)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from collections.abc import Callable
|
|
2
|
-
|
|
3
1
|
from dirsql import DirSQL
|
|
4
2
|
from textual.app import ComposeResult
|
|
5
3
|
from textual.screen import Screen
|
|
@@ -12,7 +10,6 @@ from labnotes.tui.experiment_screen import ExperimentScreen
|
|
|
12
10
|
from labnotes.tui.fill_table import fill_table
|
|
13
11
|
from labnotes.tui.reload_on_change import reload_on_change
|
|
14
12
|
from labnotes.tui.runs_table import RunsTable
|
|
15
|
-
from labnotes.tui.runs_view import Request
|
|
16
13
|
from labnotes.tui.stopped_title import stopped_title
|
|
17
14
|
from labnotes.watch_rows import watch_rows
|
|
18
15
|
|
|
@@ -20,13 +17,10 @@ WATCHED = {"files", "runs", "experiment_configs"}
|
|
|
20
17
|
|
|
21
18
|
|
|
22
19
|
class ExperimentsScreen(Screen[None]):
|
|
23
|
-
def __init__(
|
|
24
|
-
self, tables: DirSQL, config: LabnotesConfig, views: Callable[[str], Request]
|
|
25
|
-
) -> None:
|
|
20
|
+
def __init__(self, tables: DirSQL, config: LabnotesConfig) -> None:
|
|
26
21
|
super().__init__()
|
|
27
22
|
self.tables = tables
|
|
28
23
|
self.config = config
|
|
29
|
-
self.views = views
|
|
30
24
|
|
|
31
25
|
def compose(self) -> ComposeResult:
|
|
32
26
|
yield Header()
|
|
@@ -63,7 +57,5 @@ class ExperimentsScreen(Screen[None]):
|
|
|
63
57
|
|
|
64
58
|
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
|
65
59
|
self.app.push_screen(
|
|
66
|
-
ExperimentScreen(
|
|
67
|
-
self.tables, self.config, str(event.row_key.value), self.views
|
|
68
|
-
)
|
|
60
|
+
ExperimentScreen(self.tables, self.config, str(event.row_key.value))
|
|
69
61
|
)
|
|
@@ -7,13 +7,15 @@ from labnotes.tui.dim import dim
|
|
|
7
7
|
from labnotes.tui.format_elapsed import format_elapsed
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def format_cell(key: str, value: object
|
|
10
|
+
def format_cell(key: str, value: object) -> object:
|
|
11
11
|
if key == "status" and value == "passed":
|
|
12
12
|
return Text("✓", style="green")
|
|
13
13
|
if key == "status" and value == "failed":
|
|
14
14
|
return Text("✕", style="red")
|
|
15
|
+
if key == "status" and value == "running":
|
|
16
|
+
return Text("◐", style="yellow")
|
|
15
17
|
if key == "status" and value == "unfinished":
|
|
16
|
-
return
|
|
18
|
+
return dim("–")
|
|
17
19
|
if key == "duration" and isinstance(value, int) and not isinstance(value, bool):
|
|
18
20
|
return format_elapsed(value)
|
|
19
21
|
if key == "started" and isinstance(value, str):
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from collections.abc import Callable, Iterable
|
|
2
|
+
from typing import cast
|
|
3
|
+
|
|
4
|
+
from textual.message import Message
|
|
5
|
+
from textual.widget import Widget
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def guard_view(view: type[Widget], failed: Callable[[Exception], None]) -> type[Widget]:
|
|
9
|
+
"""Subclass a view so errors in its compose and handlers reach `failed`.
|
|
10
|
+
|
|
11
|
+
Textual ends the app on any error a widget raises. A view is someone else's
|
|
12
|
+
code, so its errors stay in its pane instead.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def compose(self: Widget) -> Iterable[Widget]:
|
|
16
|
+
try:
|
|
17
|
+
yield from view.compose(self)
|
|
18
|
+
except Exception as error:
|
|
19
|
+
failed(error)
|
|
20
|
+
|
|
21
|
+
# _dispatch_message is private to Textual, but every handler runs under it.
|
|
22
|
+
async def _dispatch_message(self: Widget, message: Message) -> None:
|
|
23
|
+
try:
|
|
24
|
+
await view._dispatch_message(self, message)
|
|
25
|
+
except Exception as error:
|
|
26
|
+
failed(error)
|
|
27
|
+
|
|
28
|
+
namespace = {
|
|
29
|
+
"__module__": view.__module__,
|
|
30
|
+
"__qualname__": view.__qualname__,
|
|
31
|
+
"compose": compose,
|
|
32
|
+
"_dispatch_message": _dispatch_message,
|
|
33
|
+
}
|
|
34
|
+
# type() builds a subclass of view, so it is a Widget subclass.
|
|
35
|
+
return cast(type[Widget], type(view.__name__, (view,), namespace))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import sys
|
|
3
|
+
from importlib.machinery import SourceFileLoader
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from types import ModuleType
|
|
6
|
+
|
|
7
|
+
from textual.widget import Widget
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_view(spec: str, root: Path) -> type[Widget]:
|
|
11
|
+
"""Import the widget class `spec` names: `file.py:Class` from `root`, or `module:Class`."""
|
|
12
|
+
source, _, name = spec.rpartition(":")
|
|
13
|
+
if source.endswith(".py"):
|
|
14
|
+
path = root / source
|
|
15
|
+
module = ModuleType(f"labnotes_view_{path.stem}")
|
|
16
|
+
module.__file__ = str(path)
|
|
17
|
+
# Dataclasses and pickling look a class's module up by name.
|
|
18
|
+
sys.modules[module.__name__] = module
|
|
19
|
+
SourceFileLoader(module.__name__, str(path)).exec_module(module)
|
|
20
|
+
else:
|
|
21
|
+
module = importlib.import_module(source)
|
|
22
|
+
view = getattr(module, name, None)
|
|
23
|
+
if view is None:
|
|
24
|
+
raise ImportError(f"{source} has no {name}")
|
|
25
|
+
if not isinstance(view, type) or not issubclass(view, Widget):
|
|
26
|
+
raise TypeError(f"{spec} is not a Textual widget")
|
|
27
|
+
return view
|
|
@@ -11,7 +11,7 @@ def native_row(run: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
11
11
|
if isinstance(row.get(key), str):
|
|
12
12
|
row[key] = json.loads(row[key])
|
|
13
13
|
if run["labnotes_unfinished"]:
|
|
14
|
-
row["status"] = "unfinished"
|
|
14
|
+
row["status"] = "running" if run["labnotes_running"] else "unfinished"
|
|
15
15
|
elif run["labnotes_completed"]:
|
|
16
16
|
row["status"] = "passed"
|
|
17
17
|
else:
|
|
@@ -20,5 +20,5 @@ def native_row(run: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
20
20
|
return row
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def native_rows(experiment: Mapping[str, Any]) ->
|
|
24
|
-
return
|
|
23
|
+
def native_rows(experiment: Mapping[str, Any]) -> tuple[dict[str, Any], ...]:
|
|
24
|
+
return tuple(native_row(run) for run in experiment["runs_list"])
|