labnotes 0.2.15__tar.gz → 0.2.16__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 (226) hide show
  1. {labnotes-0.2.15 → labnotes-0.2.16}/PKG-INFO +1 -1
  2. labnotes-0.2.16/conftest.py +9 -0
  3. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/run_experiment.py +27 -18
  4. labnotes-0.2.16/src/labnotes/runner/run_state.py +62 -0
  5. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/start_experiment.py +18 -2
  6. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/run_app.py +26 -4
  7. {labnotes-0.2.15 → labnotes-0.2.16}/.gitignore +0 -0
  8. {labnotes-0.2.15 → labnotes-0.2.16}/CHANGELOG.md +0 -0
  9. {labnotes-0.2.15 → labnotes-0.2.16}/MIGRATIONS.md +0 -0
  10. {labnotes-0.2.15 → labnotes-0.2.16}/README.md +0 -0
  11. {labnotes-0.2.15 → labnotes-0.2.16}/pyproject.toml +0 -0
  12. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/__init__.py +0 -0
  13. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/__main__.py +0 -0
  14. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/asset_url.py +0 -0
  15. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/check_counts.py +0 -0
  16. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/cli.py +0 -0
  17. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/command_line_group.py +0 -0
  18. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/compose_experiment.py +0 -0
  19. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/config_migration.py +0 -0
  20. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/context.md +0 -0
  21. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/context_guide.py +0 -0
  22. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/copy_experiment.py +0 -0
  23. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/count_groups.py +0 -0
  24. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/count_runs.py +0 -0
  25. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/create_app.py +0 -0
  26. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/create_experiment.py +0 -0
  27. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/create_note.py +0 -0
  28. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/data_html.py +0 -0
  29. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/default_config_text.py +0 -0
  30. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/default_counts.py +0 -0
  31. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/drain_watch.py +0 -0
  32. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/experiment_definition.py +0 -0
  33. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/experiment_notes.py +0 -0
  34. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/experiment_runs.py +0 -0
  35. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/experiments.py +0 -0
  36. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/file_changes.py +0 -0
  37. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/format_duration.py +0 -0
  38. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/format_started.py +0 -0
  39. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/format_value.py +0 -0
  40. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/get_state.py +0 -0
  41. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/grid_cell.py +0 -0
  42. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/group_runs.py +0 -0
  43. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/init.py +0 -0
  44. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/invocation_started.py +0 -0
  45. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/is_non_negative_int.py +0 -0
  46. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/is_non_negative_number.py +0 -0
  47. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/is_positive_int.py +0 -0
  48. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/latest_log.py +0 -0
  49. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/list_experiments.py +0 -0
  50. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/list_runs.py +0 -0
  51. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/load_config.py +0 -0
  52. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/load_experiment.py +0 -0
  53. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/load_run.py +0 -0
  54. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/log.py +0 -0
  55. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/log_path.py +0 -0
  56. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/memory_interval.py +0 -0
  57. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/models.py +0 -0
  58. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/nests_runs.py +0 -0
  59. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/note_path.py +0 -0
  60. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/notebook_kernel.py +0 -0
  61. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/output_html.py +0 -0
  62. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/param_names.py +0 -0
  63. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/parse_param_value.py +0 -0
  64. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/parse_params.py +0 -0
  65. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/parse_run_view.py +0 -0
  66. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/parse_subset.py +0 -0
  67. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/pivot_runs.py +0 -0
  68. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/print_rows.py +0 -0
  69. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/python_blocks.py +0 -0
  70. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/read_config.py +0 -0
  71. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/read_path.py +0 -0
  72. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/render_markdown.py +0 -0
  73. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/retries.py +0 -0
  74. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/run_columns.py +0 -0
  75. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/run_output.py +0 -0
  76. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/__init__.py +0 -0
  77. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/cgroup_memory.py +0 -0
  78. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/check_args.py +0 -0
  79. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/check_hook_placeholders.py +0 -0
  80. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/check_subset.py +0 -0
  81. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/claim_run.py +0 -0
  82. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/command_flags.py +0 -0
  83. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/container_cgroup.py +0 -0
  84. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/container_memory.py +0 -0
  85. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/continue_run.py +0 -0
  86. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/describe_event.py +0 -0
  87. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/docker_output.py +0 -0
  88. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/execute_run.py +0 -0
  89. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/exit_signal.py +0 -0
  90. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/fill_dirs.py +0 -0
  91. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/fill_experiment_env.py +0 -0
  92. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/finalize_lost_runs.py +0 -0
  93. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/first_invocation.py +0 -0
  94. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/flag_name.py +0 -0
  95. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/group_args.py +0 -0
  96. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/hold_experiment.py +0 -0
  97. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/in_subset.py +0 -0
  98. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/invocation_hook.py +0 -0
  99. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/invocation_meta.py +0 -0
  100. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/new_run_id.py +0 -0
  101. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  102. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/override_flags.py +0 -0
  103. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/param_args.py +0 -0
  104. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/param_grid.py +0 -0
  105. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/placeholders.py +0 -0
  106. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/plan_runs.py +0 -0
  107. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/prepare_experiment.py +0 -0
  108. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/project_snapshot.py +0 -0
  109. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/read_experiment_config.py +0 -0
  110. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/record_memory.py +0 -0
  111. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/run_folder.py +0 -0
  112. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/run_git.py +0 -0
  113. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/run_headless.py +0 -0
  114. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/run_locked.py +0 -0
  115. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/runs_parent.py +0 -0
  116. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/shell_command.py +0 -0
  117. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/signal_tree.py +0 -0
  118. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/stop_then_kill.py +0 -0
  119. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/untracked_diff.py +0 -0
  120. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/wait_for_tree.py +0 -0
  121. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runner/write_run_json.py +0 -0
  122. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/runs_view_command.py +0 -0
  123. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/save_conflict_copy.py +0 -0
  124. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/save_note.py +0 -0
  125. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/saved_outputs.py +0 -0
  126. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/serve_app.py +0 -0
  127. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/server.py +0 -0
  128. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/set_state.py +0 -0
  129. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/shared_params.py +0 -0
  130. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/sort_href.py +0 -0
  131. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/sql_quote.py +0 -0
  132. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/state_path.py +0 -0
  133. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/elapsed.js +0 -0
  134. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/experiment.js +0 -0
  135. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/labnotes.css +0 -0
  136. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/local-time.js +0 -0
  137. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/new-note.js +0 -0
  138. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/note-edit.js +0 -0
  139. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/note-editor.js +0 -0
  140. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/row-link.js +0 -0
  141. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/run-cell.js +0 -0
  142. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/run-output.js +0 -0
  143. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/static/view-toggle.js +0 -0
  144. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/summarize_config.py +0 -0
  145. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/__init__.py +0 -0
  146. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/experiment_rows.py +0 -0
  147. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/experiment_run_ids.py +0 -0
  148. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/experiment_toml.py +0 -0
  149. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/experiments_ddl.py +0 -0
  150. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/file_prefix.py +0 -0
  151. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/file_text.py +0 -0
  152. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/iso_time.py +0 -0
  153. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/list_experiment_runs.py +0 -0
  154. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/list_resumable_runs.py +0 -0
  155. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/note_text.py +0 -0
  156. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/open_tables.py +0 -0
  157. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/path_glob.py +0 -0
  158. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/path_pattern.py +0 -0
  159. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_blocks.py +0 -0
  160. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_experiment.py +0 -0
  161. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_file.py +0 -0
  162. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_log.py +0 -0
  163. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_memory.py +0 -0
  164. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_run.py +0 -0
  165. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/read_run_file.py +0 -0
  166. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/runs_columns.py +0 -0
  167. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tables/summarize_experiments.py +0 -0
  168. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/base.html +0 -0
  169. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/editor_imports.html +0 -0
  170. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/experiment.html +0 -0
  171. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/index.html +0 -0
  172. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/note.html +0 -0
  173. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/templates/run.html +0 -0
  174. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/__init__.py +0 -0
  175. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/browse_app.py +0 -0
  176. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/browse_tui.py +0 -0
  177. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/check_view_response.py +0 -0
  178. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/config.py +0 -0
  179. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/dim.py +0 -0
  180. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/experiment_screen.py +0 -0
  181. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/experiments_screen.py +0 -0
  182. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/fill_table.py +0 -0
  183. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/format_cell.py +0 -0
  184. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/format_elapsed.py +0 -0
  185. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/guard_view.py +0 -0
  186. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/leave_prompt.py +0 -0
  187. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/load_view.py +0 -0
  188. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/log_block.py +0 -0
  189. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/log_blocks.py +0 -0
  190. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/native_rows.py +0 -0
  191. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/reload_on_change.py +0 -0
  192. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/run_cells.py +0 -0
  193. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/run_output.py +0 -0
  194. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/run_panel.py +0 -0
  195. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/run_tui.py +0 -0
  196. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/runs_browser.py +0 -0
  197. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/runs_table.py +0 -0
  198. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/runs_view.py +0 -0
  199. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/stopped_title.py +0 -0
  200. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/table_columns.py +0 -0
  201. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/table_rows.py +0 -0
  202. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/view_column.py +0 -0
  203. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/view_pane.py +0 -0
  204. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/view_process.py +0 -0
  205. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/tui/view_switcher.py +0 -0
  206. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/__init__.py +0 -0
  207. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/format_bytes.py +0 -0
  208. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/process_alive.py +0 -0
  209. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/run_usage.py +0 -0
  210. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/tree_rss.py +0 -0
  211. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/utils/tree_usage.py +0 -0
  212. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/__init__.py +0 -0
  213. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/answer.py +0 -0
  214. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/build.py +0 -0
  215. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/changed.py +0 -0
  216. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/experiment.py +0 -0
  217. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/respond.py +0 -0
  218. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view/serve.py +0 -0
  219. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/view_specs.py +0 -0
  220. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/wait_for_note.py +0 -0
  221. {labnotes-0.2.15 → labnotes-0.2.16}/src/labnotes/watch_rows.py +0 -0
  222. {labnotes-0.2.15 → labnotes-0.2.16}/testing-conventions.toml +0 -0
  223. {labnotes-0.2.15 → labnotes-0.2.16}/tests/conftest.py +0 -0
  224. {labnotes-0.2.15 → labnotes-0.2.16}/tests/e2e/conftest.py +0 -0
  225. {labnotes-0.2.15 → labnotes-0.2.16}/tests/integration/conftest.py +0 -0
  226. {labnotes-0.2.15 → labnotes-0.2.16}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.2.15
