labnotes 0.2.5__tar.gz → 0.2.7__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.5 → labnotes-0.2.7}/PKG-INFO +6 -2
- {labnotes-0.2.5 → labnotes-0.2.7}/README.md +4 -1
- {labnotes-0.2.5 → labnotes-0.2.7}/pyproject.toml +1 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/cli.py +2 -0
- labnotes-0.2.7/src/labnotes/command_line_group.py +20 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/context.md +4 -1
- labnotes-0.2.7/src/labnotes/copy_experiment.py +12 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/create_experiment.py +26 -3
- labnotes-0.2.7/src/labnotes/experiment_definition.py +22 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/experiments.py +47 -2
- labnotes-0.2.7/src/labnotes/parse_param_value.py +8 -0
- labnotes-0.2.7/src/labnotes/parse_params.py +11 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/execute_run.py +6 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/first_invocation.py +2 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/read_experiment_config.py +4 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/run_experiment.py +2 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/start_experiment.py +2 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/uv.lock +11 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/.gitignore +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/CHANGELOG.md +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/MIGRATIONS.md +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/__init__.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/__main__.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/create_app.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/create_note.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/data_html.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/format_started.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/format_value.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/get_state.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/grid_cell.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/init.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/is_non_negative_number.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/latest_log.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/load_config.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/load_run.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/log.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/log_path.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/memory_interval.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/models.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/modes.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/note_path.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/output_html.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/parse_subset.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/pivot_runs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/read_config.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/read_path.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/retries.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/run_output.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/cgroup_memory.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/check_args.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/check_hook_placeholders.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/check_subset.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/claim_run.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/command_flags.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/container_cgroup.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/container_memory.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/continue_run.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/docker_output.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/fill_dirs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/fill_experiment_env.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/finalize_lost_runs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/flag_name.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/group_args.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/in_subset.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/invocation_hook.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/invocation_meta.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/override_flags.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/placeholders.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/project_snapshot.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/record_memory.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/run_git.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/run_locked.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/untracked_diff.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runs_view_command.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/save_note.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/server.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/set_state.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/state_path.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/list_resumable_runs.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_memory.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_run_file.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/runs_columns.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/check_view_response.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/format_cell.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/group_cells.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/invocation_columns.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/invocation_rows.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/native_rows.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/run_screen.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/runs_view.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/view_column.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/view_process.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/process_alive.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/tree_usage.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/view/__init__.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/view/answer.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/view/build.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/view/respond.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/view/serve.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/testing-conventions.toml +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/tests/conftest.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/tests/e2e/conftest.py +0 -0
- {labnotes-0.2.5 → labnotes-0.2.7}/tests/integration/conftest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: labnotes
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
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>
|
|
@@ -20,6 +20,7 @@ Requires-Dist: psutil>=7.2.2
|
|
|
20
20
|
Requires-Dist: rich>=14
|
|
21
21
|
Requires-Dist: starlette>=1.8.0
|
|
22
22
|
Requires-Dist: textual>=8.2.8
|
|
23
|
+
Requires-Dist: tomlkit>=0.15.2
|
|
23
24
|
Requires-Dist: uvicorn>=0.54.0
|
|
24
25
|
Description-Content-Type: text/markdown
|
|
25
26
|
|
|
@@ -83,7 +84,10 @@ one. Piped, they print a table.
|
|
|
83
84
|
|
|
84
85
|
`labnotes experiments new [NAME]` creates an experiment folder with a starter
|
|
85
86
|
`experiment.toml`, picking a random name if you give none. `new` itself can't
|
|
86
|
-
be an experiment name.
|
|
87
|
+
be an experiment name. `--command`, `--param NAME=VALUE` (repeat it for several
|
|
88
|
+
values) and `--repeats` write those settings into the file. `--from OTHER`
|
|
89
|
+
copies another experiment's `experiment.toml` and files, never its runs, and
|
|
90
|
+
records `from = "OTHER"`; the other flags replace the copied values.
|
|
87
91
|
|
|
88
92
|
`labnotes experiments foo run` runs `foo` from its `experiment.toml`, in the
|
|
89
93
|
experiments folder:
|
|
@@ -58,7 +58,10 @@ one. Piped, they print a table.
|
|
|
58
58
|
|
|
59
59
|
`labnotes experiments new [NAME]` creates an experiment folder with a starter
|
|
60
60
|
`experiment.toml`, picking a random name if you give none. `new` itself can't
|
|
61
|
-
be an experiment name.
|
|
61
|
+
be an experiment name. `--command`, `--param NAME=VALUE` (repeat it for several
|
|
62
|
+
values) and `--repeats` write those settings into the file. `--from OTHER`
|
|
63
|
+
copies another experiment's `experiment.toml` and files, never its runs, and
|
|
64
|
+
records `from = "OTHER"`; the other flags replace the copied values.
|
|
62
65
|
|
|
63
66
|
`labnotes experiments foo run` runs `foo` from its `experiment.toml`, in the
|
|
64
67
|
experiments folder:
|
|
@@ -2,6 +2,7 @@ from pathlib import Path
|
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
4
|
|
|
5
|
+
from labnotes.command_line_group import CommandLineGroup
|
|
5
6
|
from labnotes.context_guide import context_guide
|
|
6
7
|
from labnotes.experiments import experiments
|
|
7
8
|
from labnotes.init import init
|
|
@@ -11,6 +12,7 @@ from labnotes.server import server
|
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@click.group(
|
|
15
|
+
cls=CommandLineGroup,
|
|
14
16
|
invoke_without_command=True,
|
|
15
17
|
help="Manage experiments in a folder. `labnotes context` prints a guide for"
|
|
16
18
|
" coding agents.",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import shlex
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
COMMAND_LINE = "labnotes.command_line"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CommandLineGroup(click.Group):
|
|
10
|
+
def make_context(
|
|
11
|
+
self,
|
|
12
|
+
info_name: str | None,
|
|
13
|
+
args: list[str],
|
|
14
|
+
parent: click.Context | None = None,
|
|
15
|
+
**extra: Any,
|
|
16
|
+
) -> click.Context:
|
|
17
|
+
command_line = shlex.join(["labnotes", *args])
|
|
18
|
+
ctx = super().make_context(info_name, args, parent, **extra)
|
|
19
|
+
ctx.meta[COMMAND_LINE] = command_line
|
|
20
|
+
return ctx
|
|
@@ -43,7 +43,10 @@ keys that never come.
|
|
|
43
43
|
|
|
44
44
|
`labnotes experiments new [NAME]` creates an experiment folder with a starter
|
|
45
45
|
`experiment.toml`, picking a random name if you give none. `new` itself can't
|
|
46
|
-
be an experiment name.
|
|
46
|
+
be an experiment name. `--command`, `--param NAME=VALUE` (repeat it for several
|
|
47
|
+
values) and `--repeats` write those settings into the file. `--from OTHER`
|
|
48
|
+
copies another experiment's `experiment.toml` and files, never its runs, and
|
|
49
|
+
records `from = "OTHER"`; the other flags replace the copied values.
|
|
47
50
|
|
|
48
51
|
`labnotes experiments foo run` runs `foo` from its `experiment.toml`, in the
|
|
49
52
|
experiments folder:
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def copy_experiment(source: Path, target: Path, runs: Path) -> None:
|
|
6
|
+
def skip(folder: str, names: list[str]) -> set[str]:
|
|
7
|
+
here = Path(folder)
|
|
8
|
+
if here == runs:
|
|
9
|
+
return {name for name in names if (here / name / "run.json").exists()}
|
|
10
|
+
return {name for name in names if here / name == runs}
|
|
11
|
+
|
|
12
|
+
shutil.copytree(source, target, ignore=skip, symlinks=True)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
+
from labnotes.copy_experiment import copy_experiment
|
|
4
|
+
from labnotes.experiment_definition import experiment_definition
|
|
3
5
|
from labnotes.models import LabnotesConfig
|
|
4
6
|
from labnotes.runner.new_run_id import new_run_id
|
|
7
|
+
from labnotes.runner.runs_parent import runs_parent
|
|
5
8
|
|
|
6
9
|
STARTER = """\
|
|
7
10
|
# Runs from the project root. {run_dir} becomes the run's folder.
|
|
@@ -14,7 +17,14 @@ repeats = 1
|
|
|
14
17
|
"""
|
|
15
18
|
|
|
16
19
|
|
|
17
|
-
def create_experiment(
|
|
20
|
+
def create_experiment(
|
|
21
|
+
config: LabnotesConfig,
|
|
22
|
+
name: str | None,
|
|
23
|
+
source: str | None = None,
|
|
24
|
+
command: str | None = None,
|
|
25
|
+
repeats: int | None = None,
|
|
26
|
+
params: dict[str, list[object]] | None = None,
|
|
27
|
+
) -> Path:
|
|
18
28
|
def folder(candidate: str) -> Path:
|
|
19
29
|
return config.root / config.experiments.replace("{experiment}", candidate)
|
|
20
30
|
|
|
@@ -31,6 +41,19 @@ def create_experiment(config: LabnotesConfig, name: str | None) -> Path:
|
|
|
31
41
|
path = folder(name)
|
|
32
42
|
if path.exists():
|
|
33
43
|
raise ValueError(f"{name} already exists at {path}.")
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
text = STARTER
|
|
45
|
+
if source is not None:
|
|
46
|
+
origin = folder(source) / "experiment.toml"
|
|
47
|
+
if not origin.is_file():
|
|
48
|
+
raise ValueError(f"No experiment.toml for {source} at {origin}.")
|
|
49
|
+
text = origin.read_text(encoding="utf-8")
|
|
50
|
+
copy_experiment(
|
|
51
|
+
origin.parent, path, runs_parent(config.root, config.runs, source)
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
path.mkdir(parents=True)
|
|
55
|
+
(path / "experiment.toml").write_text(
|
|
56
|
+
experiment_definition(text, source, command, repeats, params or {}),
|
|
57
|
+
encoding="utf-8",
|
|
58
|
+
)
|
|
36
59
|
return path
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import tomlkit
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def experiment_definition(
|
|
5
|
+
text: str,
|
|
6
|
+
source: str | None,
|
|
7
|
+
command: str | None,
|
|
8
|
+
repeats: int | None,
|
|
9
|
+
params: dict[str, list[object]],
|
|
10
|
+
) -> str:
|
|
11
|
+
document = tomlkit.parse(text)
|
|
12
|
+
document.pop("from", None)
|
|
13
|
+
if command is not None:
|
|
14
|
+
document["command"] = command
|
|
15
|
+
if repeats is not None:
|
|
16
|
+
document["repeats"] = repeats
|
|
17
|
+
if params:
|
|
18
|
+
table = document.setdefault("params", tomlkit.table())
|
|
19
|
+
for name, values in params.items():
|
|
20
|
+
table[name] = values
|
|
21
|
+
head = "" if source is None else tomlkit.dumps({"from": source})
|
|
22
|
+
return head + tomlkit.dumps(document)
|
|
@@ -4,10 +4,12 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
+
from labnotes.command_line_group import COMMAND_LINE
|
|
7
8
|
from labnotes.create_experiment import create_experiment
|
|
8
9
|
from labnotes.runner.start_experiment import start_experiment
|
|
9
10
|
from labnotes.utils.format_bytes import format_bytes
|
|
10
11
|
from labnotes.load_config import load_config
|
|
12
|
+
from labnotes.parse_params import parse_params
|
|
11
13
|
from labnotes.parse_subset import parse_subset
|
|
12
14
|
from labnotes.print_rows import print_rows
|
|
13
15
|
from labnotes.read_config import CONFIG_FILE
|
|
@@ -32,7 +34,8 @@ class PassthroughCommand(click.Command):
|
|
|
32
34
|
@click.command(
|
|
33
35
|
cls=PassthroughCommand,
|
|
34
36
|
help="List experiments, show one, list its runs, or run it."
|
|
35
|
-
" `new [NAME]` creates one
|
|
37
|
+
" `new [NAME]` creates one, from --command, --param, --repeats and"
|
|
38
|
+
" --from OTHER if given. With run, --PARAM=VALUE runs only the"
|
|
36
39
|
" matrix cells where that param has that value (repeat it for several"
|
|
37
40
|
" values), and anything after --"
|
|
38
41
|
" is appended to every run's command and recorded in run.json, so never"
|
|
@@ -79,6 +82,31 @@ class PassthroughCommand(click.Command):
|
|
|
79
82
|
help="With run: shell command to run once after every run ends."
|
|
80
83
|
" Overrides experiment.toml and .labnotes.toml.",
|
|
81
84
|
)
|
|
85
|
+
@click.option(
|
|
86
|
+
"--command",
|
|
87
|
+
metavar="COMMAND",
|
|
88
|
+
help="With new: the experiment's command.",
|
|
89
|
+
)
|
|
90
|
+
@click.option(
|
|
91
|
+
"--param",
|
|
92
|
+
"params",
|
|
93
|
+
multiple=True,
|
|
94
|
+
metavar="NAME=VALUE",
|
|
95
|
+
help="With new: a value for a param, parsed as TOML or else taken as a"
|
|
96
|
+
" string. Repeat it for several values.",
|
|
97
|
+
)
|
|
98
|
+
@click.option(
|
|
99
|
+
"--repeats",
|
|
100
|
+
type=click.IntRange(min=1),
|
|
101
|
+
help="With new: how many times each combination runs.",
|
|
102
|
+
)
|
|
103
|
+
@click.option(
|
|
104
|
+
"--from",
|
|
105
|
+
"source",
|
|
106
|
+
metavar="OTHER",
|
|
107
|
+
help="With new: start from OTHER's experiment.toml and files, never its"
|
|
108
|
+
" runs. Other flags replace the copied values.",
|
|
109
|
+
)
|
|
82
110
|
@click.pass_context
|
|
83
111
|
def experiments(
|
|
84
112
|
ctx: click.Context,
|
|
@@ -90,6 +118,10 @@ def experiments(
|
|
|
90
118
|
timeout: int | None,
|
|
91
119
|
setup: str | None,
|
|
92
120
|
teardown: str | None,
|
|
121
|
+
command: str | None,
|
|
122
|
+
params: tuple[str, ...],
|
|
123
|
+
repeats: int | None,
|
|
124
|
+
source: str | None,
|
|
93
125
|
) -> None:
|
|
94
126
|
passthrough: list[str] | None = ctx.meta.get(PASSTHROUGH)
|
|
95
127
|
config = load_config(ctx)
|
|
@@ -97,10 +129,22 @@ def experiments(
|
|
|
97
129
|
raise click.ClickException(
|
|
98
130
|
f"No {CONFIG_FILE} here. Run labnotes init, or pass -c PATH."
|
|
99
131
|
)
|
|
132
|
+
if name != "new":
|
|
133
|
+
given = {
|
|
134
|
+
"--command": command,
|
|
135
|
+
"--param": params or None,
|
|
136
|
+
"--repeats": repeats,
|
|
137
|
+
"--from": source,
|
|
138
|
+
}
|
|
139
|
+
for flag, value in given.items():
|
|
140
|
+
if value is not None:
|
|
141
|
+
raise click.UsageError(f"{flag} needs new.")
|
|
100
142
|
match name, action:
|
|
101
143
|
case "new", _:
|
|
102
144
|
try:
|
|
103
|
-
path = create_experiment(
|
|
145
|
+
path = create_experiment(
|
|
146
|
+
config, action, source, command, repeats, parse_params(list(params))
|
|
147
|
+
)
|
|
104
148
|
except ValueError as error:
|
|
105
149
|
raise click.ClickException(str(error)) from error
|
|
106
150
|
click.echo(f"Created {path / 'experiment.toml'}")
|
|
@@ -120,6 +164,7 @@ def experiments(
|
|
|
120
164
|
passthrough or None,
|
|
121
165
|
setup,
|
|
122
166
|
teardown,
|
|
167
|
+
ctx.meta.get(COMMAND_LINE),
|
|
123
168
|
)
|
|
124
169
|
try:
|
|
125
170
|
if sys.stdout.isatty() and sys.stderr.isatty():
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from labnotes.parse_param_value import parse_param_value
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def parse_params(pairs: list[str]) -> dict[str, list[object]]:
|
|
5
|
+
params: dict[str, list[object]] = {}
|
|
6
|
+
for pair in pairs:
|
|
7
|
+
name, equals, value = pair.partition("=")
|
|
8
|
+
if not equals or not name:
|
|
9
|
+
raise ValueError(f"--param {pair} must look like NAME=VALUE.")
|
|
10
|
+
params.setdefault(name, []).append(parse_param_value(value))
|
|
11
|
+
return params
|
|
@@ -5,6 +5,7 @@ import os
|
|
|
5
5
|
import time
|
|
6
6
|
from collections.abc import Callable, Sequence
|
|
7
7
|
from datetime import UTC, datetime
|
|
8
|
+
from importlib.metadata import version
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
import psutil
|
|
@@ -43,6 +44,7 @@ async def execute_run(
|
|
|
43
44
|
environment: dict[str, str] | None = None,
|
|
44
45
|
git_info: dict[str, str | bool] | None = None,
|
|
45
46
|
continues: bool = False,
|
|
47
|
+
command_line: str | None = None,
|
|
46
48
|
) -> dict[str, object]:
|
|
47
49
|
resolved = shell_command(command, folder, params, args)
|
|
48
50
|
git = git_info if git_info is not None else await asyncio.to_thread(run_git, root)
|
|
@@ -56,6 +58,8 @@ async def execute_run(
|
|
|
56
58
|
"ended_by": None,
|
|
57
59
|
"signal": None,
|
|
58
60
|
"git": git,
|
|
61
|
+
"labnotes_version": version("labnotes"),
|
|
62
|
+
"labnotes_command": command_line,
|
|
59
63
|
}
|
|
60
64
|
if args:
|
|
61
65
|
run["args"] = list(args)
|
|
@@ -76,6 +80,8 @@ async def execute_run(
|
|
|
76
80
|
"error": None,
|
|
77
81
|
"git": None if git is None else git["commit"],
|
|
78
82
|
"args": list(args),
|
|
83
|
+
"labnotes_version": run["labnotes_version"],
|
|
84
|
+
"labnotes_command": command_line,
|
|
79
85
|
**logs,
|
|
80
86
|
}
|
|
81
87
|
record = folder / "run.json"
|
|
@@ -8,6 +8,8 @@ def first_invocation(run: dict[str, object]) -> dict[str, object]:
|
|
|
8
8
|
"error": run.get("error"),
|
|
9
9
|
"git": git.get("commit") if isinstance(git, dict) else None,
|
|
10
10
|
"args": run.get("args", []),
|
|
11
|
+
"labnotes_version": run.get("labnotes_version"),
|
|
12
|
+
"labnotes_command": run.get("labnotes_command"),
|
|
11
13
|
"stdout": "stdout.log",
|
|
12
14
|
"stderr": "stderr.log",
|
|
13
15
|
}
|
|
@@ -28,6 +28,7 @@ SETTINGS = {
|
|
|
28
28
|
"teardown",
|
|
29
29
|
"env",
|
|
30
30
|
"continues",
|
|
31
|
+
"from",
|
|
31
32
|
}
|
|
32
33
|
HOOKS = ("setup", "teardown")
|
|
33
34
|
VIEWS = {"runs"}
|
|
@@ -86,6 +87,9 @@ def read_experiment_config(
|
|
|
86
87
|
memory_interval = values.get("memory_interval", DEFAULT_MEMORY_INTERVAL)
|
|
87
88
|
if not is_non_negative_number(memory_interval):
|
|
88
89
|
raise ValueError(f"{path}: memory_interval must be 0 or more seconds")
|
|
90
|
+
origin = values.get("from")
|
|
91
|
+
if origin is not None and (not isinstance(origin, str) or not origin.strip()):
|
|
92
|
+
raise ValueError(f"{path}: from must be an experiment name")
|
|
89
93
|
for hook in HOOKS:
|
|
90
94
|
run = values.get(hook)
|
|
91
95
|
if run is not None and (not isinstance(run, str) or not run.strip()):
|
|
@@ -53,6 +53,7 @@ async def run_experiment(
|
|
|
53
53
|
args: list[str] | None = None,
|
|
54
54
|
environment: dict[str, str] | None = None,
|
|
55
55
|
snapshot: ProjectSnapshot | None = None,
|
|
56
|
+
command_line: str | None = None,
|
|
56
57
|
) -> list[dict[str, object]]:
|
|
57
58
|
command_root = snapshot.root if snapshot is not None else config.root
|
|
58
59
|
inherited = dict(os.environ) if environment is None else environment
|
|
@@ -323,6 +324,7 @@ async def run_experiment(
|
|
|
323
324
|
else None
|
|
324
325
|
),
|
|
325
326
|
continues=experiment.continues,
|
|
327
|
+
command_line=command_line,
|
|
326
328
|
)
|
|
327
329
|
del running[run], trees[run]
|
|
328
330
|
report({"event": "ended", "run": run, **result})
|
|
@@ -17,6 +17,7 @@ def start_experiment(
|
|
|
17
17
|
args: list[str] | None,
|
|
18
18
|
setup: str | None,
|
|
19
19
|
teardown: str | None,
|
|
20
|
+
command_line: str | None = None,
|
|
20
21
|
) -> Start:
|
|
21
22
|
async def start(report: Report, listen: Listen) -> list[dict[str, object]]:
|
|
22
23
|
experiment, limit = await prepare_experiment(
|
|
@@ -35,6 +36,7 @@ def start_experiment(
|
|
|
35
36
|
only=only,
|
|
36
37
|
args=args,
|
|
37
38
|
snapshot=snapshot,
|
|
39
|
+
command_line=command_line,
|
|
38
40
|
)
|
|
39
41
|
|
|
40
42
|
return start
|
|
@@ -568,6 +568,7 @@ dependencies = [
|
|
|
568
568
|
{ name = "rich" },
|
|
569
569
|
{ name = "starlette" },
|
|
570
570
|
{ name = "textual" },
|
|
571
|
+
{ name = "tomlkit" },
|
|
571
572
|
{ name = "uvicorn" },
|
|
572
573
|
]
|
|
573
574
|
|
|
@@ -597,6 +598,7 @@ requires-dist = [
|
|
|
597
598
|
{ name = "rich", specifier = ">=14" },
|
|
598
599
|
{ name = "starlette", specifier = ">=1.8.0" },
|
|
599
600
|
{ name = "textual", specifier = ">=8.2.8" },
|
|
601
|
+
{ name = "tomlkit", specifier = ">=0.15.2" },
|
|
600
602
|
{ name = "uvicorn", specifier = ">=0.54.0" },
|
|
601
603
|
]
|
|
602
604
|
|
|
@@ -1307,6 +1309,15 @@ wheels = [
|
|
|
1307
1309
|
{ url = "https://files.pythonhosted.org/packages/fb/be/35261223d9416a0751cdff1c7b4a6f881387218a12d439fe22fefebc8c04/textual-8.2.8-py3-none-any.whl", hash = "sha256:267375fd402dc8d981457212efa71f0e3365fd17bba144ba9bb3ed7563cb374a", size = 731418, upload-time = "2026-06-30T06:51:26.364Z" },
|
|
1308
1310
|
]
|
|
1309
1311
|
|
|
1312
|
+
[[package]]
|
|
1313
|
+
name = "tomlkit"
|
|
1314
|
+
version = "0.15.2"
|
|
1315
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1316
|
+
sdist = { url = "https://files.pythonhosted.org/packages/08/72/fc01563df95c189a9f65850096bdc205bc2fe37da6c740cd64afaf18d07e/tomlkit-0.15.2.tar.gz", hash = "sha256:83e32755ba1d20d9340bd069ee97e9f6232ba2346b63ffcba425de01f7e327a6", size = 190522, upload-time = "2026-10-16T04:07:08.166Z" }
|
|
1317
|
+
wheels = [
|
|
1318
|
+
{ url = "https://files.pythonhosted.org/packages/da/46/7d84138a60a8bd796825b2d38ebe92d0d16501ca57d501b7c5d677401bbc/tomlkit-0.15.2-py3-none-any.whl", hash = "sha256:05562412163a3037b94497385fb7f9204c637bc605af7a48199e41cf2272f437", size = 52225, upload-time = "2026-10-16T04:07:09.216Z" },
|
|
1319
|
+
]
|
|
1320
|
+
|
|
1310
1321
|
[[package]]
|
|
1311
1322
|
name = "tornado"
|
|
1312
1323
|
version = "6.5.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|