labnotes 0.0.128__tar.gz → 0.0.130__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 (218) hide show
  1. {labnotes-0.0.128 → labnotes-0.0.130}/PKG-INFO +1 -1
  2. labnotes-0.0.130/src/labnotes/compose_experiment.py +9 -0
  3. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/context.md +18 -8
  4. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/daemon_app.py +1 -0
  5. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/remote_start.py +4 -0
  6. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/models.py +1 -0
  7. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/execute_run.py +2 -1
  8. labnotes-0.0.130/src/labnotes/runner/fill_dirs.py +8 -0
  9. labnotes-0.0.130/src/labnotes/runner/fill_experiment_env.py +34 -0
  10. labnotes-0.0.130/src/labnotes/runner/hook_dirs.py +21 -0
  11. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/invocation_hook.py +4 -1
  12. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/invocation_meta.py +4 -1
  13. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/read_experiment_config.py +10 -0
  14. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/run_experiment.py +63 -17
  15. labnotes-0.0.128/src/labnotes/compose_experiment.py +0 -8
  16. labnotes-0.0.128/src/labnotes/runner/fill_ids.py +0 -8
  17. labnotes-0.0.128/src/labnotes/runner/hook_ids.py +0 -14
  18. {labnotes-0.0.128 → labnotes-0.0.130}/.gitignore +0 -0
  19. {labnotes-0.0.128 → labnotes-0.0.130}/CHANGELOG.md +0 -0
  20. {labnotes-0.0.128 → labnotes-0.0.130}/MIGRATIONS.md +0 -0
  21. {labnotes-0.0.128 → labnotes-0.0.130}/README.md +0 -0
  22. {labnotes-0.0.128 → labnotes-0.0.130}/pyproject.toml +0 -0
  23. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/__init__.py +0 -0
  24. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/__main__.py +0 -0
  25. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/asset_url.py +0 -0
  26. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/check_counts.py +0 -0
  27. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/cli.py +0 -0
  28. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/context_guide.py +0 -0
  29. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/count_groups.py +0 -0
  30. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/count_runs.py +0 -0
  31. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/create_app.py +0 -0
  32. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/create_experiment.py +0 -0
  33. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/create_note.py +0 -0
  34. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/__init__.py +0 -0
  35. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/control_run.py +0 -0
  36. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/daemon_client.py +0 -0
  37. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/daemon_running.py +0 -0
  38. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/daemon_server.py +0 -0
  39. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/describe_status.py +0 -0
  40. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/ensure_daemon.py +0 -0
  41. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/exit_when_idle.py +0 -0
  42. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/finalize_lost_runs.py +0 -0
  43. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/last_stderr.py +0 -0
  44. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/run_daemon.py +0 -0
  45. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/run_logs.py +0 -0
  46. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/serve_daemon.py +0 -0
  47. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/socket_path.py +0 -0
  48. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/stop_runs.py +0 -0
  49. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/watch_daemon.py +0 -0
  50. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon/watch_status.py +0 -0
  51. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/daemon_command.py +0 -0
  52. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/data_html.py +0 -0
  53. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/default_config_text.py +0 -0
  54. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/default_counts.py +0 -0
  55. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/drain_watch.py +0 -0
  56. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/experiment_notes.py +0 -0
  57. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/experiment_runs.py +0 -0
  58. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/experiments.py +0 -0
  59. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/file_changes.py +0 -0
  60. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/format_duration.py +0 -0
  61. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/format_started.py +0 -0
  62. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/format_value.py +0 -0
  63. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/grid_cell.py +0 -0
  64. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/group_runs.py +0 -0
  65. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/init.py +0 -0
  66. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/invocation_started.py +0 -0
  67. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/is_non_negative_int.py +0 -0
  68. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/is_non_negative_number.py +0 -0
  69. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/is_positive_int.py +0 -0
  70. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/list_experiments.py +0 -0
  71. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/list_runs.py +0 -0
  72. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/load_config.py +0 -0
  73. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/load_experiment.py +0 -0
  74. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/load_run.py +0 -0
  75. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/log.py +0 -0
  76. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/log_path.py +0 -0
  77. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/memory_interval.py +0 -0
  78. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/modes.py +0 -0
  79. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/nests_runs.py +0 -0
  80. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/note_path.py +0 -0
  81. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/notebook_kernel.py +0 -0
  82. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/output_html.py +0 -0
  83. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/parse_run_view.py +0 -0
  84. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/parse_subset.py +0 -0
  85. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/pivot_runs.py +0 -0
  86. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/print_rows.py +0 -0
  87. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/python_blocks.py +0 -0
  88. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/read_config.py +0 -0
  89. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/read_path.py +0 -0
  90. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/render_markdown.py +0 -0
  91. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/retries.py +0 -0
  92. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/run_command.py +0 -0
  93. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/run_control.py +0 -0
  94. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/run_output.py +0 -0
  95. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/run_status.py +0 -0
  96. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/__init__.py +0 -0
  97. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/cgroup_memory.py +0 -0
  98. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/check_args.py +0 -0
  99. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/check_subset.py +0 -0
  100. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/container_cgroup.py +0 -0
  101. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/container_memory.py +0 -0
  102. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/describe_event.py +0 -0
  103. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/docker_output.py +0 -0
  104. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/exit_signal.py +0 -0
  105. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/flag_name.py +0 -0
  106. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/hold_folder.py +0 -0
  107. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/in_subset.py +0 -0
  108. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/new_param_run_id.py +0 -0
  109. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/new_run_id.py +0 -0
  110. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  111. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/param_args.py +0 -0
  112. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/param_grid.py +0 -0
  113. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/param_run_name.py +0 -0
  114. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/plan_runs.py +0 -0
  115. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/prepare_experiment.py +0 -0
  116. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/record_memory.py +0 -0
  117. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/run_folder.py +0 -0
  118. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/run_headless.py +0 -0
  119. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/runs_parent.py +0 -0
  120. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/shell_command.py +0 -0
  121. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/signal_tree.py +0 -0
  122. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/stop_then_kill.py +0 -0
  123. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/wait_for_tree.py +0 -0
  124. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runner/write_run_json.py +0 -0
  125. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/runs_view_command.py +0 -0
  126. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/save_conflict_copy.py +0 -0
  127. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/save_note.py +0 -0
  128. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/saved_outputs.py +0 -0
  129. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/serve_app.py +0 -0
  130. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/server.py +0 -0
  131. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/shared_params.py +0 -0
  132. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/sort_href.py +0 -0
  133. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/sql_quote.py +0 -0
  134. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/static/elapsed.js +0 -0
  135. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/static/experiment.js +0 -0
  136. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/static/labnotes.css +0 -0
  137. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/static/local-time.js +0 -0
  138. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/static/new-note.js +0 -0
  139. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/static/note-edit.js +0 -0
  140. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/static/note-editor.js +0 -0
  141. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/static/row-link.js +0 -0
  142. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/static/run-cell.js +0 -0
  143. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/static/run-output.js +0 -0
  144. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/static/view-toggle.js +0 -0
  145. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/summarize_config.py +0 -0
  146. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/__init__.py +0 -0
  147. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/experiment_rows.py +0 -0
  148. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/experiment_run_ids.py +0 -0
  149. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/experiment_toml.py +0 -0
  150. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/experiments_ddl.py +0 -0
  151. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/file_prefix.py +0 -0
  152. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/file_text.py +0 -0
  153. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/iso_time.py +0 -0
  154. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/list_experiment_runs.py +0 -0
  155. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/note_text.py +0 -0
  156. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/open_tables.py +0 -0
  157. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/path_glob.py +0 -0
  158. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/path_pattern.py +0 -0
  159. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/read_blocks.py +0 -0
  160. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/read_experiment.py +0 -0
  161. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/read_file.py +0 -0
  162. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/read_log.py +0 -0
  163. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/read_memory.py +0 -0
  164. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/read_run.py +0 -0
  165. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/read_run_file.py +0 -0
  166. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/runs_columns.py +0 -0
  167. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tables/summarize_experiments.py +0 -0
  168. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/templates/base.html +0 -0
  169. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/templates/editor_imports.html +0 -0
  170. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/templates/experiment.html +0 -0
  171. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/templates/index.html +0 -0
  172. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/templates/note.html +0 -0
  173. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/templates/run.html +0 -0
  174. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/__init__.py +0 -0
  175. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/browse_app.py +0 -0
  176. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/browse_tui.py +0 -0
  177. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/check_view_response.py +0 -0
  178. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/config.py +0 -0
  179. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/dim.py +0 -0
  180. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/experiment_screen.py +0 -0
  181. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/experiments_screen.py +0 -0
  182. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/fill_table.py +0 -0
  183. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/format_cell.py +0 -0
  184. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/format_elapsed.py +0 -0
  185. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/group_cells.py +0 -0
  186. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/invocation_columns.py +0 -0
  187. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/invocation_rows.py +0 -0
  188. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/leave_prompt.py +0 -0
  189. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/log_block.py +0 -0
  190. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/log_blocks.py +0 -0
  191. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/native_rows.py +0 -0
  192. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/reload_on_change.py +0 -0
  193. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/run_app.py +0 -0
  194. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/run_cells.py +0 -0
  195. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/run_output.py +0 -0
  196. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/run_screen.py +0 -0
  197. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/run_tui.py +0 -0
  198. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/runs_table.py +0 -0
  199. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/runs_view.py +0 -0
  200. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/stopped_title.py +0 -0
  201. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/view_column.py +0 -0
  202. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/tui/view_process.py +0 -0
  203. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/utils/__init__.py +0 -0
  204. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/utils/format_bytes.py +0 -0
  205. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/utils/process_alive.py +0 -0
  206. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/utils/run_usage.py +0 -0
  207. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/utils/tree_rss.py +0 -0
  208. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/utils/tree_usage.py +0 -0
  209. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/view/__init__.py +0 -0
  210. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/view/answer.py +0 -0
  211. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/view/build.py +0 -0
  212. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/view/respond.py +0 -0
  213. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/view/serve.py +0 -0
  214. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/wait_for_note.py +0 -0
  215. {labnotes-0.0.128 → labnotes-0.0.130}/src/labnotes/watch_rows.py +0 -0
  216. {labnotes-0.0.128 → labnotes-0.0.130}/testing-conventions.toml +0 -0
  217. {labnotes-0.0.128 → labnotes-0.0.130}/tests/integration/conftest.py +0 -0
  218. {labnotes-0.0.128 → labnotes-0.0.130}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.128