3
+ Version: 0.2.16
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
+ import pytest
2
+
3
+
4
+ @pytest.fixture(autouse=True)
5
+ def state_home(
6
+ tmp_path_factory: pytest.TempPathFactory, monkeypatch: pytest.MonkeyPatch
7
+ ) -> None:
8
+ # Run journals live under XDG_STATE_HOME; keep tests out of the real one.
9
+ monkeypatch.setenv("XDG_STATE_HOME", str(tmp_path_factory.mktemp("state")))
@@ -30,6 +30,7 @@ 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
32
  from labnotes.runner.run_locked import run_locked
33
+ from labnotes.runner.run_state import RunState
33
34
  from labnotes.runner.signal_tree import signal_tree
34
35
  from labnotes.state_path import STATE_ENV
35
36
  from labnotes.tables.experiment_run_ids import experiment_run_ids
@@ -55,7 +56,15 @@ async def run_experiment(
55
56
  snapshot: ProjectSnapshot | None = None,
56
57
  command_line: str | None = None,
57
58
  run_id: str | None = None,
59
+ run_state: RunState | None = None,
58
60
  ) -> list[dict[str, object]]:
61
+ state = run_state or RunState.create(config, name)
62
+ forward = report
63
+
64
+ def emit(event: dict[str, object]) -> None:
65
+ state.record(event)
66
+ forward(event)
67
+
59
68
  command_root = snapshot.root if snapshot is not None else config.root
60
69
  inherited = dict(os.environ) if environment is None else environment
61
70
  tables = open_tables(config)
@@ -161,7 +170,7 @@ async def run_experiment(
161
170
  if experiment.meta is not None:
162
171
  meta = await invocation_meta(command_root, experiment.meta, hook_env["meta"])
163
172
  runs = [{"run": run, "params": params} for run, params in plan]
164
- report(
173
+ emit(
165
174
  {
166
175
  "event": "planned",
167
176
  "title": experiment.name or name,
@@ -170,7 +179,7 @@ async def run_experiment(
170
179
  }
171
180
  )
172
181
  for row in held:
173
- report({"event": "held", "run": str(row["id"])})
182
+ emit({"event": "held", "run": str(row["id"])})
174
183
  running: dict[str, set[asyncio.subprocess.Process]] = {}
175
184
  trees: dict[str, set[psutil.Process]] = {}
176
185
  stopped: set[str] = set()
@@ -189,14 +198,14 @@ async def run_experiment(
189
198
  plan.clear()
190
199
  if not stopping.is_set():
191
200
  stopping.set()
192
- report({"event": "stopping"})
201
+ emit({"event": "stopping"})
193
202
  signal_runs(signal.SIGTERM)
194
203
  if waiting:
195
204
  for run in list(waiting):
196
205
  cancel_retry(run)
197
206
  fill()
198
207
  elif any(running.values()):
199
- report({"event": "killing"})
208
+ emit({"event": "killing"})
200
209
  signal_runs(signal.SIGKILL)
201
210
 
202
211
  def kill(run: str) -> None:
@@ -207,11 +216,11 @@ async def run_experiment(
207
216
  if not running.get(run):
208
217
  return
209
218
  if run in stopped:
210
- report({"event": "killing run", "run": run})
219
+ emit({"event": "killing run", "run": run})
211
220
  signal_run(run, signal.SIGKILL)
212
221
  else:
213
222
  stopped.add(run)
214
- report({"event": "stopping run", "run": run})
223
+ emit({"event": "stopping run", "run": run})
215
224
  signal_run(run, signal.SIGTERM)
216
225
 
217
226
  ended: dict[str, dict[str, object]] = {}
@@ -227,13 +236,13 @@ async def run_experiment(
227
236
  parents[again] = run
228
237
  attempts[again] = attempt
229
238
  plan.append((again, ended[run]))
230
- report({"event": "retried", "run": run, "retry": again, "params": ended[run]})
239
+ emit({"event": "retried", "run": run, "retry": again, "params": ended[run]})
231
240
  fill()
232
241
 
233
242
  def retry_later(run: str) -> None:
234
243
  tried = attempts.get(run, 0)
235
244
  if tried >= experiment.retries:
236
- report({"event": "gave up", "run": run, "retries": tried})
245
+ emit({"event": "gave up", "run": run, "retries": tried})
237
246
  return
238
247
  seconds = retry_seconds * 2**tried
239
248
 
@@ -242,7 +251,7 @@ async def run_experiment(
242
251
  requeue(run, tried + 1)
243
252
 
244
253
  waiting[run] = asyncio.get_running_loop().call_later(seconds, due)
245
- report(
254
+ emit(
246
255
  {
247
256
  "event": "retrying",
248
257
  "run": run,
@@ -255,7 +264,7 @@ async def run_experiment(
255
264
  def cancel_retry(run: str) -> None:
256
265
  waiting.pop(run).cancel()
257
266
  release(run)
258
- report({"event": "retry cancelled", "run": run})
267
+ emit({"event": "retry cancelled", "run": run})
259
268
 
260
269
  limit = concurrency
261
270
  live: set[asyncio.Task[None]] = set()
@@ -270,7 +279,7 @@ async def run_experiment(
270
279
  live.add(task)
271
280
  started.append(task)
272
281
  if not (live or waiting):
273
- report({"event": "idle"})
282
+ emit({"event": "idle"})
274
283
  if finishing:
275
284
  done.set()
276
285
 
@@ -287,19 +296,19 @@ async def run_experiment(
287
296
  def set_concurrency(count: int) -> None:
288
297
  nonlocal limit
289
298
  limit = max(1, count)
290
- report({"event": "concurrency", "concurrency": limit})
299
+ emit({"event": "concurrency", "concurrency": limit})
291
300
  fill()
292
301
 
293
302
  async def start(run: str, params: dict[str, object]) -> None:
294
303
  folder = target_folder or run_folder(config.root, config.runs, name, run)
295
304
  folder.mkdir(parents=True, exist_ok=True)
296
305
  if not claim(run):
297
- report({"event": "held", "run": run})
306
+ emit({"event": "held", "run": run})
298
307
  return
299
308
  if snapshot is not None and snapshot.sha is not None:
300
309
  (folder / "git-sha.txt").write_text(snapshot.sha + "\n")
301
310
  (folder / "git-diff.patch").write_bytes(snapshot.diff or b"")
302
- report({"event": "started", "run": run, "params": params, "folder": folder})
311
+ emit({"event": "started", "run": run, "params": params, "folder": folder})
303
312
  # {invocation_dir} stays for configs written before runs were flat.
304
313
  dirs = dict.fromkeys(PLACEHOLDERS, str(folder.resolve()))
305
314
  running[run] = set()
@@ -337,7 +346,7 @@ async def run_experiment(
337
346
  command_line=command_line,
338
347
  )
339
348
  del running[run], trees[run]
340
- report({"event": "ended", "run": run, **result})
349
+ emit({"event": "ended", "run": run, **result})
341
350
  ended[run] = params
342
351
  results[run] = {"run": run, **result}
343
352
  if result["exit_code"] == RETRY_EXIT_CODE and not (
@@ -349,7 +358,7 @@ async def run_experiment(
349
358
 
350
359
  async def teardown() -> None:
351
360
  if experiment.teardown is not None:
352
- report({"event": "tearing down"})
361
+ emit({"event": "tearing down"})
353
362
  await invocation_hook(
354
363
  command_root,
355
364
  "teardown",
@@ -366,7 +375,7 @@ async def run_experiment(
366
375
  # Like vitest's globalSetup: a failed setup still gets its teardown.
367
376
  try:
368
377
  if experiment.setup is not None:
369
- report({"event": "setting up"})
378
+ emit({"event": "setting up"})
370
379
  await invocation_hook(
371
380
  command_root,
372
381
  "setup",
@@ -380,7 +389,7 @@ async def run_experiment(
380
389
  try:
381
390
  await teardown()
382
391
  except Exception as failure: # noqa: BLE001
383
- report({"event": "teardown failed", "error": str(failure)})
392
+ emit({"event": "teardown failed", "error": str(failure)})
384
393
  raise
385
394
  finally:
386
395
  for run in list(locks):
@@ -0,0 +1,62 @@
1
+ """Persist one invocation's run TUI events in the user's state folder."""
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import time
7
+ from pathlib import Path
8
+ from uuid import uuid4
9
+
10
+ from labnotes.models import LabnotesConfig
11
+ from labnotes.runner.run_folder import run_folder
12
+
13
+
14
+ def journal_folder(runs: Path) -> Path:
15
+ """Keep journals out of the runs folder, where scripts expect only runs."""
16
+ home = os.environ.get("XDG_STATE_HOME") or Path.home() / ".local" / "state"
17
+ key = hashlib.sha256(str(runs.resolve()).encode()).hexdigest()[:16]
18
+ return Path(home) / "labnotes" / "run-state" / key
19
+
20
+
21
+ class RunState:
22
+ """Append complete events and replay them from a disk cursor."""
23
+
24
+ def __init__(self, path: Path) -> None:
25
+ self.path = path
26
+ self.offset = 0
27
+
28
+ @classmethod
29
+ def create(cls, config: LabnotesConfig, experiment: str) -> "RunState":
30
+ """Choose a journal unique to this invocation without touching disk yet."""
31
+ runs = run_folder(config.root, config.runs, experiment, "_").parent
32
+ stamp = time.strftime("%Y%m%d-%H%M%S", time.gmtime())
33
+ return cls(
34
+ journal_folder(runs) / f"{stamp}-{os.getpid()}-{uuid4().hex[:8]}.jsonl"
35
+ )
36
+
37
+ def record(self, event: dict[str, object]) -> None:
38
+ """Write one event before callers display it."""
39
+ at = time.time()
40
+ stored = {**event, "at": at}
41
+ if event["event"] == "retrying":
42
+ stored["retry_at"] = at + float(str(event["seconds"]))
43
+ data = (json.dumps(stored, default=str) + "\n").encode()
44
+ self.path.parent.mkdir(parents=True, exist_ok=True)
45
+ self.path.touch(mode=0o600, exist_ok=True)
46
+ with self.path.open("ab") as file:
47
+ file.write(data)
48
+
49
+ def read(self) -> list[dict[str, object]]:
50
+ """Return only complete new events."""
51
+ try:
52
+ with self.path.open("rb") as file:
53
+ file.seek(self.offset)
54
+ data = file.read()
55
+ except FileNotFoundError:
56
+ return []
57
+ try:
58
+ end = data.rindex(b"\n") + 1
59
+ except ValueError:
60
+ return []
61
+ self.offset += end
62
+ return [json.loads(line) for line in data[:end].splitlines()]
@@ -1,3 +1,4 @@
1
+ from dataclasses import dataclass
1
2
  from pathlib import Path
2
3
 
3
4
  from labnotes.models import LabnotesConfig, Listen, Report, Start
@@ -5,6 +6,18 @@ from labnotes.runner.finalize_lost_runs import finalize_lost_runs
5
6
  from labnotes.runner.prepare_experiment import prepare_experiment
6
7
  from labnotes.runner.project_snapshot import project_snapshot
7
8
  from labnotes.runner.run_experiment import run_experiment
9
+ from labnotes.runner.run_state import RunState
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class ExperimentStart:
14
+ """Keep a run invocation's journal path with its scheduler callback."""
15
+
16
+ run_state: RunState
17
+ start: Start
18
+
19
+ async def __call__(self, report: Report, listen: Listen) -> list[dict[str, object]]:
20
+ return await self.start(report, listen)
8
21
 
9
22
 
10
23
  def start_experiment(
@@ -19,7 +32,9 @@ def start_experiment(
19
32
  teardown: str | None,
20
33
  command_line: str | None = None,
21
34
  run_id: str | None = None,
22
- ) -> Start:
35
+ ) -> ExperimentStart:
36
+ state = RunState.create(config, name)
37
+
23
38
  async def start(report: Report, listen: Listen) -> list[dict[str, object]]:
24
39
  experiment, limit = await prepare_experiment(
25
40
  config, name, experiment_config, concurrency, only, args, setup, teardown
@@ -39,6 +54,7 @@ def start_experiment(
39
54
  snapshot=snapshot,
40
55
  command_line=command_line,
41
56
  run_id=run_id,
57
+ run_state=state,
42
58
  )
43
59
 
44
- return start
60
+ return ExperimentStart(state, start)
@@ -21,6 +21,7 @@ from labnotes.models import Controls, LabnotesConfig, Start
21
21
  from labnotes.retries import RETRY_EXIT_CODE
22
22
  from labnotes.runner.describe_event import describe_event
23
23
  from labnotes.runner.param_args import param_args
24
+ from labnotes.runner.start_experiment import ExperimentStart
24
25
  from labnotes.runner.stop_then_kill import GRACE_SECONDS, stop_then_kill
25
26
  from labnotes.tables.open_tables import open_tables
26
27
  from labnotes.tui.config import RUN_COLUMNS
@@ -119,7 +120,11 @@ class RunLiveView(Widget):
119
120
  case "started":
120
121
  self.idle = False
121
122
  self.pending.discard(run)
122
- self.started[run] = time.monotonic()
123
+ self.started[run] = (
124
+ time.monotonic() - max(0.0, time.time() - float(str(event["at"])))
125
+ if "at" in event
126
+ else time.monotonic()
127
+ )
123
128
  self.folders[run] = Path(str(event["folder"]))
124
129
  args = " ".join(param_args(event["params"])) # type: ignore[arg-type]
125
130
  shown = {
@@ -146,7 +151,11 @@ class RunLiveView(Widget):
146
151
  self.unmeasured[run] = None
147
152
  self.measure()
148
153
  case "retrying":
149
- self.waiting[run] = time.monotonic() + float(event["seconds"]) # type: ignore[arg-type]
154
+ self.waiting[run] = time.monotonic() + (
155
+ max(0.0, float(str(event["retry_at"])) - time.time())
156
+ if "retry_at" in event
157
+ else float(str(event["seconds"]))
158
+ )
150
159
  self.tick()
151
160
  self.show(describe_event(event))
152
161
  case "gave up":
@@ -368,6 +377,8 @@ class RunApp(ViewSwitcher, App[None]):
368
377
  self.shown: Experiment | None = None
369
378
  self.left: dict[str, Widget] = {}
370
379
  self.controls = self.body.controls
380
+ self.run_state = start.run_state if isinstance(start, ExperimentStart) else None
381
+ self.watching_state = False
371
382
 
372
383
  def compose(self) -> ComposeResult:
373
384
  yield Header()
@@ -456,8 +467,19 @@ class RunApp(ViewSwitcher, App[None]):
456
467
  stop_then_kill(self.stop_runs, signal.SIGHUP)
457
468
 
458
469
  def show_event(self, event: dict[str, object]) -> None:
459
- if self.body_ready:
460
- self.body.show_event(event)
470
+ if self.run_state is None:
471
+ if self.body_ready:
472
+ self.body.show_event(event)
473
+ return
474
+ if not self.watching_state:
475
+ self.watching_state = True
476
+ self.set_interval(0.25, self.refresh_state)
477
+ self.refresh_state()
478
+
479
+ def refresh_state(self) -> None:
480
+ if self.run_state is not None and self.body_ready:
481
+ for event in self.run_state.read():
482
+ self.body.show_event(event)
461
483
 
462
484
  def stop_runs(self) -> None:
463
485
  if self.body_ready and self.body.idle:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes