labnotes 0.0.121__tar.gz → 0.0.123__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 (205) hide show
  1. {labnotes-0.0.121 → labnotes-0.0.123}/PKG-INFO +1 -1
  2. {labnotes-0.0.121 → labnotes-0.0.123}/pyproject.toml +1 -1
  3. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/context.md +5 -10
  4. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/models.py +0 -4
  5. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/run_control.py +1 -3
  6. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/describe_event.py +0 -10
  7. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/run_experiment.py +1 -51
  8. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/run_app.py +9 -140
  9. {labnotes-0.0.121 → labnotes-0.0.123}/testing-conventions.toml +6 -0
  10. {labnotes-0.0.121 → labnotes-0.0.123}/uv.lock +4 -4
  11. {labnotes-0.0.121 → labnotes-0.0.123}/.gitignore +0 -0
  12. {labnotes-0.0.121 → labnotes-0.0.123}/CHANGELOG.md +0 -0
  13. {labnotes-0.0.121 → labnotes-0.0.123}/MIGRATIONS.md +0 -0
  14. {labnotes-0.0.121 → labnotes-0.0.123}/README.md +0 -0
  15. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/__init__.py +0 -0
  16. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/__main__.py +0 -0
  17. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/asset_url.py +0 -0
  18. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/check_counts.py +0 -0
  19. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/cli.py +0 -0
  20. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/compose_experiment.py +0 -0
  21. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/context_guide.py +0 -0
  22. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/count_groups.py +0 -0
  23. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/count_runs.py +0 -0
  24. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/create_app.py +0 -0
  25. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/create_experiment.py +0 -0
  26. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/create_note.py +0 -0
  27. {labnotes-0.0.121/src/labnotes/utils → labnotes-0.0.123/src/labnotes/daemon}/__init__.py +0 -0
  28. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/control_run.py +0 -0
  29. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/daemon_app.py +0 -0
  30. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/daemon_client.py +0 -0
  31. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/daemon_running.py +0 -0
  32. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/daemon_server.py +0 -0
  33. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/describe_status.py +0 -0
  34. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/ensure_daemon.py +0 -0
  35. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/exit_when_idle.py +0 -0
  36. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/finalize_lost_runs.py +0 -0
  37. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/last_stderr.py +0 -0
  38. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/remote_start.py +0 -0
  39. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/run_daemon.py +0 -0
  40. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/run_logs.py +0 -0
  41. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/serve_daemon.py +0 -0
  42. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/socket_path.py +0 -0
  43. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/stop_runs.py +0 -0
  44. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/watch_daemon.py +0 -0
  45. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon/watch_status.py +0 -0
  46. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/daemon_command.py +0 -0
  47. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/data_html.py +0 -0
  48. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/default_config_text.py +0 -0
  49. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/default_counts.py +0 -0
  50. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/drain_watch.py +0 -0
  51. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/experiment_notes.py +0 -0
  52. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/experiment_runs.py +0 -0
  53. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/experiments.py +0 -0
  54. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/file_changes.py +0 -0
  55. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/format_duration.py +0 -0
  56. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/format_started.py +0 -0
  57. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/format_value.py +0 -0
  58. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/grid_cell.py +0 -0
  59. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/group_runs.py +0 -0
  60. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/init.py +0 -0
  61. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/invocation_started.py +0 -0
  62. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/is_non_negative_int.py +0 -0
  63. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/is_non_negative_number.py +0 -0
  64. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/is_positive_int.py +0 -0
  65. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/list_experiments.py +0 -0
  66. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/list_runs.py +0 -0
  67. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/load_config.py +0 -0
  68. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/load_experiment.py +0 -0
  69. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/load_run.py +0 -0
  70. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/log.py +0 -0
  71. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/log_path.py +0 -0
  72. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/memory_interval.py +0 -0
  73. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/modes.py +0 -0
  74. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/nests_runs.py +0 -0
  75. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/note_path.py +0 -0
  76. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/notebook_kernel.py +0 -0
  77. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/output_html.py +0 -0
  78. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/parse_run_view.py +0 -0
  79. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/parse_subset.py +0 -0
  80. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/pivot_runs.py +0 -0
  81. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/print_rows.py +0 -0
  82. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/python_blocks.py +0 -0
  83. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/read_config.py +0 -0
  84. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/read_path.py +0 -0
  85. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/render_markdown.py +0 -0
  86. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/retries.py +0 -0
  87. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/run_command.py +0 -0
  88. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/run_output.py +0 -0
  89. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/run_status.py +0 -0
  90. {labnotes-0.0.121/src/labnotes/tui → labnotes-0.0.123/src/labnotes/runner}/__init__.py +0 -0
  91. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/check_subset.py +0 -0
  92. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/execute_run.py +0 -0
  93. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/exit_signal.py +0 -0
  94. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/hold_folder.py +0 -0
  95. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/in_subset.py +0 -0
  96. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/invocation_meta.py +0 -0
  97. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/new_param_run_id.py +0 -0
  98. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/new_run_id.py +0 -0
  99. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  100. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/param_args.py +0 -0
  101. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/param_grid.py +0 -0
  102. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/param_run_name.py +0 -0
  103. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/plan_runs.py +0 -0
  104. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/prepare_experiment.py +0 -0
  105. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/read_experiment_config.py +0 -0
  106. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/record_memory.py +0 -0
  107. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/run_folder.py +0 -0
  108. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/run_headless.py +0 -0
  109. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/runs_parent.py +0 -0
  110. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/shell_command.py +0 -0
  111. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/signal_tree.py +0 -0
  112. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/stop_then_kill.py +0 -0
  113. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/wait_for_tree.py +0 -0
  114. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runner/write_run_json.py +0 -0
  115. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/runs_view_command.py +0 -0
  116. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/save_conflict_copy.py +0 -0
  117. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/save_note.py +0 -0
  118. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/saved_outputs.py +0 -0
  119. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/serve_app.py +0 -0
  120. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/server.py +0 -0
  121. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/shared_params.py +0 -0
  122. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/sort_href.py +0 -0
  123. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/sql_quote.py +0 -0
  124. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/static/elapsed.js +0 -0
  125. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/static/experiment.js +0 -0
  126. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/static/labnotes.css +0 -0
  127. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/static/local-time.js +0 -0
  128. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/static/new-note.js +0 -0
  129. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/static/note-edit.js +0 -0
  130. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/static/note-editor.js +0 -0
  131. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/static/row-link.js +0 -0
  132. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/static/run-cell.js +0 -0
  133. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/static/run-output.js +0 -0
  134. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/static/view-toggle.js +0 -0
  135. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/summarize_config.py +0 -0
  136. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/__init__.py +0 -0
  137. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/experiment_rows.py +0 -0
  138. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/experiment_run_ids.py +0 -0
  139. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/experiment_toml.py +0 -0
  140. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/experiments_ddl.py +0 -0
  141. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/file_prefix.py +0 -0
  142. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/file_text.py +0 -0
  143. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/iso_time.py +0 -0
  144. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/list_experiment_runs.py +0 -0
  145. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/note_text.py +0 -0
  146. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/open_tables.py +0 -0
  147. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/path_glob.py +0 -0
  148. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/path_pattern.py +0 -0
  149. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/read_blocks.py +0 -0
  150. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/read_experiment.py +0 -0
  151. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/read_file.py +0 -0
  152. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/read_log.py +0 -0
  153. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/read_memory.py +0 -0
  154. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/read_run.py +0 -0
  155. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/read_run_file.py +0 -0
  156. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/runs_columns.py +0 -0
  157. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tables/summarize_experiments.py +0 -0
  158. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/templates/base.html +0 -0
  159. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/templates/editor_imports.html +0 -0
  160. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/templates/experiment.html +0 -0
  161. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/templates/index.html +0 -0
  162. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/templates/note.html +0 -0
  163. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/templates/run.html +0 -0
  164. {labnotes-0.0.121/src/labnotes/runner → labnotes-0.0.123/src/labnotes/tui}/__init__.py +0 -0
  165. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/browse_app.py +0 -0
  166. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/browse_tui.py +0 -0
  167. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/check_view_response.py +0 -0
  168. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/config.py +0 -0
  169. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/dim.py +0 -0
  170. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/experiment_screen.py +0 -0
  171. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/experiments_screen.py +0 -0
  172. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/fill_table.py +0 -0
  173. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/format_cell.py +0 -0
  174. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/format_elapsed.py +0 -0
  175. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/group_cells.py +0 -0
  176. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/invocation_columns.py +0 -0
  177. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/invocation_rows.py +0 -0
  178. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/leave_prompt.py +0 -0
  179. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/log_block.py +0 -0
  180. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/log_blocks.py +0 -0
  181. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/native_rows.py +0 -0
  182. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/reload_on_change.py +0 -0
  183. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/run_cells.py +0 -0
  184. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/run_output.py +0 -0
  185. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/run_screen.py +0 -0
  186. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/run_tui.py +0 -0
  187. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/runs_table.py +0 -0
  188. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/runs_view.py +0 -0
  189. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/stopped_title.py +0 -0
  190. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/view_column.py +0 -0
  191. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/tui/view_process.py +0 -0
  192. {labnotes-0.0.121/src/labnotes/daemon → labnotes-0.0.123/src/labnotes/utils}/__init__.py +0 -0
  193. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/utils/format_bytes.py +0 -0
  194. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/utils/process_alive.py +0 -0
  195. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/utils/run_usage.py +0 -0
  196. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/utils/tree_rss.py +0 -0
  197. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/utils/tree_usage.py +0 -0
  198. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/view/__init__.py +0 -0
  199. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/view/answer.py +0 -0
  200. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/view/build.py +0 -0
  201. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/view/respond.py +0 -0
  202. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/view/serve.py +0 -0
  203. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/wait_for_note.py +0 -0
  204. {labnotes-0.0.121 → labnotes-0.0.123}/src/labnotes/watch_rows.py +0 -0
  205. {labnotes-0.0.121 → labnotes-0.0.123}/tests/integration/conftest.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.121
3
+ Version: 0.0.123
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>
@@ -36,7 +36,7 @@ labnotes = "labnotes.cli:main"
36
36
 
37
37
  [dependency-groups]
38
38
  dev = [
39
- "curtaincall>=0.3.0",
39
+ "curtaincall>=0.4.0",
40
40
  "mypy>=2.4.0",
41
41
  "playwright>=1.64.0",
42
42
  "pytest>=8.0",
@@ -132,14 +132,10 @@ sets the seconds between samples; 0 turns it off.
132
132
 
133
133
  In a terminal, `run` shows a live table of runs (status, time, memory and
134
134
  folder size) and stays
135
- open when they finish. Failed runs show their status in red; `f` hides or shows them, and `r` on one
136
- runs it again under a new id, with `retry_of` set. `j` and `k` move the cursor, `g` and `G` jump to the
135
+ open when they finish. Failed runs show their status in red. `j` and `k` move the cursor, `g` and `G` jump to the
137
136
  first and last row. Runs not started yet show grayed as `pending`, and as
138
- `skipped` once stopped. Move the cursor to a pending run and press `n` to run it
139
- next. `m` grabs a pending run; `j` and `k` move it through the queue, Enter or `m`
140
- drops it, Escape puts it back. Space deselects a pending run so it won't run; Space again puts it back at
141
- the end of the queue. `/` filters rows by params text; Enter keeps the filter,
142
- Escape clears it. `D` deselects every pending run the filter shows. `x` stops the
137
+ `skipped` once stopped. `/` filters rows by params text; Enter keeps the filter,
138
+ Escape clears it. `x` stops the
143
139
  running run under the cursor; `x` again kills it. `+` and
144
140
  `-` change how many run at once; lowering it lets running runs
145
141
  finish. Enter on a run follows its `stdout.log` and `stderr.log` together, as
@@ -180,9 +176,8 @@ going. Elsewhere, it prints a line per event instead.
180
176
  waits for a daemon when none is up. `--json` prints one JSON object per line.
181
177
  Following doesn't keep the daemon up.
182
178
  - `labnotes run control NAME ACTION [ARG]` sends one live-view control to a
183
- running experiment: `kill`, `retry`, `run_next` or `toggle` a run, `move` a
184
- run to a place in the queue counted from 0,
185
- `set_concurrency` to a number, `interrupt` or `finish`.
179
+ running experiment: `kill` a run, `set_concurrency` to a number,
180
+ `interrupt` or `finish`.
186
181
  - A second `run` of the same experiment fails while the first is going.
187
182
  - It exits 0 even if runs fail; the summary line counts failures. A run that
188
183
  was retried counts as retried, not failed. If it can't
@@ -14,13 +14,9 @@ Report = Callable[[dict[str, object]], None]
14
14
  @dataclass(frozen=True)
15
15
  class Controls:
16
16
  interrupt: Callable[[], None]
17
- run_next: Callable[[str], None]
18
17
  set_concurrency: Callable[[int], None]
19
- toggle: Callable[[str], None]
20
18
  kill: Callable[[str], None]
21
- retry: Callable[[str], None]
22
19
  finish: Callable[[], None]
23
- move: Callable[[str, int], None]
24
20
 
25
21
 
26
22
  Listen = Callable[[Controls], None]
@@ -18,9 +18,7 @@ PARAMS = {
18
18
  name="control",
19
19
  help=(
20
20
  "Send one control to an experiment's runs, as the live view's keys do. "
21
- "kill, retry, run_next and toggle take a run; move a run and its new place "
22
- "in the queue, from 0; set_concurrency a number; "
23
- "interrupt and finish nothing."
21
+ "kill takes a run; set_concurrency a number; interrupt and finish nothing."
24
22
  ),
25
23
  )
26
24
  @click.argument("name")
@@ -14,16 +14,6 @@ def describe_event(event: dict[str, object]) -> str:
14
14
  case "ended":
15
15
  outcome = event["error"] or f"exited {event['exit_code']}"
16
16
  return f"{event['run']} {outcome}"
17
- case "queued":
18
- return f"{event['run']} runs next."
19
- case "moved":
20
- pending: list[str] = event["pending"] # type: ignore[assignment]
21
- place = pending.index(str(event["run"])) + 1
22
- return f"{event['run']} moved to {place} of {len(pending)} in the queue."
23
- case "deselected":
24
- return f"{event['run']} deselected."
25
- case "reselected":
26
- return f"{event['run']} back in the queue."
27
17
  case "stopping run":
28
18
  return f"Stopping {event['run']}."
29
19
  case "killing run":
@@ -128,43 +128,6 @@ async def run_experiment(
128
128
  report({"event": "stopping run", "run": run})
129
129
  signal_run(run, signal.SIGTERM)
130
130
 
131
- held: list[Entry] = []
132
-
133
- def find(entries: list[Entry], run: str) -> Entry | None:
134
- return next((entry for entry in entries if entry[0] == run), None)
135
-
136
- def arrange(event: str, run: str) -> None:
137
- pending = [id_ for id_, _ in plan]
138
- deselected = [id_ for id_, _ in held]
139
- report({"event": event, "run": run, "pending": pending, "held": deselected})
140
-
141
- def run_next(run: str) -> None:
142
- entry = find(plan, run)
143
- if entry is not None:
144
- plan.remove(entry)
145
- plan.insert(0, entry)
146
- arrange("queued", run)
147
-
148
- def move(run: str, place: int) -> None:
149
- entry = find(plan, run)
150
- if entry is not None:
151
- plan.remove(entry)
152
- plan.insert(max(0, place), entry)
153
- arrange("moved", run)
154
-
155
- def toggle(run: str) -> None:
156
- if stopping.is_set():
157
- return
158
- if (entry := find(plan, run)) is not None:
159
- plan.remove(entry)
160
- held.append(entry)
161
- arrange("deselected", run)
162
- elif (entry := find(held, run)) is not None:
163
- held.remove(entry)
164
- plan.append(entry)
165
- arrange("reselected", run)
166
- fill()
167
-
168
131
  ended: dict[str, dict[str, object]] = {}
169
132
  parents: dict[str, str] = {}
170
133
  attempts: dict[str, int] = {}
@@ -179,15 +142,6 @@ async def run_experiment(
179
142
  report({"event": "retried", "run": run, "retry": again, "params": ended[run]})
180
143
  fill()
181
144
 
182
- def retry(run: str) -> None:
183
- if stopping.is_set() or run not in ended:
184
- return
185
- if run in waiting:
186
- waiting.pop(run).cancel()
187
- requeue(run, attempts.get(run, 0) + 1)
188
- else:
189
- requeue(run, 0)
190
-
191
145
  def retry_later(run: str) -> None:
192
146
  tried = attempts.get(run, 0)
193
147
  if tried >= experiment.retries:
@@ -277,11 +231,7 @@ async def run_experiment(
277
231
  ):
278
232
  retry_later(run)
279
233
 
280
- listen(
281
- Controls(
282
- interrupt, run_next, set_concurrency, toggle, kill, retry, finish, move
283
- )
284
- )
234
+ listen(Controls(interrupt, set_concurrency, kill, finish))
285
235
  fill()
286
236
  await done.wait()
287
237
  await asyncio.gather(*started)
@@ -26,27 +26,15 @@ from labnotes.tui.runs_table import RunsTable
26
26
 
27
27
  PENDING, PASSED, FAILED = "○", "✓", "✗"
28
28
  SPINNER = "|/-\\"
29
- SKIPPED, DESELECTED = "–", "⊘"
29
+ SKIPPED = "–"
30
30
  VIEWING_ACTIONS = {"leave", "quit", "interrupt", "command_palette"}
31
- GRABBED_ACTIONS = {"shift", "drop", "put_back"}
32
- GRABBING_ACTIONS = {*GRABBED_ACTIONS, "grab", "quit", "interrupt", "command_palette"}
33
31
 
34
32
 
35
33
  class RunApp(App[None]):
36
34
  CSS = "#concurrency { dock: right; width: auto; padding: 0 1; }"
37
35
  BINDINGS = [
38
36
  Binding("ctrl+c", "interrupt", "Quit"),
39
- Binding("n", "run_next", "Run next"),
40
- Binding("m", "grab", "Move"),
41
- Binding("j,down", "shift(1)", priority=True),
42
- Binding("k,up", "shift(-1)", priority=True),
43
- Binding("enter", "drop", "Drop", priority=True),
44
- Binding("escape", "put_back", "Put back"),
45
- Binding("space", "toggle_run", "Deselect"),
46
- Binding("D", "deselect_shown", "Deselect shown"),
47
37
  Binding("x", "kill_run", "Stop run"),
48
- Binding("f", "hide_failed", "Hide failed"),
49
- Binding("r", "retry", "Retry"),
50
38
  Binding("slash", "filter", "Filter", key_display="/"),
51
39
  Binding("escape", "leave", "Back"),
52
40
  Binding("plus,equals_sign", "concurrency(1)", "More at once", key_display="+"),
@@ -61,18 +49,10 @@ class RunApp(App[None]):
61
49
  self.results: list[dict[str, object]] = []
62
50
  self.error: Exception | None = None
63
51
  self.controls = Controls(
64
- lambda: None,
65
- lambda run: None,
66
- lambda count: None,
67
- lambda run: None,
68
- lambda run: None,
69
- lambda run: None,
70
- lambda: None,
71
- lambda run, place: None,
52
+ lambda: None, lambda count: None, lambda run: None, lambda: None
72
53
  )
73
54
  self.concurrency = 1
74
55
  self.idle = False
75
- self.next_run: str | None = None
76
56
  self.ended: set[str] = set()
77
57
  self.frame = 0
78
58
  self.started: dict[str, float] = {}
@@ -82,11 +62,8 @@ class RunApp(App[None]):
82
62
  self.failed: set[str] = set()
83
63
  self.retried: set[str] = set()
84
64
  self.waiting: dict[str, float] = {}
85
- self.hiding = False
86
65
  self.folders: dict[str, Path] = {}
87
66
  self.viewing: str | None = None
88
- self.grabbed: str | None = None
89
- self.grabbed_from: list[str] = []
90
67
  self.sampling = False
91
68
  self.unmeasured: dict[str, None] = {}
92
69
 
@@ -143,14 +120,10 @@ class RunApp(App[None]):
143
120
  case "adhoc", [only]:
144
121
  self.view(only["run"]) # type: ignore[index]
145
122
  case "started":
146
- if run == self.grabbed:
147
- self.action_put_back()
148
123
  self.idle = False
149
124
  self.pending.discard(run)
150
125
  self.started[run] = time.monotonic()
151
126
  self.folders[run] = Path(str(event["folder"]))
152
- if run == self.next_run:
153
- self.show("")
154
127
  args = " ".join(param_args(event["params"])) # type: ignore[arg-type]
155
128
  shown = {
156
129
  "run": run.rpartition("/")[2],
@@ -175,21 +148,6 @@ class RunApp(App[None]):
175
148
  self.redraw(table.cursor_row)
176
149
  self.unmeasured[run] = None
177
150
  self.measure()
178
- case "queued":
179
- self.reposition(event, run)
180
- self.next_run = run
181
- case "moved":
182
- self.reposition(event, run)
183
- case "deselected":
184
- self.pending.discard(run)
185
- self.set_cell(run, "status", dim(DESELECTED))
186
- self.reorder(event, table.cursor_row)
187
- self.show(describe_event(event))
188
- case "reselected":
189
- self.pending.add(run)
190
- self.set_cell(run, "status", dim(PENDING))
191
- self.reorder(event, table.cursor_row)
192
- self.show(describe_event(event))
193
151
  case "retrying":
194
152
  self.waiting[run] = time.monotonic() + float(event["seconds"]) # type: ignore[arg-type]
195
153
  self.tick()
@@ -233,25 +191,11 @@ class RunApp(App[None]):
233
191
  again = f" {retried} retried." if retried else ""
234
192
  self.show(f"{passed} passed, {failed} failed.{again} Ctrl-C to quit.")
235
193
 
236
- def reposition(self, event: dict[str, object], run: str) -> None:
237
- table = self.query_one(DataTable)
238
- self.reorder(event, table.cursor_row)
239
- table.move_cursor(row=table.get_row_index(run))
240
- self.show(describe_event(event))
241
-
242
- def reorder(self, event: dict[str, object], row: int) -> None:
243
- self.release()
244
- waiting: list[str] = [*event["pending"], *event["held"]] # type: ignore[misc]
245
- for run in waiting:
246
- self.cells[run] = self.cells.pop(run)
247
- self.redraw(row)
248
-
249
194
  def redraw(self, row: int) -> None:
250
195
  table = self.query_one(DataTable)
251
196
  table.clear()
252
197
  for run, cells in self.cells.items():
253
- hidden = self.hiding and run in self.failed
254
- if self.filter in str(cells.get("params", "")) and not hidden:
198
+ if self.filter in str(cells.get("params", "")):
255
199
  table.add_row(
256
200
  *(cells.get(c, "") for c in self.columns),
257
201
  key=run,
@@ -274,10 +218,8 @@ class RunApp(App[None]):
274
218
  def check_action(self, action: str, parameters: tuple[object, ...]) -> bool:
275
219
  if isinstance(self.screen, LeavePrompt):
276
220
  return False
277
- if self.grabbed is not None:
278
- return action in GRABBING_ACTIONS
279
- if action in GRABBED_ACTIONS:
280
- return False
221
+ if action == "leave" and self.viewing is None:
222
+ return self.query_one(Input).display
281
223
  return self.viewing is None or action in VIEWING_ACTIONS
282
224
 
283
225
  def show_concurrency(self, event: dict[str, object]) -> None:
@@ -324,7 +266,6 @@ class RunApp(App[None]):
324
266
  self.set_cell(run, "status", SPINNER[self.frame])
325
267
 
326
268
  def show(self, text: str) -> None:
327
- self.next_run = None
328
269
  self.query_one("#status", Static).update(text)
329
270
 
330
271
  def stop_runs(self) -> None:
@@ -358,84 +299,10 @@ class RunApp(App[None]):
358
299
  cell = table.coordinate_to_cell_key(table.cursor_coordinate)
359
300
  return str(cell.row_key.value)
360
301
 
361
- def action_run_next(self) -> None:
362
- if (run := self.cursor_run()) is not None:
363
- self.controls.run_next(run)
364
-
365
- def queue(self) -> list[str]:
366
- return [run for run in self.cells if run in self.pending]
367
-
368
- def action_grab(self) -> None:
369
- if self.grabbed is not None:
370
- self.action_drop()
371
- return
372
- run = self.cursor_run()
373
- if run not in self.pending:
374
- return
375
- self.grabbed = run
376
- self.grabbed_from = list(self.cells)
377
- self.refresh_bindings()
378
- self.show(f"Moving {run}. j/k moves it, Enter drops it, Escape puts it back.")
379
-
380
- def action_shift(self, step: int) -> None:
381
- run = str(self.grabbed)
382
- queue = self.queue()
383
- place = queue.index(run) + step
384
- if place not in range(len(queue)):
385
- return
386
- order = list(self.cells)
387
- here, there = order.index(run), order.index(queue[place])
388
- order[here], order[there] = order[there], order[here]
389
- self.cells = {key: self.cells[key] for key in order}
390
- self.redraw_on(run)
391
-
392
- def action_drop(self) -> None:
393
- run = str(self.grabbed)
394
- self.release()
395
- self.show("")
396
- self.controls.move(run, self.queue().index(run))
397
-
398
- def action_put_back(self) -> None:
399
- run = str(self.grabbed)
400
- self.release()
401
- places = {key: place for place, key in enumerate(self.grabbed_from)}
402
- ordered = sorted(self.cells, key=lambda key: places.get(key, len(places)))
403
- self.cells = {key: self.cells[key] for key in ordered}
404
- self.redraw_on(run)
405
- self.show("")
406
-
407
- def release(self) -> None:
408
- self.grabbed = None
409
- self.refresh_bindings()
410
-
411
- def redraw_on(self, run: str) -> None:
412
- table = self.query_one(DataTable)
413
- self.redraw(table.cursor_row)
414
- table.move_cursor(row=table.get_row_index(run))
415
-
416
- def action_toggle_run(self) -> None:
417
- if (run := self.cursor_run()) is not None:
418
- self.controls.toggle(run)
419
-
420
- def action_hide_failed(self) -> None:
421
- self.hiding = not self.hiding
422
- self.redraw(self.query_one(DataTable).cursor_row)
423
- self.show("Failed runs hidden." if self.hiding else "Failed runs shown.")
424
-
425
- def action_retry(self) -> None:
426
- if (run := self.cursor_run()) is not None and run in self.failed:
427
- self.controls.retry(run)
428
-
429
302
  def action_kill_run(self) -> None:
430
303
  if (run := self.cursor_run()) is not None:
431
304
  self.controls.kill(run)
432
305
 
433
- def action_deselect_shown(self) -> None:
434
- shown = [str(key.value) for key in self.query_one(DataTable).rows]
435
- for run in shown:
436
- if run in self.pending:
437
- self.controls.toggle(run)
438
-
439
306
  def action_concurrency(self, change: int) -> None:
440
307
  self.controls.set_concurrency(self.concurrency + change)
441
308
 
@@ -443,10 +310,10 @@ class RunApp(App[None]):
443
310
  field = self.query_one(Input)
444
311
  field.display = True
445
312
  field.focus()
313
+ self.refresh_bindings()
446
314
 
447
315
  def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
448
- if self.grabbed is None:
449
- self.view(str(event.row_key.value))
316
+ self.view(str(event.row_key.value))
450
317
 
451
318
  def view(self, run: str) -> None:
452
319
  self.viewing = run
@@ -481,6 +348,7 @@ class RunApp(App[None]):
481
348
  field.value = ""
482
349
  field.display = False
483
350
  self.query_one(DataTable).focus()
351
+ self.refresh_bindings()
484
352
 
485
353
  def on_input_changed(self, event: Input.Changed) -> None:
486
354
  self.filter = event.value
@@ -489,3 +357,4 @@ class RunApp(App[None]):
489
357
  def on_input_submitted(self, event: Input.Submitted) -> None:
490
358
  event.input.display = bool(event.value)
491
359
  self.query_one(DataTable).focus()
360
+ self.refresh_bindings()
@@ -81,3 +81,9 @@ path = "src/labnotes/experiments.py"
81
81
  rules = ["mutation"]
82
82
  lines = [120]
83
83
  reason = "click hands back the \"json\" choice literal itself, so is not equals !="
84
+
85
+ [[python.exempt]]
86
+ path = "src/labnotes/tui/run_app.py"
87
+ rules = ["mutation"]
88
+ lines = [221]
89
+ reason = "the leave action name is the string literal from the Binding, which CPython interns, so is equals =="
@@ -237,15 +237,15 @@ wheels = [
237
237
 
238
238
  [[package]]
239
239
  name = "curtaincall"
240
- version = "0.3.0"
240
+ version = "0.4.0"
241
241
  source = { registry = "https://pypi.org/simple" }
242
242
  dependencies = [
243
243
  { name = "pexpect" },
244
244
  { name = "pyte" },
245
245
  ]
246
- sdist = { url = "https://files.pythonhosted.org/packages/ca/12/056a3c89fcbc135b15a7cef872472a73bdad3594ddfccbe11b9a028b2f5f/curtaincall-0.3.0.tar.gz", hash = "sha256:afc9e9ce00a256982d69a737976527d38217082ca79fcafcf381fa2cd2bba041", size = 68784, upload-time = "2026-02-16T01:44:39.874Z" }
246
+ sdist = { url = "https://files.pythonhosted.org/packages/4c/c3/14a2abeb4d38d1c381c33fb3fbd4d55675a0f98be8c39b15d688c546b3f3/curtaincall-0.4.0.tar.gz", hash = "sha256:b68c9652cb33eb0955e5b9307573302e8b6f260930a67ab69152c48db79b75cc", size = 33924, upload-time = "2026-10-26T14:16:25.535Z" }
247
247
  wheels = [
248
- { url = "https://files.pythonhosted.org/packages/f8/52/1092ca83246b88a880f2fe35e38919b9e069c0ce97708880c4234ab72c32/curtaincall-0.3.0-py3-none-any.whl", hash = "sha256:4a754fa4a8ad726a00ec24ebb545f8f5a34a0f8d159936845d125bfeb2dfb652", size = 21504, upload-time = "2026-02-16T01:44:38.567Z" },
248
+ { url = "https://files.pythonhosted.org/packages/bc/43/6c5749737b1fbb9a2757adf6df70ded2a38e832b63d4b4f9e75b6adf1080/curtaincall-0.4.0-py3-none-any.whl", hash = "sha256:2294a8b8e2d8cd10f47f5004ae7bc9b68ce9f7f1182c364dba294b3d190aa726", size = 29057, upload-time = "2026-10-26T14:16:24.342Z" },
249
249
  ]
250
250
 
251
251
  [[package]]
@@ -602,7 +602,7 @@ requires-dist = [
602
602
 
603
603
  [package.metadata.requires-dev]
604
604
  dev = [
605
- { name = "curtaincall", specifier = ">=0.3.0" },
605
+ { name = "curtaincall", specifier = ">=0.4.0" },
606
606
  { name = "mypy", specifier = ">=2.4.0" },
607
607
  { name = "playwright", specifier = ">=1.64.0" },
608
608
  { name = "pytest", specifier = ">=8.0" },
File without changes
File without changes
File without changes
File without changes