3
+ Version: 0.0.130
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>
@@ -0,0 +1,9 @@
1
+ def compose_experiment(
2
+ base: dict[str, object], layer: dict[str, object]
3
+ ) -> dict[str, object]:
4
+ composed = {**base, **layer}
5
+ for key in ("params", "env"):
6
+ base_values, layer_values = base.get(key), layer.get(key)
7
+ if isinstance(base_values, dict) and isinstance(layer_values, dict):
8
+ composed[key] = {**base_values, **layer_values}
9
+ return composed
@@ -15,7 +15,7 @@ path = "{experiment}/runs/{run}" # one folder per run
15
15
 
16
16
  Paths are relative to the root. `{experiment}` and `{run}` stand for folder names; the experiments and files paths use `{experiment}`, the runs path both. A project laid out differently keeps its folders and changes the paths: look at where its experiments, notes and runs live, then write the templates that match.
17
17
 
18
- When `experiment.toml` sits next to `.labnotes.toml`, the root is the one experiment, named after its folder. Paths then drop `{experiment}`: files default to `files/`, runs to `runs/{run}`, and `[experiments] path` is rejected. An `[experiment]` table in `.labnotes.toml` holds the same keys as `experiment.toml` and is the base each `experiment.toml` layers over (top-level keys replace, `params` merge by name). With no `[experiments] path`, it also makes the root the one experiment.
18
+ When `experiment.toml` sits next to `.labnotes.toml`, the root is the one experiment, named after its folder. Paths then drop `{experiment}`: files default to `files/`, runs to `runs/{run}`, and `[experiments] path` is rejected. An `[experiment]` table in `.labnotes.toml` holds the same keys as `experiment.toml` and is the base each `experiment.toml` layers over (top-level keys replace, `params` and `env` merge by name). With no `[experiments] path`, it also makes the root the one experiment.
19
19
 
20
20
  `labnotes experiments` reads three tables from those paths:
21
21
 
@@ -72,10 +72,9 @@ Each run gets a new run folder. In `matrix` mode it is named for the UTC time it
72
72
  its folder the same way. One that plans more gets one folder named that way, and its runs nest
73
73
  inside it (`runs/20261020-000429-brave-fox/quiet-river`); the run id is that path under `runs/`. Either way, folder listings sort by start. `command` runs in a shell from the root, with
74
74
  `{run_dir}` replaced by the run folder's absolute path. Pass it to the
75
- script so it writes its outputs there. `{run_id}` becomes the run id, and
76
- `{invocation_id}` an id every run of one `run` command shares: the run id when
77
- it plans one run, else the adhoc invocation folder's name, or in `matrix` mode a
78
- stamped id that names no folder. All three are shell-quoted.
75
+ script so it writes its outputs there. `{invocation_dir}` is the shared adhoc
76
+ group folder's absolute path when several runs are planned; otherwise it equals
77
+ each run's `{run_dir}`. Both placeholders are shell-quoted.
79
78
  Each param is appended as `--name value`, with the name as written (use
80
79
  `source-language` for a `--source-language` flag), booleans as 0 or 1, and lists
81
80
  and tables as JSON. labnotes writes `run.json` (params, `command`, created,
@@ -96,9 +95,20 @@ the value from the start, even if the code changed while they waited.
96
95
  commands run once per `run` from the root: setup before the first run starts,
97
96
  teardown after the last one ends, even if runs failed, were stopped, or setup
98
97
  failed (so teardown must cope with a partial setup). A failing setup starts no
99
- run; a failing teardown fails the invocation. Both get `{invocation_id}`, and
100
- `{run_id}` only when one run is planned; otherwise `{run_id}` there fails the
101
- invocation before anything starts.
98
+ run; a failing teardown fails the invocation. Both get `{invocation_dir}` only:
99
+ the shared folder for a multi-run adhoc invocation, or the run folder when one
100
+ run is planned. That folder exists before setup starts. In matrix mode with
101
+ several runs there is no shared folder, so `{invocation_dir}` in a hook fails.
102
+ `{run_dir}` is never filled in hooks; use `{invocation_dir}` instead. An
103
+ unavailable placeholder fails before anything starts.
104
+ `[env]` in `experiment.toml` sets string environment values for `command`,
105
+ `meta`, `setup`, and `teardown`. `[experiment.env]` in `.labnotes.toml`
106
+ provides shared values; experiment values replace them by name. Variables in
107
+ the environment that starts `labnotes experiments NAME run` win over both,
108
+ including inline assignments. Values can use raw `{run_dir}` and
109
+ `{invocation_dir}` placeholders. In hooks and meta, only `{invocation_dir}` is
110
+ available, and it requires one planned run in matrix mode.
111
+ `LABNOTES_*` names are reserved for labnotes.
102
112
  A run counts when it exited 0 and its `run.json` doesn't hold
103
113
  `"excluded": true`, so excluding a run by hand means adding that key; the next
104
114
  `run` replaces it. Other keys you add to `run.json` are kept and show in `run`.
@@ -109,6 +109,7 @@ def daemon_app(config: LabnotesConfig) -> Starlette:
109
109
  body.get("timeout"), # type: ignore[arg-type]
110
110
  only=body.get("only"), # type: ignore[arg-type]
111
111
  args=body.get("args"), # type: ignore[arg-type]
112
+ environment=body.get("environment"), # type: ignore[arg-type]
112
113
  )
113
114
  # Clients hear why the session ended; the daemon keeps serving.
114
115
  except Exception as failure: # noqa: BLE001
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import dataclasses
3
+ import os
3
4
  from collections.abc import Callable
4
5
  from pathlib import Path
5
6
 
@@ -21,6 +22,8 @@ def remote_start(
21
22
  setup: str | None = None,
22
23
  teardown: str | None = None,
23
24
  ) -> Start:
25
+ environment = dict(os.environ)
26
+
24
27
  async def start(report: Report, listen: Listen) -> list[dict[str, object]]:
25
28
  try:
26
29
  return await follow(report, listen)
@@ -54,6 +57,7 @@ def remote_start(
54
57
  "args": args,
55
58
  "setup": setup,
56
59
  "teardown": teardown,
60
+ "environment": environment,
57
61
  },
58
62
  )
59
63
  if reply.status_code != 202:
@@ -66,3 +66,4 @@ class ExperimentConfig:
66
66
  memory_interval: float = DEFAULT_MEMORY_INTERVAL
67
67
  setup: str | None = None
68
68
  teardown: str | None = None
69
+ env: dict[str, str] = field(default_factory=dict)
@@ -34,6 +34,7 @@ async def execute_run(
34
34
  args: Sequence[str] = (),
35
35
  *,
36
36
  memory_interval: float,
37
+ environment: dict[str, str] | None = None,
37
38
  ) -> dict[str, object]:
38
39
  resolved = shell_command(command, folder, params, args)
39
40
  run: dict[str, object] = {
@@ -64,7 +65,7 @@ async def execute_run(
64
65
  env={
65
66
  "FORCE_COLOR": "1",
66
67
  "CLICOLOR_FORCE": "1",
67
- **os.environ,
68
+ **(os.environ if environment is None else environment),
68
69
  LOG_ENV: str(folder / "log.jsonl"),
69
70
  RUN_ENV: str(folder.resolve()),
70
71
  },
@@ -0,0 +1,8 @@
1
+ import shlex
2
+ from collections.abc import Mapping
3
+
4
+
5
+ def fill_dirs(command: str, dirs: Mapping[str, str], *, quote: bool = True) -> str:
6
+ for name, value in dirs.items():
7
+ command = command.replace(f"{{{name}}}", shlex.quote(value) if quote else value)
8
+ return command
@@ -0,0 +1,34 @@
1
+ from collections.abc import Mapping
2
+
3
+ from labnotes.runner.fill_dirs import fill_dirs
4
+
5
+
6
+ def fill_experiment_env(
7
+ configured: Mapping[str, str],
8
+ inherited: Mapping[str, str],
9
+ dirs: Mapping[str, str],
10
+ *,
11
+ hook: str | None = None,
12
+ runs: int = 1,
13
+ ) -> dict[str, str]:
14
+ caller = {
15
+ key: value
16
+ for key, value in inherited.items()
17
+ if not key.startswith("LABNOTES_")
18
+ }
19
+ filled: dict[str, str] = {}
20
+ for key, value in configured.items():
21
+ if key in caller:
22
+ continue
23
+ if hook is not None and "{run_dir}" in value:
24
+ raise ValueError(
25
+ f"{hook} env.{key} uses {{run_dir}}; use {{invocation_dir}} instead."
26
+ )
27
+ for name in ("run_dir", "invocation_dir"):
28
+ if f"{{{name}}}" in value and name not in dirs:
29
+ raise ValueError(
30
+ f"{hook} env.{key} uses {{{name}}}, but this invocation plans "
31
+ f"{runs} runs. {{{name}}} needs exactly one run in this mode."
32
+ )
33
+ filled[key] = fill_dirs(value, dirs, quote=False)
34
+ return {**filled, **caller}
@@ -0,0 +1,21 @@
1
+ def hook_dirs(
2
+ hooks: dict[str, str | None], invocation_dir: str | None, run_dirs: list[str]
3
+ ) -> dict[str, str]:
4
+ match run_dirs:
5
+ case [run_dir]:
6
+ invocation_dir = invocation_dir or run_dir
7
+ available = {"invocation_dir": invocation_dir} if invocation_dir is not None else {}
8
+ for hook, command in hooks.items():
9
+ if command is None:
10
+ continue
11
+ if "{run_dir}" in command:
12
+ raise ValueError(
13
+ f"{hook} command {command} uses {{run_dir}}; "
14
+ "use {invocation_dir} instead."
15
+ )
16
+ if "{invocation_dir}" in command and invocation_dir is None:
17
+ raise ValueError(
18
+ f"{hook} command {command} uses {{invocation_dir}}, but this "
19
+ f"invocation plans {len(run_dirs)} runs and has no shared folder."
20
+ )
21
+ return available
@@ -2,12 +2,15 @@ import asyncio
2
2
  from pathlib import Path
3
3
 
4
4
 
5
- async def invocation_hook(root: Path, hook: str, command: str) -> None:
5
+ async def invocation_hook(
6
+ root: Path, hook: str, command: str, environment: dict[str, str] | None = None
7
+ ) -> None:
6
8
  process = await asyncio.create_subprocess_shell(
7
9
  command,
8
10
  cwd=root,
9
11
  stdout=asyncio.subprocess.DEVNULL,
10
12
  stderr=asyncio.subprocess.PIPE,
13
+ env=environment,
11
14
  )
12
15
  _, stderr = await process.communicate()
13
16
  if process.returncode != 0:
@@ -3,12 +3,15 @@ import json
3
3
  from pathlib import Path
4
4
 
5
5
 
6
- async def invocation_meta(root: Path, command: str) -> dict[str, object]:
6
+ async def invocation_meta(
7
+ root: Path, command: str, environment: dict[str, str] | None = None
8
+ ) -> dict[str, object]:
7
9
  process = await asyncio.create_subprocess_shell(
8
10
  command,
9
11
  cwd=root,
10
12
  stdout=asyncio.subprocess.PIPE,
11
13
  stderr=asyncio.subprocess.PIPE,
14
+ env=environment,
12
15
  )
13
16
  stdout, stderr = await process.communicate()
14
17
  detail = stderr.decode(errors="replace").strip()
@@ -26,6 +26,7 @@ SETTINGS = {
26
26
  "views",
27
27
  "setup",
28
28
  "teardown",
29
+ "env",
29
30
  }
30
31
  HOOKS = ("setup", "teardown")
31
32
  VIEWS = {"runs"}
@@ -88,6 +89,14 @@ def read_experiment_config(
88
89
  run = values.get(hook)
89
90
  if run is not None and (not isinstance(run, str) or not run.strip()):
90
91
  raise ValueError(f"{path}: {hook} must be a string")
92
+ env = values.get("env", {})
93
+ if not isinstance(env, dict):
94
+ raise ValueError(f"{path}: env must be a table, like [env]")
95
+ for key, value in env.items():
96
+ if key.startswith("LABNOTES_"):
97
+ raise ValueError(f"{path}: env.{key} is reserved for labnotes")
98
+ if not isinstance(value, str):
99
+ raise ValueError(f"{path}: env.{key} must be a string")
91
100
  views = values.get("views", {})
92
101
  if not isinstance(views, dict):
93
102
  raise ValueError(f"{path}: views must be a table, like [views]")
@@ -110,4 +119,5 @@ def read_experiment_config(
110
119
  memory_interval,
111
120
  values.get("setup"),
112
121
  values.get("teardown"),
122
+ env,
113
123
  )
@@ -1,6 +1,8 @@
1
1
  import asyncio
2
2
  import json
3
+ import os
3
4
  import signal
5
+ from pathlib import Path
4
6
 
5
7
  import psutil
6
8
 
@@ -13,8 +15,9 @@ from labnotes.models import (
13
15
  )
14
16
  from labnotes.retries import RETRY_EXIT_CODE, RETRY_SECONDS
15
17
  from labnotes.runner.execute_run import execute_run
16
- from labnotes.runner.fill_ids import fill_ids
17
- from labnotes.runner.hook_ids import hook_ids
18
+ from labnotes.runner.fill_experiment_env import fill_experiment_env
19
+ from labnotes.runner.fill_dirs import fill_dirs
20
+ from labnotes.runner.hook_dirs import hook_dirs
18
21
  from labnotes.runner.in_subset import in_subset
19
22
  from labnotes.runner.invocation_hook import invocation_hook
20
23
  from labnotes.runner.invocation_meta import invocation_meta
@@ -42,10 +45,9 @@ async def run_experiment(
42
45
  retry_seconds: float = RETRY_SECONDS,
43
46
  only: dict[str, str] | None = None,
44
47
  args: list[str] | None = None,
48
+ environment: dict[str, str] | None = None,
45
49
  ) -> list[dict[str, object]]:
46
- meta = None
47
- if experiment.meta is not None:
48
- meta = await invocation_meta(config.root, experiment.meta)
50
+ inherited = dict(os.environ) if environment is None else environment
49
51
  tables = open_tables(config)
50
52
  existing = await experiment_run_ids(tables, name)
51
53
  finished: list[dict[str, object]] = []
@@ -79,14 +81,35 @@ async def run_experiment(
79
81
  run = new_id(params)
80
82
  planned.add(run)
81
83
  plan.append((run, params))
82
- match plan:
83
- case [(invocation, _)]:
84
- pass
85
- case _:
86
- invocation = group or new_stamped_run_id(taken)
87
- planned.add(invocation)
88
- hooks = {"setup": experiment.setup, "teardown": experiment.teardown}
89
- ids = hook_ids(hooks, invocation, [run for run, _ in plan])
84
+ run_dirs = [
85
+ str(run_folder(config.root, config.runs, name, run).resolve())
86
+ for run, _ in plan
87
+ ]
88
+ group_dir = (
89
+ str(run_folder(config.root, config.runs, name, group).resolve())
90
+ if group is not None
91
+ else None
92
+ )
93
+ hooks = {
94
+ "setup": experiment.setup,
95
+ "teardown": experiment.teardown,
96
+ "meta": experiment.meta,
97
+ }
98
+ dirs = hook_dirs(hooks, group_dir, run_dirs)
99
+ hook_env = {
100
+ hook: fill_experiment_env(
101
+ experiment.env, inherited, dirs, hook=hook, runs=len(plan)
102
+ )
103
+ for hook, command in hooks.items()
104
+ if command is not None
105
+ }
106
+ meta = None
107
+ if experiment.meta is not None:
108
+ meta = await invocation_meta(
109
+ config.root, fill_dirs(experiment.meta, dirs), hook_env["meta"]
110
+ )
111
+ if dirs:
112
+ Path(dirs["invocation_dir"]).mkdir(parents=True, exist_ok=True)
90
113
  runs = [{"run": run, "params": params} for run, params in plan]
91
114
  report(
92
115
  {
@@ -215,14 +238,21 @@ async def run_experiment(
215
238
 
216
239
  async def start(run: str, params: dict[str, object]) -> None:
217
240
  folder = run_folder(config.root, config.runs, name, run)
218
- folder.mkdir(parents=True)
241
+ folder.mkdir(parents=True, exist_ok=True)
242
+ run_invocation_dir = group_dir or str(folder.resolve())
219
243
  report({"event": "started", "run": run, "params": params, "folder": folder})
220
244
  running[run] = set()
221
245
  trees[run] = set()
222
246
  result = await execute_run(
223
247
  config.root,
224
248
  folder,
225
- fill_ids(experiment.command, {"run_id": run, "invocation_id": invocation}),
249
+ fill_dirs(
250
+ experiment.command,
251
+ {
252
+ "run_dir": str(folder.resolve()),
253
+ "invocation_dir": run_invocation_dir,
254
+ },
255
+ ),
226
256
  params,
227
257
  running[run],
228
258
  stopping,
@@ -234,6 +264,14 @@ async def run_experiment(
234
264
  meta=meta,
235
265
  args=args or (),
236
266
  memory_interval=experiment.memory_interval,
267
+ environment=fill_experiment_env(
268
+ experiment.env,
269
+ inherited,
270
+ {
271
+ "run_dir": str(folder.resolve()),
272
+ "invocation_dir": run_invocation_dir,
273
+ },
274
+ ),
237
275
  )
238
276
  del running[run], trees[run]
239
277
  report({"event": "ended", "run": run, **result})
@@ -248,7 +286,10 @@ async def run_experiment(
248
286
  if experiment.teardown is not None:
249
287
  report({"event": "tearing down"})
250
288
  await invocation_hook(
251
- config.root, "teardown", fill_ids(experiment.teardown, ids)
289
+ config.root,
290
+ "teardown",
291
+ fill_dirs(experiment.teardown, dirs),
292
+ hook_env["teardown"],
252
293
  )
253
294
 
254
295
  listen(Controls(interrupt, set_concurrency, kill, finish))
@@ -256,7 +297,12 @@ async def run_experiment(
256
297
  try:
257
298
  if experiment.setup is not None:
258
299
  report({"event": "setting up"})
259
- await invocation_hook(config.root, "setup", fill_ids(experiment.setup, ids))
300
+ await invocation_hook(
301
+ config.root,
302
+ "setup",
303
+ fill_dirs(experiment.setup, dirs),
304
+ hook_env["setup"],
305
+ )
260
306
  fill()
261
307
  await done.wait()
262
308
  await asyncio.gather(*started)
@@ -1,8 +0,0 @@
1
- def compose_experiment(
2
- base: dict[str, object], layer: dict[str, object]
3
- ) -> dict[str, object]:
4
- composed = {**base, **layer}
5
- base_params, layer_params = base.get("params"), layer.get("params")
6
- if isinstance(base_params, dict) and isinstance(layer_params, dict):
7
- composed["params"] = {**base_params, **layer_params}
8
- return composed
@@ -1,8 +0,0 @@
1
- import shlex
2
- from collections.abc import Mapping
3
-
4
-
5
- def fill_ids(command: str, ids: Mapping[str, str]) -> str:
6
- for name, value in ids.items():
7
- command = command.replace(f"{{{name}}}", shlex.quote(value))
8
- return command
@@ -1,14 +0,0 @@
1
- def hook_ids(
2
- hooks: dict[str, str | None], invocation: str, runs: list[str]
3
- ) -> dict[str, str]:
4
- match runs:
5
- case [run]:
6
- return {"run_id": run, "invocation_id": invocation}
7
- for hook, command in hooks.items():
8
- if command is not None and "{run_id}" in command:
9
- raise ValueError(
10
- f"{hook} command {command} uses {{run_id}}, but this invocation plans "
11
- f"{len(runs)} runs. {{run_id}} needs exactly one; "
12
- "use {invocation_id} instead."
13
- )
14
- return {"invocation_id": invocation}
File without changes
File without changes
File without changes
File without changes
File without changes