labnotes 0.0.116__tar.gz → 0.0.118__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {labnotes-0.0.116 → labnotes-0.0.118}/PKG-INFO +1 -1
- {labnotes-0.0.116 → labnotes-0.0.118}/pyproject.toml +6 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/context.md +1 -1
- labnotes-0.0.118/src/labnotes/grid_cell.py +21 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/load_experiment.py +15 -0
- labnotes-0.0.118/src/labnotes/pivot_runs.py +43 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/static/labnotes.css +22 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/templates/experiment.html +40 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/testing-conventions.toml +1 -1
- {labnotes-0.0.116 → labnotes-0.0.118}/.gitignore +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/CHANGELOG.md +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/MIGRATIONS.md +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/README.md +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/__main__.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/create_note.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/__init__.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/control_run.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/daemon_app.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/daemon_client.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/daemon_running.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/daemon_server.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/describe_status.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/ensure_daemon.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/exit_when_idle.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/finalize_lost_runs.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/last_stderr.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/remote_start.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/run_daemon.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/run_logs.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/serve_daemon.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/socket_path.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/stop_runs.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/watch_daemon.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon/watch_status.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/daemon_command.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/format_started.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/init.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/is_non_negative_number.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/load_run.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/log.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/log_path.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/memory_interval.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/models.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/modes.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/note_path.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/retries.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/run_command.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/run_control.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/run_output.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/run_status.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/invocation_meta.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/record_memory.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/run_experiment.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/runs_view_command.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/save_note.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/server.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/read_memory.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/read_run_file.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/runs_columns.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/check_view_response.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/format_cell.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/group_cells.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/invocation_columns.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/invocation_rows.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/native_rows.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/run_screen.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/runs_view.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/view_column.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/tui/view_process.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/utils/process_alive.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/utils/tree_usage.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/view/__init__.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/view/answer.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/view/build.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/view/respond.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/view/serve.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/tests/integration/conftest.py +0 -0
- {labnotes-0.0.116 → labnotes-0.0.118}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: labnotes
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.118
|
|
4
4
|
Summary: Browse experiment designs, notes, and run data from a folder.
|
|
5
5
|
Project-URL: Repository, https://github.com/thekevinscott/labnotes
|
|
6
6
|
Author-email: Kevin Scott <me@thekevinscott.com>
|
|
@@ -44,6 +44,12 @@ dev = [
|
|
|
44
44
|
"types-psutil>=7.2.2.20260906",
|
|
45
45
|
]
|
|
46
46
|
|
|
47
|
+
# thekevinscott's own packages skip the user-level uv release cooldown.
|
|
48
|
+
[tool.uv.exclude-newer-package]
|
|
49
|
+
curtaincall = false
|
|
50
|
+
dirsql = false
|
|
51
|
+
posix-tools = false
|
|
52
|
+
|
|
47
53
|
[tool.hatch.version]
|
|
48
54
|
source = "vcs"
|
|
49
55
|
|
|
@@ -180,4 +180,4 @@ going. Elsewhere, it prints a line per event instead.
|
|
|
180
180
|
was retried counts as retried, not failed. If it can't
|
|
181
181
|
start or reach the daemon, it prints the cause on one line and exits 1.
|
|
182
182
|
|
|
183
|
-
`uvx labnotes server` serves the experiments, their markdown files and their runs as a local site, read from the same `.labnotes.toml`. An experiment's page shows its `experiment.toml` on one line, then a Runs tab with a table of its runs and a Notebooks tab with one tab per markdown file. Clicking a run opens its `stdout.log` and `stderr.log` together, followed as they're written until the run ends.
|
|
183
|
+
`uvx labnotes server` serves the experiments, their markdown files and their runs as a local site, read from the same `.labnotes.toml`. An experiment's page shows its `experiment.toml` on one line, then a Runs tab with a table of its runs and a Notebooks tab with one tab per markdown file. For a matrix experiment, the Runs tab also has a grid (`?view=grid`): one cell per combination of the params that vary, with how many runs count toward `repeats`. Clicking a run opens its `stdout.log` and `stderr.log` together, followed as they're written until the run ends.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import TypedDict
|
|
2
|
+
from urllib.parse import urlencode
|
|
3
|
+
|
|
4
|
+
from labnotes.format_value import format_value
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Cell(TypedDict):
|
|
8
|
+
values: dict[str, object]
|
|
9
|
+
runs: object
|
|
10
|
+
completed: object
|
|
11
|
+
href: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def grid_cell(values: dict[str, object], counts: dict[str, object]) -> Cell:
|
|
15
|
+
filters = [(f"f.{key}", format_value(value)) for key, value in values.items()]
|
|
16
|
+
return {
|
|
17
|
+
"values": values,
|
|
18
|
+
"runs": counts.get("runs", 0),
|
|
19
|
+
"completed": counts.get("completed", 0),
|
|
20
|
+
"href": f"?{urlencode(filters)}#runs",
|
|
21
|
+
}
|
|
@@ -17,6 +17,7 @@ from labnotes.list_runs import list_runs
|
|
|
17
17
|
from labnotes.models import QueryParams
|
|
18
18
|
from labnotes.nests_runs import nests_runs
|
|
19
19
|
from labnotes.parse_run_view import parse_run_view
|
|
20
|
+
from labnotes.pivot_runs import pivot_runs
|
|
20
21
|
from labnotes.python_blocks import python_blocks
|
|
21
22
|
from labnotes.render_markdown import render_markdown
|
|
22
23
|
from labnotes.runs_view_command import runs_view_command
|
|
@@ -65,6 +66,19 @@ async def load_experiment(
|
|
|
65
66
|
if nested
|
|
66
67
|
else None
|
|
67
68
|
)
|
|
69
|
+
matrix = summary["mode"] == "matrix"
|
|
70
|
+
gridded = matrix and params.getlist("view")[-1:] == ["grid"]
|
|
71
|
+
axes = {key: varies[key] for key in choices if len(varies[key]) > 1}
|
|
72
|
+
grid = (
|
|
73
|
+
pivot_runs(
|
|
74
|
+
axes,
|
|
75
|
+
await count_runs(
|
|
76
|
+
tables, source, str(summary["completed_run"]), group_by=list(axes)
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
if gridded
|
|
80
|
+
else None
|
|
81
|
+
)
|
|
68
82
|
return {
|
|
69
83
|
"name": row["name"],
|
|
70
84
|
"display_name": row["display_name"],
|
|
@@ -86,6 +100,7 @@ async def load_experiment(
|
|
|
86
100
|
"runs_list": runs,
|
|
87
101
|
"runs_live": await daemon_running(root),
|
|
88
102
|
"run_groups": groups,
|
|
103
|
+
"run_grid": grid,
|
|
89
104
|
"run_columns": shown,
|
|
90
105
|
"run_view": view,
|
|
91
106
|
"run_choices": choices,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from itertools import product
|
|
2
|
+
from typing import TypedDict
|
|
3
|
+
|
|
4
|
+
from labnotes.grid_cell import Cell, grid_cell
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Row(TypedDict):
|
|
8
|
+
labels: list[object]
|
|
9
|
+
cells: list[Cell]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Grid(TypedDict):
|
|
13
|
+
row_keys: list[str]
|
|
14
|
+
column_key: str | None
|
|
15
|
+
columns: list[object]
|
|
16
|
+
rows: list[Row]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _key(value: object) -> object:
|
|
20
|
+
return int(value) if isinstance(value, bool) else value
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def pivot_runs(axes: dict[str, list[object]], counts: list[dict[str, object]]) -> Grid:
|
|
24
|
+
by_cell = {tuple(_key(row[key]) for key in axes): row for row in counts}
|
|
25
|
+
row_keys = [*axes]
|
|
26
|
+
column_key = row_keys.pop() if row_keys else None
|
|
27
|
+
columns: list[object] = [None] if column_key is None else axes[column_key]
|
|
28
|
+
rows: list[Row] = []
|
|
29
|
+
for labels in product(*(axes[key] for key in row_keys)):
|
|
30
|
+
cells = []
|
|
31
|
+
for column in columns:
|
|
32
|
+
values: dict[str, object] = dict(zip(row_keys, labels))
|
|
33
|
+
if column_key is not None:
|
|
34
|
+
values[column_key] = column
|
|
35
|
+
found = by_cell.get(tuple(_key(values[key]) for key in axes), {})
|
|
36
|
+
cells.append(grid_cell(values, found))
|
|
37
|
+
rows.append({"labels": list(labels), "cells": cells})
|
|
38
|
+
return {
|
|
39
|
+
"row_keys": row_keys,
|
|
40
|
+
"column_key": column_key,
|
|
41
|
+
"columns": columns,
|
|
42
|
+
"rows": rows,
|
|
43
|
+
}
|
|
@@ -217,6 +217,22 @@ h3 {
|
|
|
217
217
|
.new-note { font: inherit; color: var(--ink-2); background: none; border: 0; padding: 0 0.2rem; cursor: pointer; }
|
|
218
218
|
.new-note:hover { color: var(--ink); }
|
|
219
219
|
|
|
220
|
+
/* Runs grid: counted runs per combination of the params that vary. */
|
|
221
|
+
.runs-grid { border-collapse: separate; border-spacing: 3px; font-size: 0.86rem; }
|
|
222
|
+
.runs-grid th { font-family: var(--mono); font-size: 0.8rem; font-weight: 500; color: var(--ink-2); text-align: left; white-space: nowrap; padding: 0 0.75rem 0.3rem; }
|
|
223
|
+
.runs-grid thead th { vertical-align: bottom; }
|
|
224
|
+
.runs-grid th.label { color: var(--ink-3); }
|
|
225
|
+
.runs-grid th.axis { font-family: var(--sans); font-size: 0.68rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); border-bottom: 1px solid var(--rule); }
|
|
226
|
+
.grid-cell { min-width: 6rem; padding: 0; background: var(--surface); border: 1px solid var(--rule); border-radius: 7px; }
|
|
227
|
+
.grid-cell a { display: block; padding: 0.5rem 0.7rem 0.6rem; color: var(--ink); text-decoration: none; }
|
|
228
|
+
.grid-cell:hover { border-color: var(--rule-strong); }
|
|
229
|
+
.grid-cell .num { display: block; margin-bottom: 0.4rem; }
|
|
230
|
+
.grid-cell .of { color: var(--ink-3); }
|
|
231
|
+
.grid-cell.empty .num { color: var(--ink-3); }
|
|
232
|
+
.grid-cell.done { border-color: color-mix(in srgb, var(--accent) 35%, var(--rule)); }
|
|
233
|
+
.grid-cell .meter { display: block; height: 4px; border-radius: 2px; background: var(--track); overflow: hidden; }
|
|
234
|
+
.grid-cell .meter-fill { display: block; height: 100%; background: var(--accent); }
|
|
235
|
+
|
|
220
236
|
/* Runs table */
|
|
221
237
|
.narrowed { color: var(--ink-2); font-size: 0.88rem; margin: 0 0 0.75rem; }
|
|
222
238
|
.narrowed .clear { margin-left: 0.8rem; color: var(--ink-3); }
|
|
@@ -390,6 +406,12 @@ h3 {
|
|
|
390
406
|
}
|
|
391
407
|
.view-toggle button + button { border-left: 1px solid var(--rule-strong); }
|
|
392
408
|
.view-toggle button[aria-pressed="true"] { background: var(--accent-soft); color: var(--ink); }
|
|
409
|
+
#runs .view-toggle { display: flex; width: fit-content; float: none; margin: 0 0 0.75rem auto; }
|
|
410
|
+
.view-toggle a { display: flex; padding: 0.3rem 0.55rem; background: var(--surface); color: var(--ink-3); }
|
|
411
|
+
.view-toggle a + a { border-left: 1px solid var(--rule-strong); }
|
|
412
|
+
.view-toggle a:hover { color: var(--ink); }
|
|
413
|
+
.view-toggle a[aria-current="page"] { background: var(--accent-soft); color: var(--ink); }
|
|
414
|
+
.view-toggle svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }
|
|
393
415
|
|
|
394
416
|
/* Index as cards: the same table, laid out as a grid. */
|
|
395
417
|
.ledger[data-view="cards"] { display: block; background: none; border: 0; border-radius: 0; overflow: visible; }
|
|
@@ -51,6 +51,45 @@
|
|
|
51
51
|
{% set nested = e.run_groups is not none %}
|
|
52
52
|
{% set width = e.run_columns | length + (2 if nested else 1) %}
|
|
53
53
|
{% set narrowed = view.status or view.filters or view.picked %}
|
|
54
|
+
{% set grid = e.run_grid %}
|
|
55
|
+
{% if e.mode == 'matrix' and e.runs %}
|
|
56
|
+
<nav class="view-toggle" aria-label="Runs view">
|
|
57
|
+
<a href="?#runs" title="Table" aria-label="Table"{% if not grid %} aria-current="page"{% endif %}><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2 3.5h12M2 8h12M2 12.5h12"/></svg></a>
|
|
58
|
+
<a href="?view=grid#runs" title="Grid" aria-label="Grid"{% if grid %} aria-current="page"{% endif %}><svg viewBox="0 0 16 16" aria-hidden="true"><rect x="2" y="2" width="5" height="5" rx="1"/><rect x="9" y="2" width="5" height="5" rx="1"/><rect x="2" y="9" width="5" height="5" rx="1"/><rect x="9" y="9" width="5" height="5" rx="1"/></svg></a>
|
|
59
|
+
</nav>
|
|
60
|
+
{% endif %}
|
|
61
|
+
{% if grid %}
|
|
62
|
+
<div class="scroll">
|
|
63
|
+
<table class="runs-grid">
|
|
64
|
+
<thead>
|
|
65
|
+
{% if grid.column_key is not none %}
|
|
66
|
+
<tr class="axis-row">
|
|
67
|
+
{% if grid.row_keys %}<td colspan="{{ grid.row_keys | length }}"></td>{% endif %}
|
|
68
|
+
<th scope="colgroup" colspan="{{ grid.columns | length }}" class="axis">{{ grid.column_key }}</th>
|
|
69
|
+
</tr>
|
|
70
|
+
<tr>
|
|
71
|
+
{% for key in grid.row_keys %}<th scope="col" class="label">{{ key }}</th>{% endfor %}
|
|
72
|
+
{% for column in grid.columns %}<th scope="col">{{ column | value }}</th>{% endfor %}
|
|
73
|
+
</tr>
|
|
74
|
+
{% endif %}
|
|
75
|
+
</thead>
|
|
76
|
+
<tbody>
|
|
77
|
+
{% for row in grid.rows %}
|
|
78
|
+
<tr>
|
|
79
|
+
{% for label in row.labels %}<th scope="row">{{ label | value }}</th>{% endfor %}
|
|
80
|
+
{% for cell in row.cells %}
|
|
81
|
+
{% set goal = e.runs_per_cell or 1 %}
|
|
82
|
+
<td class="grid-cell{{ ' done' if cell.completed >= goal }}{{ ' empty' if not cell.runs }}" title="{% for key, value in cell['values'].items() %}{{ key }} = {{ value | value }}{{ ', ' if not loop.last }}{% endfor %}{{ ': ' if cell['values'] }}{{ cell.completed }} complete of {{ cell.runs }} recorded">
|
|
83
|
+
<a href="{{ cell.href }}"><span class="num">{{ cell.completed }}<span class="of">/{{ goal }}</span></span>
|
|
84
|
+
<span class="meter"><span class="meter-fill" style="width: {{ [cell.completed / goal * 100, 100] | min }}%"></span></span></a>
|
|
85
|
+
</td>
|
|
86
|
+
{% endfor %}
|
|
87
|
+
</tr>
|
|
88
|
+
{% endfor %}
|
|
89
|
+
</tbody>
|
|
90
|
+
</table>
|
|
91
|
+
</div>
|
|
92
|
+
{% else %}
|
|
54
93
|
{% if narrowed %}<p class="narrowed"><span class="num">{{ e.runs_list | length }}</span> of <span class="num">{{ e.runs }}</span> <a class="clear" href="{{ '?sort=' ~ (view.sort | urlencode) ~ ('&order=desc' if view.descending else '') if view.sort else '?' }}#runs">Clear filters</a></p>{% endif %}
|
|
55
94
|
{% if e.runs %}
|
|
56
95
|
<form id="run-filters" method="get" action="#runs">
|
|
@@ -117,6 +156,7 @@
|
|
|
117
156
|
{% else %}
|
|
118
157
|
<p class="empty">No runs recorded yet.</p>
|
|
119
158
|
{% endif %}
|
|
159
|
+
{% endif %}
|
|
120
160
|
</section>
|
|
121
161
|
{% endif %}
|
|
122
162
|
<script src="{{ asset('experiment.js') }}" defer></script>
|
|
@@ -13,7 +13,7 @@ reason = "last_stream is always the same str object taken from STREAMS, so is eq
|
|
|
13
13
|
[[python.exempt]]
|
|
14
14
|
path = "src/labnotes/load_experiment.py"
|
|
15
15
|
rules = ["mutation"]
|
|
16
|
-
lines = [61]
|
|
16
|
+
lines = [61, 69]
|
|
17
17
|
reason = "summarize_config returns mode matrix or adhoc; \"matrix\" <= \"adhoc\" is false like ==, and both are interned literals so is equals =="
|
|
18
18
|
|
|
19
19
|
[[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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|