labnotes 0.2.17__tar.gz → 0.2.18__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 (234) hide show
  1. {labnotes-0.2.17 → labnotes-0.2.18}/PKG-INFO +1 -1
  2. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/experiments.py +50 -15
  3. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/leave_prompt.py +18 -0
  4. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/run_app.py +129 -8
  5. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/run_tui.py +9 -2
  6. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/runs_browser.py +14 -20
  7. {labnotes-0.2.17 → labnotes-0.2.18}/.gitignore +0 -0
  8. {labnotes-0.2.17 → labnotes-0.2.18}/CHANGELOG.md +0 -0
  9. {labnotes-0.2.17 → labnotes-0.2.18}/MIGRATIONS.md +0 -0
  10. {labnotes-0.2.17 → labnotes-0.2.18}/README.md +0 -0
  11. {labnotes-0.2.17 → labnotes-0.2.18}/conftest.py +0 -0
  12. {labnotes-0.2.17 → labnotes-0.2.18}/pyproject.toml +0 -0
  13. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/__init__.py +0 -0
  14. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/__main__.py +0 -0
  15. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/asset_url.py +0 -0
  16. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/check_counts.py +0 -0
  17. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/cli.py +0 -0
  18. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/command_line_group.py +0 -0
  19. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/compose_experiment.py +0 -0
  20. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/config_migration.py +0 -0
  21. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/context.md +0 -0
  22. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/context_guide.py +0 -0
  23. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/copy_experiment.py +0 -0
  24. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/count_groups.py +0 -0
  25. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/count_runs.py +0 -0
  26. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/create_app.py +0 -0
  27. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/create_experiment.py +0 -0
  28. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/create_note.py +0 -0
  29. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/daemon/__init__.py +0 -0
  30. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/daemon/client.py +0 -0
  31. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/daemon/command.py +0 -0
  32. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/daemon/ensure.py +0 -0
  33. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/daemon/marker.py +0 -0
  34. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/daemon/server.py +0 -0
  35. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/daemon/socket_path.py +0 -0
  36. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/daemon_command.py +0 -0
  37. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/data_html.py +0 -0
  38. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/default_config_text.py +0 -0
  39. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/default_counts.py +0 -0
  40. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/drain_watch.py +0 -0
  41. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/experiment_definition.py +0 -0
  42. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/experiment_notes.py +0 -0
  43. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/experiment_runs.py +0 -0
  44. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/file_changes.py +0 -0
  45. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/format_duration.py +0 -0
  46. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/format_started.py +0 -0
  47. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/format_value.py +0 -0
  48. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/get_state.py +0 -0
  49. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/grid_cell.py +0 -0
  50. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/group_runs.py +0 -0
  51. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/init.py +0 -0
  52. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/invocation_started.py +0 -0
  53. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/is_non_negative_int.py +0 -0
  54. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/is_non_negative_number.py +0 -0
  55. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/is_positive_int.py +0 -0
  56. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/latest_log.py +0 -0
  57. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/list_experiments.py +0 -0
  58. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/list_runs.py +0 -0
  59. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/load_config.py +0 -0
  60. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/load_experiment.py +0 -0
  61. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/load_run.py +0 -0
  62. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/log.py +0 -0
  63. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/log_path.py +0 -0
  64. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/memory_interval.py +0 -0
  65. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/models.py +0 -0
  66. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/nests_runs.py +0 -0
  67. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/note_path.py +0 -0
  68. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/notebook_kernel.py +0 -0
  69. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/output_html.py +0 -0
  70. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/param_names.py +0 -0
  71. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/parse_param_value.py +0 -0
  72. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/parse_params.py +0 -0
  73. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/parse_run_view.py +0 -0
  74. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/parse_subset.py +0 -0
  75. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/pivot_runs.py +0 -0
  76. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/print_rows.py +0 -0
  77. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/python_blocks.py +0 -0
  78. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/read_config.py +0 -0
  79. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/read_path.py +0 -0
  80. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/render_markdown.py +0 -0
  81. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/retries.py +0 -0
  82. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/run_columns.py +0 -0
  83. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/run_output.py +0 -0
  84. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/__init__.py +0 -0
  85. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/cgroup_memory.py +0 -0
  86. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/check_args.py +0 -0
  87. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/check_hook_placeholders.py +0 -0
  88. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/check_subset.py +0 -0
  89. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/claim_run.py +0 -0
  90. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/command_flags.py +0 -0
  91. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/container_cgroup.py +0 -0
  92. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/container_memory.py +0 -0
  93. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/continue_run.py +0 -0
  94. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/describe_event.py +0 -0
  95. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/docker_output.py +0 -0
  96. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/execute_run.py +0 -0
  97. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/exit_signal.py +0 -0
  98. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/fill_dirs.py +0 -0
  99. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/fill_experiment_env.py +0 -0
  100. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/finalize_lost_runs.py +0 -0
  101. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/first_invocation.py +0 -0
  102. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/flag_name.py +0 -0
  103. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/group_args.py +0 -0
  104. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/hold_experiment.py +0 -0
  105. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/in_subset.py +0 -0
  106. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/invocation_hook.py +0 -0
  107. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/invocation_meta.py +0 -0
  108. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/new_run_id.py +0 -0
  109. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  110. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/override_flags.py +0 -0
  111. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/param_args.py +0 -0
  112. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/param_grid.py +0 -0
  113. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/placeholders.py +0 -0
  114. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/plan_runs.py +0 -0
  115. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/prepare_experiment.py +0 -0
  116. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/project_snapshot.py +0 -0
  117. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/read_experiment_config.py +0 -0
  118. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/record_memory.py +0 -0
  119. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/run_experiment.py +0 -0
  120. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/run_folder.py +0 -0
  121. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/run_git.py +0 -0
  122. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/run_headless.py +0 -0
  123. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/run_locked.py +0 -0
  124. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/run_state.py +0 -0
  125. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/runs_parent.py +0 -0
  126. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/shell_command.py +0 -0
  127. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/signal_tree.py +0 -0
  128. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/start_experiment.py +0 -0
  129. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/stop_then_kill.py +0 -0
  130. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/untracked_diff.py +0 -0
  131. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/wait_for_tree.py +0 -0
  132. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runner/write_run_json.py +0 -0
  133. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/runs_view_command.py +0 -0
  134. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/save_conflict_copy.py +0 -0
  135. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/save_note.py +0 -0
  136. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/saved_outputs.py +0 -0
  137. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/serve_app.py +0 -0
  138. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/server.py +0 -0
  139. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/set_state.py +0 -0
  140. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/shared_params.py +0 -0
  141. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/sort_href.py +0 -0
  142. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/sql_quote.py +0 -0
  143. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/state_path.py +0 -0
  144. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/static/elapsed.js +0 -0
  145. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/static/experiment.js +0 -0
  146. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/static/labnotes.css +0 -0
  147. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/static/local-time.js +0 -0
  148. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/static/new-note.js +0 -0
  149. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/static/note-edit.js +0 -0
  150. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/static/note-editor.js +0 -0
  151. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/static/row-link.js +0 -0
  152. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/static/run-cell.js +0 -0
  153. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/static/run-output.js +0 -0
  154. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/static/view-toggle.js +0 -0
  155. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/summarize_config.py +0 -0
  156. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/__init__.py +0 -0
  157. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/experiment_rows.py +0 -0
  158. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/experiment_run_ids.py +0 -0
  159. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/experiment_toml.py +0 -0
  160. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/experiments_ddl.py +0 -0
  161. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/file_prefix.py +0 -0
  162. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/file_text.py +0 -0
  163. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/iso_time.py +0 -0
  164. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/list_experiment_runs.py +0 -0
  165. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/list_resumable_runs.py +0 -0
  166. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/note_text.py +0 -0
  167. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/open_tables.py +0 -0
  168. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/path_glob.py +0 -0
  169. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/path_pattern.py +0 -0
  170. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/read_blocks.py +0 -0
  171. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/read_experiment.py +0 -0
  172. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/read_file.py +0 -0
  173. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/read_log.py +0 -0
  174. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/read_memory.py +0 -0
  175. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/read_run.py +0 -0
  176. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/read_run_file.py +0 -0
  177. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/runs_columns.py +0 -0
  178. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tables/summarize_experiments.py +0 -0
  179. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/templates/base.html +0 -0
  180. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/templates/editor_imports.html +0 -0
  181. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/templates/experiment.html +0 -0
  182. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/templates/index.html +0 -0
  183. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/templates/note.html +0 -0
  184. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/templates/run.html +0 -0
  185. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/__init__.py +0 -0
  186. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/browse_app.py +0 -0
  187. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/browse_tui.py +0 -0
  188. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/check_view_response.py +0 -0
  189. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/config.py +0 -0
  190. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/dim.py +0 -0
  191. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/experiment_screen.py +0 -0
  192. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/experiments_screen.py +0 -0
  193. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/fill_table.py +0 -0
  194. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/format_cell.py +0 -0
  195. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/format_elapsed.py +0 -0
  196. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/guard_view.py +0 -0
  197. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/load_view.py +0 -0
  198. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/log_block.py +0 -0
  199. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/log_blocks.py +0 -0
  200. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/native_rows.py +0 -0
  201. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/reload_on_change.py +0 -0
  202. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/run_cells.py +0 -0
  203. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/run_output.py +0 -0
  204. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/run_panel.py +0 -0
  205. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/runs_table.py +0 -0
  206. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/runs_view.py +0 -0
  207. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/stopped_title.py +0 -0
  208. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/table_columns.py +0 -0
  209. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/table_rows.py +0 -0
  210. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/view_column.py +0 -0
  211. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/view_pane.py +0 -0
  212. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/view_process.py +0 -0
  213. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/tui/view_switcher.py +0 -0
  214. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/utils/__init__.py +0 -0
  215. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/utils/format_bytes.py +0 -0
  216. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/utils/process_alive.py +0 -0
  217. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/utils/run_usage.py +0 -0
  218. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/utils/tree_rss.py +0 -0
  219. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/utils/tree_usage.py +0 -0
  220. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/view/__init__.py +0 -0
  221. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/view/answer.py +0 -0
  222. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/view/build.py +0 -0
  223. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/view/changed.py +0 -0
  224. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/view/experiment.py +0 -0
  225. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/view/respond.py +0 -0
  226. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/view/serve.py +0 -0
  227. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/view_specs.py +0 -0
  228. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/wait_for_note.py +0 -0
  229. {labnotes-0.2.17 → labnotes-0.2.18}/src/labnotes/watch_rows.py +0 -0
  230. {labnotes-0.2.17 → labnotes-0.2.18}/testing-conventions.toml +0 -0
  231. {labnotes-0.2.17 → labnotes-0.2.18}/tests/conftest.py +0 -0
  232. {labnotes-0.2.17 → labnotes-0.2.18}/tests/e2e/conftest.py +0 -0
  233. {labnotes-0.2.17 → labnotes-0.2.18}/tests/integration/conftest.py +0 -0
  234. {labnotes-0.2.17 → labnotes-0.2.18}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.2.17
3
+ Version: 0.2.18
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>
@@ -7,7 +7,9 @@ import click
7
7
  from labnotes.command_line_group import COMMAND_LINE
8
8
  from labnotes.create_experiment import create_experiment
9
9
  from labnotes.daemon.command import command as daemon_command
10
+ from labnotes.daemon.ensure import ensure_daemon
10
11
  from labnotes.runner.start_experiment import start_experiment
12
+ from labnotes.runner.run_state import RunState
11
13
  from labnotes.utils.format_bytes import format_bytes
12
14
  from labnotes.load_config import load_config
13
15
  from labnotes.parse_params import parse_params
@@ -169,24 +171,57 @@ def experiments(
169
171
  only = parse_subset(ctx.args)
170
172
  except ValueError as error:
171
173
  raise click.UsageError(str(error)) from error
172
- start = start_experiment(
173
- config,
174
- name,
175
- experiment_config,
176
- concurrency,
177
- timeout,
178
- only,
179
- passthrough or None,
180
- setup,
181
- teardown,
182
- ctx.meta.get(COMMAND_LINE),
183
- run_id,
184
- )
185
174
  try:
186
175
  if not headless and sys.stdout.isatty() and sys.stderr.isatty():
187
- results = run_tui(start, name, config, experiment_config)
176
+ asyncio.run(ensure_daemon(config, name))
177
+ reply = daemon_command(
178
+ config,
179
+ name,
180
+ {
181
+ "command": "restart" if run_id else "start",
182
+ "options": {
183
+ "experiment_config": str(experiment_config)
184
+ if experiment_config
185
+ else None,
186
+ "concurrency": concurrency,
187
+ "timeout": timeout,
188
+ "only": only,
189
+ "args": passthrough or None,
190
+ "setup": setup,
191
+ "teardown": teardown,
192
+ "command_line": ctx.meta.get(COMMAND_LINE),
193
+ "run_id": run_id,
194
+ },
195
+ },
196
+ )
197
+ if not reply.get("ok"):
198
+ raise ValueError(
199
+ str(reply.get("error", "Daemon rejected start"))
200
+ )
201
+ run_tui(
202
+ None,
203
+ name,
204
+ config,
205
+ experiment_config,
206
+ daemon_journal=RunState(Path(str(reply["journal"]))),
207
+ )
208
+ return
188
209
  else:
189
- results = run_headless(start)
210
+ results = run_headless(
211
+ start_experiment(
212
+ config,
213
+ name,
214
+ experiment_config,
215
+ concurrency,
216
+ timeout,
217
+ only,
218
+ passthrough or None,
219
+ setup,
220
+ teardown,
221
+ ctx.meta.get(COMMAND_LINE),
222
+ run_id,
223
+ )
224
+ )
190
225
  except ValueError as error:
191
226
  raise click.ClickException(str(error)) from error
192
227
  superseded = {r["retry_of"] for r in results if "retry_of" in r}
@@ -25,3 +25,21 @@ class LeavePrompt(ModalScreen[str | None]):
25
25
  "Stop them (enter or s) / cancel (esc).",
26
26
  id="prompt",
27
27
  )
