labnotes 0.0.125__tar.gz → 0.0.126__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 (208) hide show
  1. {labnotes-0.0.125 → labnotes-0.0.126}/PKG-INFO +1 -1
  2. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/context.md +5 -0
  3. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/daemon_app.py +2 -0
  4. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/remote_start.py +4 -0
  5. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/experiments.py +16 -0
  6. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/models.py +2 -0
  7. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/describe_event.py +6 -0
  8. labnotes-0.0.126/src/labnotes/runner/invocation_hook.py +16 -0
  9. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/prepare_experiment.py +7 -0
  10. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/read_experiment_config.py +9 -0
  11. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/run_experiment.py +21 -3
  12. {labnotes-0.0.125 → labnotes-0.0.126}/.gitignore +0 -0
  13. {labnotes-0.0.125 → labnotes-0.0.126}/CHANGELOG.md +0 -0
  14. {labnotes-0.0.125 → labnotes-0.0.126}/MIGRATIONS.md +0 -0
  15. {labnotes-0.0.125 → labnotes-0.0.126}/README.md +0 -0
  16. {labnotes-0.0.125 → labnotes-0.0.126}/pyproject.toml +0 -0
  17. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/__init__.py +0 -0
  18. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/__main__.py +0 -0
  19. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/asset_url.py +0 -0
  20. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/check_counts.py +0 -0
  21. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/cli.py +0 -0
  22. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/compose_experiment.py +0 -0
  23. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/context_guide.py +0 -0
  24. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/count_groups.py +0 -0
  25. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/count_runs.py +0 -0
  26. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/create_app.py +0 -0
  27. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/create_experiment.py +0 -0
  28. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/create_note.py +0 -0
  29. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/__init__.py +0 -0
  30. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/control_run.py +0 -0
  31. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/daemon_client.py +0 -0
  32. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/daemon_running.py +0 -0
  33. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/daemon_server.py +0 -0
  34. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/describe_status.py +0 -0
  35. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/ensure_daemon.py +0 -0
  36. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/exit_when_idle.py +0 -0
  37. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/finalize_lost_runs.py +0 -0
  38. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/last_stderr.py +0 -0
  39. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/run_daemon.py +0 -0
  40. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/run_logs.py +0 -0
  41. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/serve_daemon.py +0 -0
  42. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/socket_path.py +0 -0
  43. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/stop_runs.py +0 -0
  44. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/watch_daemon.py +0 -0
  45. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon/watch_status.py +0 -0
  46. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/daemon_command.py +0 -0
  47. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/data_html.py +0 -0
  48. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/default_config_text.py +0 -0
  49. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/default_counts.py +0 -0
  50. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/drain_watch.py +0 -0
  51. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/experiment_notes.py +0 -0
  52. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/experiment_runs.py +0 -0
  53. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/file_changes.py +0 -0
  54. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/format_duration.py +0 -0
  55. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/format_started.py +0 -0
  56. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/format_value.py +0 -0
  57. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/grid_cell.py +0 -0
  58. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/group_runs.py +0 -0
  59. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/init.py +0 -0
  60. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/invocation_started.py +0 -0
  61. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/is_non_negative_int.py +0 -0
  62. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/is_non_negative_number.py +0 -0
  63. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/is_positive_int.py +0 -0
  64. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/list_experiments.py +0 -0
  65. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/list_runs.py +0 -0
  66. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/load_config.py +0 -0
  67. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/load_experiment.py +0 -0
  68. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/load_run.py +0 -0
  69. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/log.py +0 -0
  70. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/log_path.py +0 -0
  71. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/memory_interval.py +0 -0
  72. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/modes.py +0 -0
  73. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/nests_runs.py +0 -0
  74. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/note_path.py +0 -0
  75. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/notebook_kernel.py +0 -0
  76. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/output_html.py +0 -0
  77. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/parse_run_view.py +0 -0
  78. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/parse_subset.py +0 -0
  79. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/pivot_runs.py +0 -0
  80. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/print_rows.py +0 -0
  81. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/python_blocks.py +0 -0
  82. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/read_config.py +0 -0
  83. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/read_path.py +0 -0
  84. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/render_markdown.py +0 -0
  85. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/retries.py +0 -0
  86. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/run_command.py +0 -0
  87. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/run_control.py +0 -0
  88. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/run_output.py +0 -0
  89. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/run_status.py +0 -0
  90. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/__init__.py +0 -0
  91. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/check_args.py +0 -0
  92. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/check_subset.py +0 -0
  93. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/execute_run.py +0 -0
  94. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/exit_signal.py +0 -0
  95. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/flag_name.py +0 -0
  96. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/hold_folder.py +0 -0
  97. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/in_subset.py +0 -0
  98. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/invocation_meta.py +0 -0
  99. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/new_param_run_id.py +0 -0
  100. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/new_run_id.py +0 -0
  101. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  102. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/param_args.py +0 -0
  103. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/param_grid.py +0 -0
  104. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/param_run_name.py +0 -0
  105. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/plan_runs.py +0 -0
  106. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/record_memory.py +0 -0
  107. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/run_folder.py +0 -0
  108. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/run_headless.py +0 -0
  109. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/runs_parent.py +0 -0
  110. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/shell_command.py +0 -0
  111. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/signal_tree.py +0 -0
  112. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/stop_then_kill.py +0 -0
  113. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/wait_for_tree.py +0 -0
  114. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runner/write_run_json.py +0 -0
  115. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/runs_view_command.py +0 -0
  116. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/save_conflict_copy.py +0 -0
  117. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/save_note.py +0 -0
  118. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/saved_outputs.py +0 -0
  119. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/serve_app.py +0 -0
  120. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/server.py +0 -0
  121. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/shared_params.py +0 -0
  122. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/sort_href.py +0 -0
  123. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/sql_quote.py +0 -0
  124. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/elapsed.js +0 -0
  125. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/experiment.js +0 -0
  126. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/labnotes.css +0 -0
  127. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/local-time.js +0 -0
  128. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/new-note.js +0 -0
  129. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/note-edit.js +0 -0
  130. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/note-editor.js +0 -0
  131. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/row-link.js +0 -0
  132. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/run-cell.js +0 -0
  133. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/run-output.js +0 -0
  134. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/static/view-toggle.js +0 -0
  135. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/summarize_config.py +0 -0
  136. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/__init__.py +0 -0
  137. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/experiment_rows.py +0 -0
  138. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/experiment_run_ids.py +0 -0
  139. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/experiment_toml.py +0 -0
  140. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/experiments_ddl.py +0 -0
  141. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/file_prefix.py +0 -0
  142. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/file_text.py +0 -0
  143. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/iso_time.py +0 -0
  144. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/list_experiment_runs.py +0 -0
  145. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/note_text.py +0 -0
  146. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/open_tables.py +0 -0
  147. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/path_glob.py +0 -0
  148. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/path_pattern.py +0 -0
  149. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_blocks.py +0 -0
  150. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_experiment.py +0 -0
  151. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_file.py +0 -0
  152. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_log.py +0 -0
  153. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_memory.py +0 -0
  154. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_run.py +0 -0
  155. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/read_run_file.py +0 -0
  156. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/runs_columns.py +0 -0
  157. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tables/summarize_experiments.py +0 -0
  158. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/base.html +0 -0
  159. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/editor_imports.html +0 -0
  160. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/experiment.html +0 -0
  161. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/index.html +0 -0
  162. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/note.html +0 -0
  163. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/templates/run.html +0 -0
  164. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/__init__.py +0 -0
  165. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/browse_app.py +0 -0
  166. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/browse_tui.py +0 -0
  167. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/check_view_response.py +0 -0
  168. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/config.py +0 -0
  169. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/dim.py +0 -0
  170. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/experiment_screen.py +0 -0
  171. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/experiments_screen.py +0 -0
  172. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/fill_table.py +0 -0
  173. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/format_cell.py +0 -0
  174. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/format_elapsed.py +0 -0
  175. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/group_cells.py +0 -0
  176. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/invocation_columns.py +0 -0
  177. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/invocation_rows.py +0 -0
  178. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/leave_prompt.py +0 -0
  179. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/log_block.py +0 -0
  180. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/log_blocks.py +0 -0
  181. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/native_rows.py +0 -0
  182. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/reload_on_change.py +0 -0
  183. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/run_app.py +0 -0
  184. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/run_cells.py +0 -0
  185. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/run_output.py +0 -0
  186. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/run_screen.py +0 -0
  187. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/run_tui.py +0 -0
  188. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/runs_table.py +0 -0
  189. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/runs_view.py +0 -0
  190. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/stopped_title.py +0 -0
  191. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/view_column.py +0 -0
  192. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/tui/view_process.py +0 -0
  193. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/__init__.py +0 -0
  194. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/format_bytes.py +0 -0
  195. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/process_alive.py +0 -0
  196. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/run_usage.py +0 -0
  197. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/tree_rss.py +0 -0
  198. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/utils/tree_usage.py +0 -0
  199. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/view/__init__.py +0 -0
  200. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/view/answer.py +0 -0
  201. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/view/build.py +0 -0
  202. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/view/respond.py +0 -0
  203. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/view/serve.py +0 -0
  204. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/wait_for_note.py +0 -0
  205. {labnotes-0.0.125 → labnotes-0.0.126}/src/labnotes/watch_rows.py +0 -0
  206. {labnotes-0.0.125 → labnotes-0.0.126}/testing-conventions.toml +0 -0
  207. {labnotes-0.0.125 → labnotes-0.0.126}/tests/integration/conftest.py +0 -0
  208. {labnotes-0.0.125 → labnotes-0.0.126}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.125
3
+ Version: 0.0.126
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>
@@ -89,6 +89,11 @@ command that prints one JSON object, such as the git commit. labnotes runs it
89
89
  once from the root when `run` starts and copies it into every run's `run.json`
90
90
  as `meta`. If it fails or prints anything else, no run starts. Queued runs keep
91
91
  the value from the start, even if the code changed while they waited.
92
+ `setup` and `teardown` (same places; `--setup`/`--teardown` override) are shell
93
+ commands run once per `run` from the root: setup before the first run starts,
94
+ teardown after the last one ends, even if runs failed, were stopped, or setup
95
+ failed (so teardown must cope with a partial setup). A failing setup starts no
96
+ run; a failing teardown fails the invocation.
92
97
  A run counts when it exited 0 and its `run.json` doesn't hold
93
98
  `"excluded": true`, so excluding a run by hand means adding that key; the next
94
99
  `run` replaces it. Other keys you add to `run.json` are kept and show in `run`.
@@ -80,6 +80,8 @@ def daemon_app(config: LabnotesConfig) -> Starlette:
80
80
  body.get("concurrency"), # type: ignore[arg-type]
81
81
  body.get("only"), # type: ignore[arg-type]
82
82
  body.get("args"), # type: ignore[arg-type]
83
+ body.get("setup"), # type: ignore[arg-type]
84
+ body.get("teardown"), # type: ignore[arg-type]
83
85
  )
84
86
  except ValueError as error:
85
87
  return refuse(400, str(error))
@@ -18,6 +18,8 @@ def remote_start(
18
18
  timeout: float | None = None,
19
19
  only: dict[str, str] | None = None,
20
20
  args: list[str] | None = None,
21
+ setup: str | None = None,
22
+ teardown: str | None = None,
21
23
  ) -> Start:
22
24
  async def start(report: Report, listen: Listen) -> list[dict[str, object]]:
23
25
  try:
@@ -50,6 +52,8 @@ def remote_start(
50
52
  "timeout": timeout,
51
53
  "only": only,
52
54
  "args": args,
55
+ "setup": setup,
56
+ "teardown": teardown,
53
57
  },
54
58
  )
55
59
  if reply.status_code != 202:
@@ -66,6 +66,18 @@ class PassthroughCommand(click.Command):
66
66
  help="With run: stop any run still going after this many seconds."
67
67
  " Unset, runs have no time limit.",
68
68
  )
69
+ @click.option(
70
+ "--setup",
71
+ metavar="COMMAND",
72
+ help="With run: shell command to run once before any run starts."
73
+ " Overrides experiment.toml and .labnotes.toml.",
74
+ )
75
+ @click.option(
76
+ "--teardown",
77
+ metavar="COMMAND",
78
+ help="With run: shell command to run once after every run ends."
79
+ " Overrides experiment.toml and .labnotes.toml.",
80
+ )
69
81
  @click.pass_context
70
82
  def experiments(
71
83
  ctx: click.Context,
@@ -75,6 +87,8 @@ def experiments(
75
87
  concurrency: int | None,
76
88
  experiment_config: Path | None,
77
89
  timeout: int | None,
90
+ setup: str | None,
91
+ teardown: str | None,
78
92
  ) -> None:
79
93
  passthrough: list[str] | None = ctx.meta.get(PASSTHROUGH)
80
94
  config = load_config(ctx)
@@ -103,6 +117,8 @@ def experiments(
103
117
  timeout,
104
118
  only,
105
119
  passthrough or None,
120
+ setup,
121
+ teardown,
106
122
  )
107
123
  try:
108
124
  if sys.stdout.isatty() and sys.stderr.isatty():
@@ -64,3 +64,5 @@ class ExperimentConfig:
64
64
  retries: int = DEFAULT_RETRIES
65
65
  meta: str | None = None
66
66
  memory_interval: float = DEFAULT_MEMORY_INTERVAL
67
+ setup: str | None = None
68
+ teardown: str | None = None
@@ -33,6 +33,12 @@ def describe_event(event: dict[str, object]) -> str:
33
33
  return "No runs left."
34
34
  case "concurrency":
35
35
  return f"{event['concurrency']} at a time."
36
+ case "setting up":
37
+ return "Setting up."
38
+ case "tearing down":
39
+ return "Tearing down."
40
+ case "teardown failed":
41
+ return str(event["error"])
36
42
  case "stopping":
37
43
  return "Stopping runs. Ctrl-C again to kill them."
38
44
  case _:
@@ -0,0 +1,16 @@
1
+ import asyncio
2
+ from pathlib import Path
3
+
4
+
5
+ async def invocation_hook(root: Path, hook: str, command: str) -> None:
6
+ process = await asyncio.create_subprocess_shell(
7
+ command,
8
+ cwd=root,
9
+ stdout=asyncio.subprocess.DEVNULL,
10
+ stderr=asyncio.subprocess.PIPE,
11
+ )
12
+ _, stderr = await process.communicate()
13
+ if process.returncode != 0:
14
+ message = f"{hook} command {command} exited {process.returncode}"
15
+ detail = stderr.decode(errors="replace").strip()
16
+ raise ValueError(f"{message}: {detail}" if detail else message)
@@ -1,3 +1,4 @@
1
+ import dataclasses
1
2
  from pathlib import Path
2
3
 
3
4
  from labnotes.models import ExperimentConfig, LabnotesConfig
@@ -14,6 +15,8 @@ async def prepare_experiment(
14
15
  concurrency: int | None,
15
16
  only: dict[str, str] | None = None,
16
17
  args: list[str] | None = None,
18
+ setup: str | None = None,
19
+ teardown: str | None = None,
17
20
  ) -> tuple[ExperimentConfig, int]:
18
21
  if config.experiment not in (None, name):
19
22
  raise ValueError(
@@ -33,6 +36,10 @@ async def prepare_experiment(
33
36
  config.retries,
34
37
  config.defaults,
35
38
  )
39
+ if setup is not None:
40
+ experiment = dataclasses.replace(experiment, setup=setup)
41
+ if teardown is not None:
42
+ experiment = dataclasses.replace(experiment, teardown=teardown)
36
43
  if only:
37
44
  check_subset(experiment.params, only)
38
45
  if args:
@@ -24,7 +24,10 @@ SETTINGS = {
24
24
  "meta",
25
25
  "memory_interval",
26
26
  "views",
27
+ "setup",
28
+ "teardown",
27
29
  }
30
+ HOOKS = ("setup", "teardown")
28
31
  VIEWS = {"runs"}
29
32
 
30
33
 
@@ -81,6 +84,10 @@ def read_experiment_config(
81
84
  memory_interval = values.get("memory_interval", DEFAULT_MEMORY_INTERVAL)
82
85
  if not is_non_negative_number(memory_interval):
83
86
  raise ValueError(f"{path}: memory_interval must be 0 or more seconds")
87
+ for hook in HOOKS:
88
+ run = values.get(hook)
89
+ if run is not None and (not isinstance(run, str) or not run.strip()):
90
+ raise ValueError(f"{path}: {hook} must be a string")
84
91
  views = values.get("views", {})
85
92
  if not isinstance(views, dict):
86
93
  raise ValueError(f"{path}: views must be a table, like [views]")
@@ -101,4 +108,6 @@ def read_experiment_config(
101
108
  retries,
102
109
  meta,
103
110
  memory_interval,
111
+ values.get("setup"),
112
+ values.get("teardown"),
104
113
  )
@@ -14,6 +14,7 @@ from labnotes.models import (
14
14
  from labnotes.retries import RETRY_EXIT_CODE, RETRY_SECONDS
15
15
  from labnotes.runner.execute_run import execute_run
16
16
  from labnotes.runner.in_subset import in_subset
17
+ from labnotes.runner.invocation_hook import invocation_hook
17
18
  from labnotes.runner.invocation_meta import invocation_meta
18
19
  from labnotes.runner.new_param_run_id import new_param_run_id
19
20
  from labnotes.runner.new_stamped_run_id import new_stamped_run_id
@@ -233,8 +234,25 @@ async def run_experiment(
233
234
  ):
234
235
  retry_later(run)
235
236
 
237
+ async def teardown() -> None:
238
+ if experiment.teardown is not None:
239
+ report({"event": "tearing down"})
240
+ await invocation_hook(config.root, "teardown", experiment.teardown)
241
+
236
242
  listen(Controls(interrupt, set_concurrency, kill, finish))
237
- fill()
238
- await done.wait()
239
- await asyncio.gather(*started)
243
+ # Like vitest's globalSetup: a failed setup still gets its teardown.
244
+ try:
245
+ if experiment.setup is not None:
246
+ report({"event": "setting up"})
247
+ await invocation_hook(config.root, "setup", experiment.setup)
248
+ fill()
249
+ await done.wait()
250
+ await asyncio.gather(*started)
251
+ except BaseException:
252
+ try:
253
+ await teardown()
254
+ except Exception as failure: # noqa: BLE001
255
+ report({"event": "teardown failed", "error": str(failure)})
256
+ raise
257
+ await teardown()
240
258
  return results
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes