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.
Files changed (216) hide show
  1. {labnotes-0.2.5 → labnotes-0.2.7}/PKG-INFO +6 -2
  2. {labnotes-0.2.5 → labnotes-0.2.7}/README.md +4 -1
  3. {labnotes-0.2.5 → labnotes-0.2.7}/pyproject.toml +1 -0
  4. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/cli.py +2 -0
  5. labnotes-0.2.7/src/labnotes/command_line_group.py +20 -0
  6. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/context.md +4 -1
  7. labnotes-0.2.7/src/labnotes/copy_experiment.py +12 -0
  8. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/create_experiment.py +26 -3
  9. labnotes-0.2.7/src/labnotes/experiment_definition.py +22 -0
  10. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/experiments.py +47 -2
  11. labnotes-0.2.7/src/labnotes/parse_param_value.py +8 -0
  12. labnotes-0.2.7/src/labnotes/parse_params.py +11 -0
  13. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/execute_run.py +6 -0
  14. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/first_invocation.py +2 -0
  15. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/read_experiment_config.py +4 -0
  16. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/run_experiment.py +2 -0
  17. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/start_experiment.py +2 -0
  18. {labnotes-0.2.5 → labnotes-0.2.7}/uv.lock +11 -0
  19. {labnotes-0.2.5 → labnotes-0.2.7}/.gitignore +0 -0
  20. {labnotes-0.2.5 → labnotes-0.2.7}/CHANGELOG.md +0 -0
  21. {labnotes-0.2.5 → labnotes-0.2.7}/MIGRATIONS.md +0 -0
  22. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/__init__.py +0 -0
  23. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/__main__.py +0 -0
  24. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/asset_url.py +0 -0
  25. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/check_counts.py +0 -0
  26. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/compose_experiment.py +0 -0
  27. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/context_guide.py +0 -0
  28. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/count_groups.py +0 -0
  29. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/count_runs.py +0 -0
  30. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/create_app.py +0 -0
  31. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/create_note.py +0 -0
  32. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/data_html.py +0 -0
  33. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/default_config_text.py +0 -0
  34. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/default_counts.py +0 -0
  35. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/drain_watch.py +0 -0
  36. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/experiment_notes.py +0 -0
  37. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/experiment_runs.py +0 -0
  38. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/file_changes.py +0 -0
  39. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/format_duration.py +0 -0
  40. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/format_started.py +0 -0
  41. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/format_value.py +0 -0
  42. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/get_state.py +0 -0
  43. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/grid_cell.py +0 -0
  44. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/group_runs.py +0 -0
  45. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/init.py +0 -0
  46. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/invocation_started.py +0 -0
  47. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/is_non_negative_int.py +0 -0
  48. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/is_non_negative_number.py +0 -0
  49. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/is_positive_int.py +0 -0
  50. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/latest_log.py +0 -0
  51. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/list_experiments.py +0 -0
  52. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/list_runs.py +0 -0
  53. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/load_config.py +0 -0
  54. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/load_experiment.py +0 -0
  55. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/load_run.py +0 -0
  56. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/log.py +0 -0
  57. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/log_path.py +0 -0
  58. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/memory_interval.py +0 -0
  59. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/models.py +0 -0
  60. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/modes.py +0 -0
  61. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/nests_runs.py +0 -0
  62. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/note_path.py +0 -0
  63. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/notebook_kernel.py +0 -0
  64. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/output_html.py +0 -0
  65. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/parse_run_view.py +0 -0
  66. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/parse_subset.py +0 -0
  67. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/pivot_runs.py +0 -0
  68. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/print_rows.py +0 -0
  69. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/python_blocks.py +0 -0
  70. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/read_config.py +0 -0
  71. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/read_path.py +0 -0
  72. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/render_markdown.py +0 -0
  73. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/retries.py +0 -0
  74. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/run_output.py +0 -0
  75. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/__init__.py +0 -0
  76. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/cgroup_memory.py +0 -0
  77. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/check_args.py +0 -0
  78. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/check_hook_placeholders.py +0 -0
  79. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/check_subset.py +0 -0
  80. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/claim_run.py +0 -0
  81. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/command_flags.py +0 -0
  82. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/container_cgroup.py +0 -0
  83. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/container_memory.py +0 -0
  84. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/continue_run.py +0 -0
  85. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/describe_event.py +0 -0
  86. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/docker_output.py +0 -0
  87. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/exit_signal.py +0 -0
  88. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/fill_dirs.py +0 -0
  89. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/fill_experiment_env.py +0 -0
  90. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/finalize_lost_runs.py +0 -0
  91. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/flag_name.py +0 -0
  92. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/group_args.py +0 -0
  93. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/in_subset.py +0 -0
  94. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/invocation_hook.py +0 -0
  95. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/invocation_meta.py +0 -0
  96. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/new_param_run_id.py +0 -0
  97. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/new_run_id.py +0 -0
  98. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  99. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/override_flags.py +0 -0
  100. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/param_args.py +0 -0
  101. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/param_grid.py +0 -0
  102. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/param_run_name.py +0 -0
  103. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/placeholders.py +0 -0
  104. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/plan_runs.py +0 -0
  105. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/prepare_experiment.py +0 -0
  106. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/project_snapshot.py +0 -0
  107. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/record_memory.py +0 -0
  108. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/run_folder.py +0 -0
  109. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/run_git.py +0 -0
  110. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/run_headless.py +0 -0
  111. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/run_locked.py +0 -0
  112. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/runs_parent.py +0 -0
  113. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/shell_command.py +0 -0
  114. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/signal_tree.py +0 -0
  115. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/stop_then_kill.py +0 -0
  116. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/untracked_diff.py +0 -0
  117. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/wait_for_tree.py +0 -0
  118. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runner/write_run_json.py +0 -0
  119. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/runs_view_command.py +0 -0
  120. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/save_conflict_copy.py +0 -0
  121. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/save_note.py +0 -0
  122. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/saved_outputs.py +0 -0
  123. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/serve_app.py +0 -0
  124. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/server.py +0 -0
  125. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/set_state.py +0 -0
  126. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/shared_params.py +0 -0
  127. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/sort_href.py +0 -0
  128. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/sql_quote.py +0 -0
  129. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/state_path.py +0 -0
  130. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/elapsed.js +0 -0
  131. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/experiment.js +0 -0
  132. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/labnotes.css +0 -0
  133. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/local-time.js +0 -0
  134. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/new-note.js +0 -0
  135. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/note-edit.js +0 -0
  136. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/note-editor.js +0 -0
  137. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/row-link.js +0 -0
  138. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/run-cell.js +0 -0
  139. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/run-output.js +0 -0
  140. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/static/view-toggle.js +0 -0
  141. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/summarize_config.py +0 -0
  142. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/__init__.py +0 -0
  143. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/experiment_rows.py +0 -0
  144. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/experiment_run_ids.py +0 -0
  145. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/experiment_toml.py +0 -0
  146. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/experiments_ddl.py +0 -0
  147. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/file_prefix.py +0 -0
  148. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/file_text.py +0 -0
  149. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/iso_time.py +0 -0
  150. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/list_experiment_runs.py +0 -0
  151. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/list_resumable_runs.py +0 -0
  152. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/note_text.py +0 -0
  153. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/open_tables.py +0 -0
  154. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/path_glob.py +0 -0
  155. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/path_pattern.py +0 -0
  156. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_blocks.py +0 -0
  157. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_experiment.py +0 -0
  158. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_file.py +0 -0
  159. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_log.py +0 -0
  160. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_memory.py +0 -0
  161. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_run.py +0 -0
  162. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/read_run_file.py +0 -0
  163. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/runs_columns.py +0 -0
  164. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tables/summarize_experiments.py +0 -0
  165. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/base.html +0 -0
  166. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/editor_imports.html +0 -0
  167. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/experiment.html +0 -0
  168. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/index.html +0 -0
  169. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/note.html +0 -0
  170. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/templates/run.html +0 -0
  171. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/__init__.py +0 -0
  172. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/browse_app.py +0 -0
  173. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/browse_tui.py +0 -0
  174. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/check_view_response.py +0 -0
  175. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/config.py +0 -0
  176. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/dim.py +0 -0
  177. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/experiment_screen.py +0 -0
  178. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/experiments_screen.py +0 -0
  179. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/fill_table.py +0 -0
  180. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/format_cell.py +0 -0
  181. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/format_elapsed.py +0 -0
  182. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/group_cells.py +0 -0
  183. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/invocation_columns.py +0 -0
  184. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/invocation_rows.py +0 -0
  185. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/leave_prompt.py +0 -0
  186. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/log_block.py +0 -0
  187. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/log_blocks.py +0 -0
  188. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/native_rows.py +0 -0
  189. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/reload_on_change.py +0 -0
  190. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/run_app.py +0 -0
  191. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/run_cells.py +0 -0
  192. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/run_output.py +0 -0
  193. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/run_screen.py +0 -0
  194. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/run_tui.py +0 -0
  195. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/runs_table.py +0 -0
  196. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/runs_view.py +0 -0
  197. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/stopped_title.py +0 -0
  198. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/view_column.py +0 -0
  199. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/tui/view_process.py +0 -0
  200. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/__init__.py +0 -0
  201. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/format_bytes.py +0 -0
  202. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/process_alive.py +0 -0
  203. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/run_usage.py +0 -0
  204. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/tree_rss.py +0 -0
  205. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/utils/tree_usage.py +0 -0
  206. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/view/__init__.py +0 -0
  207. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/view/answer.py +0 -0
  208. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/view/build.py +0 -0
  209. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/view/respond.py +0 -0
  210. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/view/serve.py +0 -0
  211. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/wait_for_note.py +0 -0
  212. {labnotes-0.2.5 → labnotes-0.2.7}/src/labnotes/watch_rows.py +0 -0
  213. {labnotes-0.2.5 → labnotes-0.2.7}/testing-conventions.toml +0 -0
  214. {labnotes-0.2.5 → labnotes-0.2.7}/tests/conftest.py +0 -0
  215. {labnotes-0.2.5 → labnotes-0.2.7}/tests/e2e/conftest.py +0 -0
  216. {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.5
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:
@@ -25,6 +25,7 @@ dependencies = [
25
25
  "rich>=14",
26
26
  "starlette>=1.8.0",
27
27
  "textual>=8.2.8",
28
+ "tomlkit>=0.15.2",
28
29
  "uvicorn>=0.54.0",
29
30
  ]
30
31
 
@@ -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(config: LabnotesConfig, name: str | None) -> Path:
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
- path.mkdir(parents=True)
35
- (path / "experiment.toml").write_text(STARTER, encoding="utf-8")
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. With run, --PARAM=VALUE runs only the"
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(config, action)
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,8 @@
1
+ import tomllib
2
+
3
+
4
+ def parse_param_value(value: str) -> object:
5
+ try:
6
+ return tomllib.loads(f"value = {value}")["value"]
7
+ except tomllib.TOMLDecodeError:
8
+ return value
@@ -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