28
+
29
+
30
+ class DetachedLeavePrompt(LeavePrompt):
31
+ """Choose explicitly whether a detached daemon should keep its cells."""
32
+
33
+ BINDINGS = [
34
+ Binding("s", "dismiss('stop')", "Stop runs"),
35
+ Binding("enter,l", "dismiss('leave')", "Leave running"),
36
+ Binding("escape", "dismiss", "Cancel"),
37
+ ]
38
+
39
+ def compose(self) -> ComposeResult:
40
+ runs = "run" if self.count == 1 else "runs"
41
+ yield Static(
42
+ f"{self.count} {runs} still going. "
43
+ "Stop them (s) / leave running (enter or l) / cancel (esc).",
44
+ id="prompt",
45
+ )
@@ -1,4 +1,7 @@
1
+ import asyncio
2
+ import json
1
3
  import signal
4
+ import socket
2
5
  import time
3
6
  import tomllib
4
7
  from collections.abc import Mapping, Sequence
@@ -16,18 +19,21 @@ from textual.widget import Widget
16
19
  from textual.widgets import ContentSwitcher, DataTable, Footer, Header, Input, Static
17
20
 
18
21
  from labnotes.compose_experiment import compose_experiment
22
+ from labnotes.daemon.command import command as daemon_command
23
+ from labnotes.daemon.client import daemon_socket
19
24
  from labnotes.load_experiment import load_experiment
20
25
  from labnotes.models import Controls, LabnotesConfig, Start
21
26
  from labnotes.retries import RETRY_EXIT_CODE
22
27
  from labnotes.runner.describe_event import describe_event
23
28
  from labnotes.runner.param_args import param_args
24
29
  from labnotes.runner.start_experiment import ExperimentStart
30
+ from labnotes.runner.run_state import RunState
25
31
  from labnotes.runner.stop_then_kill import GRACE_SECONDS, stop_then_kill
26
32
  from labnotes.tables.open_tables import open_tables
27
33
  from labnotes.tui.config import RUN_COLUMNS
28
34
  from labnotes.tui.dim import dim
29
35
  from labnotes.tui.format_elapsed import format_elapsed
30
- from labnotes.tui.leave_prompt import LeavePrompt
36
+ from labnotes.tui.leave_prompt import DetachedLeavePrompt, LeavePrompt
31
37
  from labnotes.tui.log_blocks import LogBlocks
32
38
  from labnotes.tui.native_rows import native_rows
33
39
  from labnotes.tui.reload_on_change import reload_on_change
@@ -55,6 +61,7 @@ class RunLiveView(Widget):
55
61
  """
56
62
  BINDINGS = [
57
63
  Binding("x", "kill_run", "Stop run"),
64
+ Binding("r", "restart_run", "Restart"),
58
65
  Binding("slash", "filter", "Filter", key_display="/"),
59
66
  Binding("escape", "leave", "Back"),
60
67
  Binding("plus,equals_sign", "concurrency(1)", "More at once", key_display="+"),
@@ -294,6 +301,10 @@ class RunLiveView(Widget):
294
301
  def action_concurrency(self, change: int) -> None:
295
302
  self.controls.set_concurrency(self.concurrency + change)
296
303
 
304
+ def action_restart_run(self) -> None:
305
+ if (run := self.cursor_run()) is not None and isinstance(self.app, RunApp):
306
+ self.app.restart_run(run)
307
+
297
308
  def action_filter(self) -> None:
298
309
  field = self.query_one(Input)
299
310
  field.display = True
@@ -354,11 +365,12 @@ class RunApp(ViewSwitcher, App[None]):
354
365
 
355
366
  def __init__(
356
367
  self,
357
- start: Start,
368
+ start: Start | None,
358
369
  title: str,
359
370
  columns: Sequence[str],
360
371
  config: LabnotesConfig | None = None,
361
372
  experiment_config: Path | None = None,
373
+ daemon_journal: RunState | None = None,
362
374
  ) -> None:
363
375
  super().__init__()
364
376
  self.start = start
@@ -377,8 +389,12 @@ class RunApp(ViewSwitcher, App[None]):
377
389
  self.shown: Experiment | None = None
378
390
  self.left: dict[str, Widget] = {}
379
391
  self.controls = self.body.controls
380
- self.run_state = start.run_state if isinstance(start, ExperimentStart) else None
392
+ self.daemon_journal = daemon_journal
393
+ self.run_state = daemon_journal or (
394
+ start.run_state if isinstance(start, ExperimentStart) else None
395
+ )
381
396
  self.watching_state = False
397
+ self.attached: socket.socket | None = None
382
398
 
383
399
  def compose(self) -> ComposeResult:
384
400
  yield Header()
@@ -412,7 +428,93 @@ class RunApp(ViewSwitcher, App[None]):
412
428
  exit_on_error=False,
413
429
  )
414
430
  self.run_worker(self.watch_files(tables), exit_on_error=False)
415
- self.run_worker(self.drive())
431
+ if self.daemon_journal is not None:
432
+ journal = self.daemon_journal
433
+ try:
434
+ self.attach_daemon()
435
+ except (OSError, ValueError) as error:
436
+ self.error = error
437
+ self.exit()
438
+ return
439
+ self.listen(
440
+ Controls(
441
+ lambda: self.queue_command({"command": "stop_all"}),
442
+ lambda count: self.queue_command(
443
+ {
444
+ "command": "set_concurrency",
445
+ "count": count,
446
+ "journal": str(journal.path),
447
+ }
448
+ ),
449
+ lambda run: self.queue_command({"command": "stop_one", "run": run}),
450
+ lambda: None,
451
+ )
452
+ )
453
+ self.set_interval(0.25, self.refresh_state)
454
+ self.refresh_state()
455
+ else:
456
+ self.run_worker(self.drive())
457
+
458
+ def attach_daemon(self) -> None:
459
+ assert self.config is not None
460
+ path = daemon_socket(self.config, self.experiment_name)
461
+ if path is None:
462
+ raise ValueError(f"No daemon is running for {self.experiment_name}.")
463
+ client = socket.socket(socket.AF_UNIX)
464
+ try:
465
+ client.settimeout(10)
466
+ client.connect(str(path))
467
+ client.sendall(b'{"command":"attach"}\n')
468
+ with client.makefile("r") as replies:
469
+ reply = json.loads(replies.readline())
470
+ if not reply.get("ok"):
471
+ raise ValueError(str(reply.get("error", "Daemon rejected attach")))
472
+ except Exception:
473
+ client.close()
474
+ raise
475
+ self.attached = client
476
+
477
+ def on_unmount(self) -> None:
478
+ if self.attached is not None:
479
+ self.attached.close()
480
+
481
+ def queue_command(self, message: dict[str, object]) -> None:
482
+ self.run_worker(self.send_command(message), exit_on_error=False)
483
+
484
+ async def send_command(self, message: dict[str, object]) -> None:
485
+ assert self.config is not None
486
+ try:
487
+ reply = await asyncio.to_thread(
488
+ daemon_command, self.config, self.experiment_name, message
489
+ )
490
+ if not reply.get("ok"):
491
+ raise ValueError(str(reply.get("error", "Daemon rejected command")))
492
+ except ValueError as error:
493
+ if self.body_ready:
494
+ self.body.show(str(error))
495
+
496
+ def restart_run(self, run: str) -> None:
497
+ if self.daemon_journal is None:
498
+ return
499
+ if run not in self.body.failed:
500
+ self.body.show(f"{run} has not failed.")
501
+ return
502
+ self.run_worker(self.restart_daemon_run(run), exit_on_error=False)
503
+
504
+ async def restart_daemon_run(self, run: str) -> None:
505
+ assert self.config is not None
506
+ try:
507
+ reply = await asyncio.to_thread(
508
+ daemon_command,
509
+ self.config,
510
+ self.experiment_name,
511
+ {"command": "restart", "options": {"run_id": run}},
512
+ )
513
+ if not reply.get("ok"):
514
+ raise ValueError(str(reply.get("error", "Daemon rejected restart")))
515
+ self.body.show(f"Restarted {run}.")
516
+ except ValueError as error:
517
+ self.body.show(f"Could not restart {run}: {error}")
416
518
 
417
519
  def configured_views(self) -> dict[str, str]:
418
520
  if self.config is None:
@@ -453,6 +555,7 @@ class RunApp(ViewSwitcher, App[None]):
453
555
  self.deliver(self.shown, frozenset({str(row["path"])}))
454
556
 
455
557
  async def drive(self) -> None:
558
+ assert self.start is not None
456
559
  try:
457
560
  self.results = await self.start(self.show_event, self.listen)
458
561
  except Exception as error:
@@ -463,8 +566,9 @@ class RunApp(ViewSwitcher, App[None]):
463
566
  self.controls = controls
464
567
  if self.body_ready:
465
568
  self.body.controls = controls
466
- stop_then_kill(self.stop_runs, signal.SIGTERM)
467
- stop_then_kill(self.stop_runs, signal.SIGHUP)
569
+ if self.daemon_journal is None:
570
+ stop_then_kill(self.stop_runs, signal.SIGTERM)
571
+ stop_then_kill(self.stop_runs, signal.SIGHUP)
468
572
 
469
573
  def show_event(self, event: dict[str, object]) -> None:
470
574
  if self.run_state is None:
@@ -488,6 +592,14 @@ class RunApp(ViewSwitcher, App[None]):
488
592
  self.controls.interrupt()
489
593
 
490
594
  def action_interrupt(self) -> None:
595
+ if self.daemon_journal is not None:
596
+ if self.body_ready and self.body.started:
597
+ self.push_screen(
598
+ DetachedLeavePrompt(len(self.body.started)), self.answer
599
+ )
600
+ else:
601
+ self.exit()
602
+ return
491
603
  if self.body_ready and self.body.started:
492
604
  self.push_screen(LeavePrompt(len(self.body.started)), self.answer)
493
605
  elif self.body_ready and self.body.idle:
@@ -500,8 +612,17 @@ class RunApp(ViewSwitcher, App[None]):
500
612
 
501
613
  def answer(self, choice: str | None) -> None:
502
614
  if choice == "stop":
503
- self.controls.interrupt()
504
- self.set_timer(GRACE_SECONDS, self.controls.interrupt)
615
+ if self.daemon_journal is None:
616
+ self.controls.interrupt()
617
+ self.set_timer(GRACE_SECONDS, self.controls.interrupt)
618
+ else:
619
+ self.run_worker(self.stop_daemon_and_exit(), exit_on_error=False)
620
+ elif choice == "leave" and self.daemon_journal is not None:
621
+ self.exit()
622
+
623
+ async def stop_daemon_and_exit(self) -> None:
624
+ await self.send_command({"command": "stop_all"})
625
+ self.exit()
505
626
 
506
627
  def action_leave(self) -> None:
507
628
  if self.query_one(ContentSwitcher).current != "view-1":
@@ -1,18 +1,25 @@
1
1
  from pathlib import Path
2
2
 
3
3
  from labnotes.models import LabnotesConfig, Start
4
+ from labnotes.runner.run_state import RunState
4
5
  from labnotes.tui.config import RUN_COLUMNS
5
6
  from labnotes.tui.run_app import RunApp
6
7
 
7
8
 
8
9
  def run_tui(
9
- start: Start,
10
+ start: Start | None,
10
11
  title: str,
11
12
  config: LabnotesConfig | None = None,
12
13
  experiment_config: Path | None = None,
14
+ daemon_journal: RunState | None = None,
13
15
  ) -> list[dict[str, object]]:
14
16
  app = RunApp(
15
- start, title, RUN_COLUMNS, config=config, experiment_config=experiment_config
17
+ start,
18
+ title,
19
+ RUN_COLUMNS,
20
+ config=config,
21
+ experiment_config=experiment_config,
22
+ daemon_journal=daemon_journal,
16
23
  )
17
24
  app.run()
18
25
  if app.error is not None:
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  import os
3
- import sys
4
3
  from collections.abc import Mapping
5
4
  from pathlib import Path
6
5
  from typing import Any, cast
@@ -11,6 +10,8 @@ from textual.widget import Widget
11
10
  from textual.widgets import DataTable, Static
12
11
 
13
12
  from labnotes.runs_view_command import runs_view_command
13
+ from labnotes.daemon.command import command as daemon_command
14
+ from labnotes.daemon.ensure import ensure_daemon
14
15
  from labnotes.models import LabnotesConfig
15
16
  from labnotes.runner.claim_run import claim_run
16
17
  from labnotes.tui.fill_table import fill_table
@@ -144,28 +145,21 @@ class RunsBrowser(Widget):
144
145
 
145
146
  async def restart(self, run_id: str) -> None:
146
147
  config = cast(LabnotesConfig, getattr(self.app, "config"))
147
- process = await asyncio.create_subprocess_exec(
148
- sys.executable,
149
- "-m",
150
- "labnotes",
151
- "-c",
152
- str(config.path),
153
- "experiments",
154
- self.experiment.name,
155
- "run",
156
- "--run-id",
157
- run_id,
158
- stdout=asyncio.subprocess.PIPE,
159
- stderr=asyncio.subprocess.PIPE,
160
- )
161
- stdout, stderr = await process.communicate()
162
- if process.returncode == 0:
148
+ try:
149
+ await ensure_daemon(config, self.experiment.name)
150
+ reply = await asyncio.to_thread(
151
+ daemon_command,
152
+ config,
153
+ self.experiment.name,
154
+ {"command": "restart", "options": {"run_id": run_id}},
155
+ )
156
+ if not reply.get("ok"):
157
+ raise ValueError(str(reply.get("error", "Daemon rejected restart")))
163
158
  self.restart_status = (
164
159
  f"Restarted {run_id}. Select the run to view its output."
165
160
  )
166
- else:
167
- detail = stderr.decode().strip() or stdout.decode().strip()
168
- self.restart_status = f"Could not restart {run_id}: {detail}"
161
+ except ValueError as error:
162
+ self.restart_status = f"Could not restart {run_id}: {error}"
169
163
  self.show_status()
170
164
 
171
165
  async def action_open_row(self) -> None:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes