labnotes 0.0.109__tar.gz → 0.0.111__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 (180) hide show
  1. {labnotes-0.0.109 → labnotes-0.0.111}/PKG-INFO +1 -1
  2. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/context.md +13 -5
  3. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/models.py +1 -0
  4. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/execute_run.py +6 -1
  5. labnotes-0.0.111/src/labnotes/runner/invocation_meta.py +28 -0
  6. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/read_experiment_config.py +5 -1
  7. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/run_experiment.py +5 -0
  8. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/open_tables.py +1 -1
  9. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/read_run.py +2 -0
  10. {labnotes-0.0.109 → labnotes-0.0.111}/testing-conventions.toml +1 -1
  11. {labnotes-0.0.109 → labnotes-0.0.111}/.gitignore +0 -0
  12. {labnotes-0.0.109 → labnotes-0.0.111}/CHANGELOG.md +0 -0
  13. {labnotes-0.0.109 → labnotes-0.0.111}/MIGRATIONS.md +0 -0
  14. {labnotes-0.0.109 → labnotes-0.0.111}/README.md +0 -0
  15. {labnotes-0.0.109 → labnotes-0.0.111}/pyproject.toml +0 -0
  16. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/__init__.py +0 -0
  17. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/__main__.py +0 -0
  18. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/asset_url.py +0 -0
  19. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/check_counts.py +0 -0
  20. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/cli.py +0 -0
  21. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/compose_experiment.py +0 -0
  22. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/context_guide.py +0 -0
  23. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/count_groups.py +0 -0
  24. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/count_runs.py +0 -0
  25. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/create_app.py +0 -0
  26. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/create_experiment.py +0 -0
  27. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/create_note.py +0 -0
  28. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/__init__.py +0 -0
  29. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/control_run.py +0 -0
  30. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/daemon_app.py +0 -0
  31. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/daemon_client.py +0 -0
  32. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/daemon_running.py +0 -0
  33. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/daemon_server.py +0 -0
  34. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/describe_status.py +0 -0
  35. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/ensure_daemon.py +0 -0
  36. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/exit_when_idle.py +0 -0
  37. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/finalize_lost_runs.py +0 -0
  38. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/remote_start.py +0 -0
  39. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/run_daemon.py +0 -0
  40. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/run_logs.py +0 -0
  41. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/serve_daemon.py +0 -0
  42. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/socket_path.py +0 -0
  43. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/stop_runs.py +0 -0
  44. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/watch_daemon.py +0 -0
  45. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon/watch_status.py +0 -0
  46. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/daemon_command.py +0 -0
  47. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/data_html.py +0 -0
  48. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/default_config_text.py +0 -0
  49. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/default_counts.py +0 -0
  50. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/drain_watch.py +0 -0
  51. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/experiment_notes.py +0 -0
  52. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/experiment_runs.py +0 -0
  53. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/experiments.py +0 -0
  54. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/file_changes.py +0 -0
  55. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/format_duration.py +0 -0
  56. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/format_started.py +0 -0
  57. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/format_value.py +0 -0
  58. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/group_runs.py +0 -0
  59. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/init.py +0 -0
  60. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/invocation_started.py +0 -0
  61. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/is_non_negative_int.py +0 -0
  62. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/is_positive_int.py +0 -0
  63. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/list_experiments.py +0 -0
  64. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/list_runs.py +0 -0
  65. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/load_config.py +0 -0
  66. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/load_experiment.py +0 -0
  67. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/load_run.py +0 -0
  68. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/log.py +0 -0
  69. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/log_path.py +0 -0
  70. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/modes.py +0 -0
  71. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/nests_runs.py +0 -0
  72. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/note_path.py +0 -0
  73. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/notebook_kernel.py +0 -0
  74. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/output_html.py +0 -0
  75. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/parse_run_view.py +0 -0
  76. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/print_rows.py +0 -0
  77. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/python_blocks.py +0 -0
  78. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/read_config.py +0 -0
  79. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/read_path.py +0 -0
  80. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/render_markdown.py +0 -0
  81. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/retries.py +0 -0
  82. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/run_command.py +0 -0
  83. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/run_control.py +0 -0
  84. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/run_output.py +0 -0
  85. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/run_status.py +0 -0
  86. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/__init__.py +0 -0
  87. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/describe_event.py +0 -0
  88. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/exit_signal.py +0 -0
  89. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/hold_folder.py +0 -0
  90. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/new_param_run_id.py +0 -0
  91. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/new_run_id.py +0 -0
  92. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  93. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/param_args.py +0 -0
  94. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/param_grid.py +0 -0
  95. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/param_run_name.py +0 -0
  96. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/plan_runs.py +0 -0
  97. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/prepare_experiment.py +0 -0
  98. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/run_folder.py +0 -0
  99. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/run_headless.py +0 -0
  100. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/runs_parent.py +0 -0
  101. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/shell_command.py +0 -0
  102. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/signal_tree.py +0 -0
  103. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/stop_then_kill.py +0 -0
  104. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/wait_for_tree.py +0 -0
  105. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/runner/write_run_json.py +0 -0
  106. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/save_conflict_copy.py +0 -0
  107. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/save_note.py +0 -0
  108. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/saved_outputs.py +0 -0
  109. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/serve_app.py +0 -0
  110. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/server.py +0 -0
  111. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/shared_params.py +0 -0
  112. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/sort_href.py +0 -0
  113. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/sql_quote.py +0 -0
  114. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/static/elapsed.js +0 -0
  115. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/static/experiment.js +0 -0
  116. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/static/labnotes.css +0 -0
  117. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/static/local-time.js +0 -0
  118. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/static/new-note.js +0 -0
  119. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/static/note-edit.js +0 -0
  120. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/static/note-editor.js +0 -0
  121. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/static/row-link.js +0 -0
  122. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/static/run-cell.js +0 -0
  123. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/static/run-output.js +0 -0
  124. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/static/view-toggle.js +0 -0
  125. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/summarize_config.py +0 -0
  126. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/__init__.py +0 -0
  127. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/experiment_rows.py +0 -0
  128. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/experiment_run_ids.py +0 -0
  129. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/experiment_toml.py +0 -0
  130. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/experiments_ddl.py +0 -0
  131. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/file_prefix.py +0 -0
  132. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/file_text.py +0 -0
  133. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/iso_time.py +0 -0
  134. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/list_experiment_runs.py +0 -0
  135. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/note_text.py +0 -0
  136. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/path_glob.py +0 -0
  137. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/path_pattern.py +0 -0
  138. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/read_blocks.py +0 -0
  139. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/read_experiment.py +0 -0
  140. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/read_file.py +0 -0
  141. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/read_log.py +0 -0
  142. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tables/summarize_experiments.py +0 -0
  143. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/templates/base.html +0 -0
  144. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/templates/editor_imports.html +0 -0
  145. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/templates/experiment.html +0 -0
  146. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/templates/index.html +0 -0
  147. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/templates/note.html +0 -0
  148. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/templates/run.html +0 -0
  149. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/__init__.py +0 -0
  150. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/browse_app.py +0 -0
  151. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/browse_tui.py +0 -0
  152. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/config.py +0 -0
  153. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/dim.py +0 -0
  154. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/experiment_screen.py +0 -0
  155. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/experiments_screen.py +0 -0
  156. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/fill_table.py +0 -0
  157. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/format_elapsed.py +0 -0
  158. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/group_cells.py +0 -0
  159. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/invocation_columns.py +0 -0
  160. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/invocation_rows.py +0 -0
  161. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/leave_prompt.py +0 -0
  162. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/log_block.py +0 -0
  163. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/log_blocks.py +0 -0
  164. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/reload_on_change.py +0 -0
  165. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/run_app.py +0 -0
  166. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/run_cells.py +0 -0
  167. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/run_output.py +0 -0
  168. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/run_screen.py +0 -0
  169. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/run_tui.py +0 -0
  170. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/runs_table.py +0 -0
  171. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/tui/stopped_title.py +0 -0
  172. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/utils/__init__.py +0 -0
  173. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/utils/format_bytes.py +0 -0
  174. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/utils/process_alive.py +0 -0
  175. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/utils/run_usage.py +0 -0
  176. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/utils/tree_rss.py +0 -0
  177. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/wait_for_note.py +0 -0
  178. {labnotes-0.0.109 → labnotes-0.0.111}/src/labnotes/watch_rows.py +0 -0
  179. {labnotes-0.0.109 → labnotes-0.0.111}/tests/integration/conftest.py +0 -0
  180. {labnotes-0.0.109 → labnotes-0.0.111}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.109
3
+ Version: 0.0.111
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>
@@ -24,8 +24,9 @@ When `experiment.toml` sits next to `.labnotes.toml`, the root is the one experi
24
24
  `experiment.toml`, files or runs.
25
25
  - `files`: `experiment`, `filename`, `path`, `ctime`, `mtime`.
26
26
  - `runs`: `experiment`, `id`, `folder`, `created`, `ended`, `exit_code`,
27
- `error`, `stdout`, `stderr`, `params` as JSON, and `run`, the whole
28
- `run.json` as JSON, read from each run folder's `run.json`.
27
+ `error`, `stdout`, `stderr`, `params` as JSON, `meta` as JSON (null without
28
+ a `meta` command), `command`, and `run`, the whole `run.json` as JSON, read
29
+ from each run folder's `run.json`.
29
30
 
30
31
  Keys come from folder names, so renaming a folder renames its rows on the next
31
32
  read. Paths are relative to the root, and times are UTC ISO 8601.
@@ -74,13 +75,20 @@ inside it (`runs/20261020-000429-brave-fox/quiet-river`); the run id is that pat
74
75
  script so it writes its outputs there.
75
76
  Each param is appended as `--name value`, with the name as written (use
76
77
  `source-language` for a `--source-language` flag), booleans as 0 or 1, and lists
77
- and tables as JSON. labnotes writes `run.json` (params, created, ended, exit
78
- code, `ended_by`, `signal`), `stdout.log` and `stderr.log` to the
79
- run folder. `ended_by` is `"timeout"`, `"stop"` (labnotes stopped it),
78
+ and tables as JSON. labnotes writes `run.json` (params, `command`, created,
79
+ ended, exit code, `ended_by`, `signal`), `stdout.log` and `stderr.log` to the
80
+ run folder. `command` is the shell command the run executed, with `{run_dir}`
81
+ and the params filled in, written before the run starts, so a killed run has it
82
+ and a later edit to `experiment.toml` doesn't hide what a run did. `ended_by` is `"timeout"`, `"stop"` (labnotes stopped it),
80
83
  `"signal"` (a signal labnotes didn't send, like the out-of-memory killer),
81
84
  `"lost"` (its process died unseen; the daemon marks these when it starts, with
82
85
  `ended` set and `exit_code` null) or null (the script exited itself). `signal` is the signal number read from the
83
86
  exit code (negative, or 128 + N from the shell), else null.
87
+ `meta` in `experiment.toml` (or `[experiment]` in `.labnotes.toml`) is a shell
88
+ command that prints one JSON object, such as the git commit. labnotes runs it
89
+ once from the root when `run` starts and copies it into every run's `run.json`
90
+ as `meta`. If it fails or prints anything else, no run starts. Queued runs keep
91
+ the value from the start, even if the code changed while they waited.
84
92
  A run counts when it exited 0 and its `run.json` doesn't hold
85
93
  `"excluded": true`, so excluding a run by hand means adding that key; the next
86
94
  `run` replaces it. Other keys you add to `run.json` are kept and show in `run`.
@@ -65,3 +65,4 @@ class ExperimentConfig:
65
65
  mode: str = DEFAULT_MODE
66
66
  concurrency: int = 1
67
67
  retries: int = DEFAULT_RETRIES
68
+ meta: str | None = None
@@ -29,9 +29,12 @@ async def execute_run(
29
29
  timeout: float | None = None,
30
30
  stop: Callable[[], None] = lambda: None,
31
31
  stopped: Callable[[], bool] = lambda: False,
32
+ meta: dict[str, object] | None = None,
32
33
  ) -> dict[str, object]:
34
+ resolved = shell_command(command, folder, params)
33
35
  run: dict[str, object] = {
34
36
  "params": params,
37
+ "command": resolved,
35
38
  "created": iso_time(time.time()),
36
39
  "ended": None,
37
40
  "exit_code": None,
@@ -41,6 +44,8 @@ async def execute_run(
41
44
  }
42
45
  if retry_of is not None:
43
46
  run["retry_of"] = retry_of
47
+ if meta is not None:
48
+ run["meta"] = meta
44
49
  write_run_json(folder, run)
45
50
  with (
46
51
  (folder / "stdout.log").open("wb") as stdout,
@@ -48,7 +53,7 @@ async def execute_run(
48
53
  ):
49
54
  try:
50
55
  process = await asyncio.create_subprocess_shell(
51
- shell_command(command, folder, params),
56
+ resolved,
52
57
  cwd=root,
53
58
  env={
54
59
  "FORCE_COLOR": "1",
@@ -0,0 +1,28 @@
1
+ import asyncio
2
+ import json
3
+ from pathlib import Path
4
+
5
+
6
+ async def invocation_meta(root: Path, command: str) -> dict[str, object]:
7
+ process = await asyncio.create_subprocess_shell(
8
+ command,
9
+ cwd=root,
10
+ stdout=asyncio.subprocess.PIPE,
11
+ stderr=asyncio.subprocess.PIPE,
12
+ )
13
+ stdout, stderr = await process.communicate()
14
+ detail = stderr.decode(errors="replace").strip()
15
+
16
+ def fail(reason: str) -> ValueError:
17
+ message = f"meta command {command} {reason}"
18
+ return ValueError(f"{message}: {detail}" if detail else message)
19
+
20
+ if process.returncode != 0:
21
+ raise fail(f"exited {process.returncode}")
22
+ try:
23
+ meta = json.loads(stdout)
24
+ except ValueError:
25
+ meta = None
26
+ if not isinstance(meta, dict):
27
+ raise fail("didn't print a JSON object")
28
+ return meta
@@ -19,6 +19,7 @@ SETTINGS = {
19
19
  "mode",
20
20
  "concurrency",
21
21
  "retries",
22
+ "meta",
22
23
  }
23
24
 
24
25
 
@@ -69,6 +70,9 @@ def read_experiment_config(
69
70
  retries = values.get("retries", retries)
70
71
  if not is_non_negative_int(retries):
71
72
  raise ValueError(f"{path}: retries must be 0 or more")
73
+ meta = values.get("meta")
74
+ if meta is not None and (not isinstance(meta, str) or not meta.strip()):
75
+ raise ValueError(f"{path}: meta must be a string")
72
76
  return ExperimentConfig(
73
- command, repeats, params, name, counts, mode, concurrency, retries
77
+ command, repeats, params, name, counts, mode, concurrency, retries, meta
74
78
  )
@@ -13,6 +13,7 @@ from labnotes.models import (
13
13
  )
14
14
  from labnotes.retries import RETRY_EXIT_CODE, RETRY_SECONDS
15
15
  from labnotes.runner.execute_run import execute_run
16
+ from labnotes.runner.invocation_meta import invocation_meta
16
17
  from labnotes.runner.new_param_run_id import new_param_run_id
17
18
  from labnotes.runner.new_stamped_run_id import new_stamped_run_id
18
19
  from labnotes.runner.param_grid import param_grid
@@ -36,6 +37,9 @@ async def run_experiment(
36
37
  timeout: float | None = None,
37
38
  retry_seconds: float = RETRY_SECONDS,
38
39
  ) -> list[dict[str, object]]:
40
+ meta = None
41
+ if experiment.meta is not None:
42
+ meta = await invocation_meta(config.root, experiment.meta)
39
43
  tables = open_tables(config)
40
44
  existing = await experiment_run_ids(tables, name)
41
45
  finished: list[dict[str, object]] = []
@@ -256,6 +260,7 @@ async def run_experiment(
256
260
  timeout=timeout,
257
261
  stop=lambda: kill(run),
258
262
  stopped=lambda: run in stopped,
263
+ meta=meta,
259
264
  )
260
265
  del running[run], trees[run]
261
266
  report({"event": "ended", "run": run, **result})
@@ -22,7 +22,7 @@ RUNS = """
22
22
  CREATE TABLE runs (
23
23
  experiment TEXT, id TEXT, folder TEXT, created TEXT, ended TEXT,
24
24
  exit_code INTEGER, error TEXT,
25
- stdout TEXT, stderr TEXT, params TEXT, run TEXT,
25
+ stdout TEXT, stderr TEXT, params TEXT, meta TEXT, command TEXT, run TEXT,
26
26
  PRIMARY KEY (experiment, id)
27
27
  )
28
28
  """
@@ -25,6 +25,8 @@ def read_run(
25
25
  "stdout": f"{folder}/stdout.log",
26
26
  "stderr": f"{folder}/stderr.log",
27
27
  "params": json.dumps(run.get("params", {})),
28
+ "meta": None if (meta := run.get("meta")) is None else json.dumps(meta),
29
+ "command": run.get("command"),
28
30
  "run": json.dumps(run),
29
31
  }
30
32
  ]
@@ -67,7 +67,7 @@ reason = "WorkerState members are singletons, so is equals =="
67
67
  [[python.exempt]]
68
68
  path = "src/labnotes/runner/run_experiment.py"
69
69
  rules = ["mutation"]
70
- lines = [184, 263]
70
+ lines = [188, 269]
71
71
  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 =="
72
72
 
73
73
  [[python.exempt]]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes