labnotes 0.2.12__tar.gz → 0.2.14__tar.gz

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