labnotes 0.0.122__tar.gz → 0.0.124__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 (208) hide show
  1. {labnotes-0.0.122 → labnotes-0.0.124}/PKG-INFO +1 -1
  2. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/context.md +5 -10
  3. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/daemon_app.py +2 -0
  4. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/remote_start.py +2 -0
  5. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/experiments.py +24 -2
  6. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/models.py +0 -4
  7. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/run_control.py +1 -3
  8. labnotes-0.0.124/src/labnotes/runner/check_args.py +15 -0
  9. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/describe_event.py +0 -10
  10. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/execute_run.py +5 -2
  11. labnotes-0.0.124/src/labnotes/runner/flag_name.py +4 -0
  12. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/prepare_experiment.py +4 -0
  13. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/run_experiment.py +3 -51
  14. labnotes-0.0.124/src/labnotes/runner/shell_command.py +12 -0
  15. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/run_app.py +9 -140
  16. {labnotes-0.0.122 → labnotes-0.0.124}/testing-conventions.toml +6 -0
  17. labnotes-0.0.122/src/labnotes/runner/shell_command.py +0 -9
  18. {labnotes-0.0.122 → labnotes-0.0.124}/.gitignore +0 -0
  19. {labnotes-0.0.122 → labnotes-0.0.124}/CHANGELOG.md +0 -0
  20. {labnotes-0.0.122 → labnotes-0.0.124}/MIGRATIONS.md +0 -0
  21. {labnotes-0.0.122 → labnotes-0.0.124}/README.md +0 -0
  22. {labnotes-0.0.122 → labnotes-0.0.124}/pyproject.toml +0 -0
  23. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/__init__.py +0 -0
  24. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/__main__.py +0 -0
  25. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/asset_url.py +0 -0
  26. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/check_counts.py +0 -0
  27. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/cli.py +0 -0
  28. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/compose_experiment.py +0 -0
  29. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/context_guide.py +0 -0
  30. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/count_groups.py +0 -0
  31. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/count_runs.py +0 -0
  32. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/create_app.py +0 -0
  33. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/create_experiment.py +0 -0
  34. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/create_note.py +0 -0
  35. {labnotes-0.0.122/src/labnotes/utils → labnotes-0.0.124/src/labnotes/daemon}/__init__.py +0 -0
  36. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/control_run.py +0 -0
  37. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/daemon_client.py +0 -0
  38. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/daemon_running.py +0 -0
  39. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/daemon_server.py +0 -0
  40. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/describe_status.py +0 -0
  41. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/ensure_daemon.py +0 -0
  42. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/exit_when_idle.py +0 -0
  43. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/finalize_lost_runs.py +0 -0
  44. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/last_stderr.py +0 -0
  45. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/run_daemon.py +0 -0
  46. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/run_logs.py +0 -0
  47. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/serve_daemon.py +0 -0
  48. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/socket_path.py +0 -0
  49. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/stop_runs.py +0 -0
  50. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/watch_daemon.py +0 -0
  51. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon/watch_status.py +0 -0
  52. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/daemon_command.py +0 -0
  53. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/data_html.py +0 -0
  54. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/default_config_text.py +0 -0
  55. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/default_counts.py +0 -0
  56. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/drain_watch.py +0 -0
  57. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/experiment_notes.py +0 -0
  58. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/experiment_runs.py +0 -0
  59. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/file_changes.py +0 -0
  60. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/format_duration.py +0 -0
  61. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/format_started.py +0 -0
  62. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/format_value.py +0 -0
  63. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/grid_cell.py +0 -0
  64. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/group_runs.py +0 -0
  65. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/init.py +0 -0
  66. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/invocation_started.py +0 -0
  67. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/is_non_negative_int.py +0 -0
  68. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/is_non_negative_number.py +0 -0
  69. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/is_positive_int.py +0 -0
  70. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/list_experiments.py +0 -0
  71. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/list_runs.py +0 -0
  72. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/load_config.py +0 -0
  73. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/load_experiment.py +0 -0
  74. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/load_run.py +0 -0
  75. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/log.py +0 -0
  76. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/log_path.py +0 -0
  77. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/memory_interval.py +0 -0
  78. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/modes.py +0 -0
  79. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/nests_runs.py +0 -0
  80. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/note_path.py +0 -0
  81. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/notebook_kernel.py +0 -0
  82. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/output_html.py +0 -0
  83. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/parse_run_view.py +0 -0
  84. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/parse_subset.py +0 -0
  85. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/pivot_runs.py +0 -0
  86. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/print_rows.py +0 -0
  87. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/python_blocks.py +0 -0
  88. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/read_config.py +0 -0
  89. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/read_path.py +0 -0
  90. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/render_markdown.py +0 -0
  91. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/retries.py +0 -0
  92. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/run_command.py +0 -0
  93. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/run_output.py +0 -0
  94. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/run_status.py +0 -0
  95. {labnotes-0.0.122/src/labnotes/tui → labnotes-0.0.124/src/labnotes/runner}/__init__.py +0 -0
  96. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/check_subset.py +0 -0
  97. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/exit_signal.py +0 -0
  98. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/hold_folder.py +0 -0
  99. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/in_subset.py +0 -0
  100. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/invocation_meta.py +0 -0
  101. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/new_param_run_id.py +0 -0
  102. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/new_run_id.py +0 -0
  103. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  104. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/param_args.py +0 -0
  105. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/param_grid.py +0 -0
  106. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/param_run_name.py +0 -0
  107. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/plan_runs.py +0 -0
  108. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/read_experiment_config.py +0 -0
  109. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/record_memory.py +0 -0
  110. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/run_folder.py +0 -0
  111. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/run_headless.py +0 -0
  112. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/runs_parent.py +0 -0
  113. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/signal_tree.py +0 -0
  114. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/stop_then_kill.py +0 -0
  115. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/wait_for_tree.py +0 -0
  116. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runner/write_run_json.py +0 -0
  117. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/runs_view_command.py +0 -0
  118. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/save_conflict_copy.py +0 -0
  119. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/save_note.py +0 -0
  120. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/saved_outputs.py +0 -0
  121. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/serve_app.py +0 -0
  122. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/server.py +0 -0
  123. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/shared_params.py +0 -0
  124. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/sort_href.py +0 -0
  125. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/sql_quote.py +0 -0
  126. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/static/elapsed.js +0 -0
  127. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/static/experiment.js +0 -0
  128. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/static/labnotes.css +0 -0
  129. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/static/local-time.js +0 -0
  130. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/static/new-note.js +0 -0
  131. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/static/note-edit.js +0 -0
  132. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/static/note-editor.js +0 -0
  133. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/static/row-link.js +0 -0
  134. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/static/run-cell.js +0 -0
  135. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/static/run-output.js +0 -0
  136. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/static/view-toggle.js +0 -0
  137. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/summarize_config.py +0 -0
  138. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/__init__.py +0 -0
  139. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/experiment_rows.py +0 -0
  140. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/experiment_run_ids.py +0 -0
  141. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/experiment_toml.py +0 -0
  142. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/experiments_ddl.py +0 -0
  143. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/file_prefix.py +0 -0
  144. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/file_text.py +0 -0
  145. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/iso_time.py +0 -0
  146. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/list_experiment_runs.py +0 -0
  147. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/note_text.py +0 -0
  148. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/open_tables.py +0 -0
  149. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/path_glob.py +0 -0
  150. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/path_pattern.py +0 -0
  151. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/read_blocks.py +0 -0
  152. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/read_experiment.py +0 -0
  153. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/read_file.py +0 -0
  154. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/read_log.py +0 -0
  155. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/read_memory.py +0 -0
  156. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/read_run.py +0 -0
  157. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/read_run_file.py +0 -0
  158. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/runs_columns.py +0 -0
  159. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tables/summarize_experiments.py +0 -0
  160. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/templates/base.html +0 -0
  161. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/templates/editor_imports.html +0 -0
  162. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/templates/experiment.html +0 -0
  163. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/templates/index.html +0 -0
  164. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/templates/note.html +0 -0
  165. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/templates/run.html +0 -0
  166. {labnotes-0.0.122/src/labnotes/runner → labnotes-0.0.124/src/labnotes/tui}/__init__.py +0 -0
  167. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/browse_app.py +0 -0
  168. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/browse_tui.py +0 -0
  169. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/check_view_response.py +0 -0
  170. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/config.py +0 -0
  171. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/dim.py +0 -0
  172. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/experiment_screen.py +0 -0
  173. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/experiments_screen.py +0 -0
  174. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/fill_table.py +0 -0
  175. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/format_cell.py +0 -0
  176. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/format_elapsed.py +0 -0
  177. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/group_cells.py +0 -0
  178. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/invocation_columns.py +0 -0
  179. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/invocation_rows.py +0 -0
  180. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/leave_prompt.py +0 -0
  181. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/log_block.py +0 -0
  182. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/log_blocks.py +0 -0
  183. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/native_rows.py +0 -0
  184. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/reload_on_change.py +0 -0
  185. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/run_cells.py +0 -0
  186. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/run_output.py +0 -0
  187. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/run_screen.py +0 -0
  188. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/run_tui.py +0 -0
  189. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/runs_table.py +0 -0
  190. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/runs_view.py +0 -0
  191. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/stopped_title.py +0 -0
  192. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/view_column.py +0 -0
  193. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/tui/view_process.py +0 -0
  194. {labnotes-0.0.122/src/labnotes/daemon → labnotes-0.0.124/src/labnotes/utils}/__init__.py +0 -0
  195. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/utils/format_bytes.py +0 -0
  196. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/utils/process_alive.py +0 -0
  197. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/utils/run_usage.py +0 -0
  198. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/utils/tree_rss.py +0 -0
  199. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/utils/tree_usage.py +0 -0
  200. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/view/__init__.py +0 -0
  201. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/view/answer.py +0 -0
  202. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/view/build.py +0 -0
  203. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/view/respond.py +0 -0
  204. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/view/serve.py +0 -0
  205. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/wait_for_note.py +0 -0
  206. {labnotes-0.0.122 → labnotes-0.0.124}/src/labnotes/watch_rows.py +0 -0
  207. {labnotes-0.0.122 → labnotes-0.0.124}/tests/integration/conftest.py +0 -0
  208. {labnotes-0.0.122 → labnotes-0.0.124}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.122
3
+ Version: 0.0.124
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>
@@ -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
@@ -79,6 +79,7 @@ def daemon_app(config: LabnotesConfig) -> Starlette:
79
79
  None if path is None else Path(str(path)),
80
80
  body.get("concurrency"), # type: ignore[arg-type]
81
81
  body.get("only"), # type: ignore[arg-type]
82
+ body.get("args"), # type: ignore[arg-type]
82
83
  )
83
84
  except ValueError as error:
84
85
  return refuse(400, str(error))
@@ -105,6 +106,7 @@ def daemon_app(config: LabnotesConfig) -> Starlette:
105
106
  listen,
106
107
  body.get("timeout"), # type: ignore[arg-type]
107
108
  only=body.get("only"), # type: ignore[arg-type]
109
+ args=body.get("args"), # type: ignore[arg-type]
108
110
  )
109
111
  # Clients hear why the session ended; the daemon keeps serving.
110
112
  except Exception as failure: # noqa: BLE001
@@ -17,6 +17,7 @@ def remote_start(
17
17
  concurrency: int | None,
18
18
  timeout: float | None = None,
19
19
  only: dict[str, str] | None = None,
20
+ args: list[str] | None = None,
20
21
  ) -> Start:
21
22
  async def start(report: Report, listen: Listen) -> list[dict[str, object]]:
22
23
  try:
@@ -48,6 +49,7 @@ def remote_start(
48
49
  "concurrency": concurrency,
49
50
  "timeout": timeout,
50
51
  "only": only,
52
+ "args": args,
51
53
  },
52
54
  )
53
55
  if reply.status_code != 202:
@@ -17,12 +17,25 @@ from labnotes.tui.browse_tui import browse_tui
17
17
  from labnotes.tui.run_tui import run_tui
18
18
 
19
19
  BYTES = ("memory", "size")
20
+ PASSTHROUGH = "labnotes.passthrough"
21
+
22
+
23
+ class PassthroughCommand(click.Command):
24
+ def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
25
+ if "--" in args:
26
+ at = args.index("--")
27
+ ctx.meta[PASSTHROUGH] = args[at + 1 :]
28
+ args = args[:at]
29
+ return super().parse_args(ctx, args)
20
30
 
21
31
 
22
32
  @click.command(
33
+ cls=PassthroughCommand,
23
34
  help="List experiments, show one, list its runs, or run it."
24
35
  " `new [NAME]` creates one. With run, --PARAM=VALUE runs only the"
25
- " matrix cells where that param has that value.",
36
+ " matrix cells where that param has that value, and anything after --"
37
+ " is appended to every run's command and recorded in run.json, so never"
38
+ " pass secrets there.",
26
39
  context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
27
40
  )
28
41
  @click.argument("name", required=False)
@@ -63,6 +76,7 @@ def experiments(
63
76
  experiment_config: Path | None,
64
77
  timeout: int | None,
65
78
  ) -> None:
79
+ passthrough: list[str] | None = ctx.meta.get(PASSTHROUGH)
66
80
  config = load_config(ctx)
67
81
  if config is None:
68
82
  raise click.ClickException(
@@ -82,7 +96,13 @@ def experiments(
82
96
  except ValueError as error:
83
97
  raise click.UsageError(str(error)) from error
84
98
  start = remote_start(
85
- config, name, experiment_config, concurrency, timeout, only
99
+ config,
100
+ name,
101
+ experiment_config,
102
+ concurrency,
103
+ timeout,
104
+ only,
105
+ passthrough or None,
86
106
  )
87
107
  try:
88
108
  if sys.stdout.isatty() and sys.stderr.isatty():
@@ -101,6 +121,8 @@ def experiments(
101
121
  passed = len(results) - failed - retried
102
122
  click.echo(f"{passed} passed, {failed} failed.{again}")
103
123
  return
124
+ if passthrough is not None:
125
+ raise click.UsageError("-- ARGS needs run.")
104
126
  if ctx.args:
105
127
  raise click.UsageError(f"Unexpected {ctx.args[0]}. --PARAM=VALUE needs run.")
106
128
  if action not in (None, "runs"):
@@ -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")
@@ -0,0 +1,15 @@
1
+ import shlex
2
+
3
+ from labnotes.runner.flag_name import flag_name
4
+
5
+
6
+ def check_args(params: dict[str, list[object]], command: str, args: list[str]) -> None:
7
+ taken = {flag_name(token) for token in shlex.split(command)}
8
+ for arg in args:
9
+ name = flag_name(arg)
10
+ if name is None:
11
+ continue
12
+ if name in params:
13
+ raise ValueError(f"--{name} is a param. Pick it with --{name}=VALUE.")
14
+ if name in taken:
15
+ raise ValueError(f"--{name} is already in the command.")
@@ -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":
@@ -2,7 +2,7 @@ import asyncio
2
2
  import contextlib
3
3
  import os
4
4
  import time
5
- from collections.abc import Callable
5
+ from collections.abc import Callable, Sequence
6
6
  from pathlib import Path
7
7
 
8
8
  import psutil
@@ -31,10 +31,11 @@ async def execute_run(
31
31
  stop: Callable[[], None] = lambda: None,
32
32
  stopped: Callable[[], bool] = lambda: False,
33
33
  meta: dict[str, object] | None = None,
34
+ args: Sequence[str] = (),
34
35
  *,
35
36
  memory_interval: float,
36
37
  ) -> dict[str, object]:
37
- resolved = shell_command(command, folder, params)
38
+ resolved = shell_command(command, folder, params, args)
38
39
  run: dict[str, object] = {
39
40
  "params": params,
40
41
  "command": resolved,
@@ -45,6 +46,8 @@ async def execute_run(
45
46
  "ended_by": None,
46
47
  "signal": None,
47
48
  }
49
+ if args:
50
+ run["args"] = list(args)
48
51
  if retry_of is not None:
49
52
  run["retry_of"] = retry_of
50
53
  if meta is not None:
@@ -0,0 +1,4 @@
1
+ def flag_name(token: str) -> str | None:
2
+ if not token.startswith("--"):
3
+ return None
4
+ return token.removeprefix("--").partition("=")[0] or None
@@ -1,6 +1,7 @@
1
1
  from pathlib import Path
2
2
 
3
3
  from labnotes.models import ExperimentConfig, LabnotesConfig
4
+ from labnotes.runner.check_args import check_args
4
5
  from labnotes.runner.check_subset import check_subset
5
6
  from labnotes.runner.read_experiment_config import read_experiment_config
6
7
  from labnotes.tables.file_text import file_text
@@ -12,6 +13,7 @@ async def prepare_experiment(
12
13
  experiment_config: Path | None,
13
14
  concurrency: int | None,
14
15
  only: dict[str, str] | None = None,
16
+ args: list[str] | None = None,
15
17
  ) -> tuple[ExperimentConfig, int]:
16
18
  if config.experiment not in (None, name):
17
19
  raise ValueError(
@@ -33,4 +35,6 @@ async def prepare_experiment(
33
35
  )
34
36
  if only:
35
37
  check_subset(experiment.params, only)
38
+ if args:
39
+ check_args(experiment.params, experiment.command, args)
36
40
  return experiment, experiment.concurrency if concurrency is None else concurrency
@@ -38,6 +38,7 @@ async def run_experiment(
38
38
  timeout: float | None = None,
39
39
  retry_seconds: float = RETRY_SECONDS,
40
40
  only: dict[str, str] | None = None,
41
+ args: list[str] | None = None,
41
42
  ) -> list[dict[str, object]]:
42
43
  meta = None
43
44
  if experiment.meta is not None:
@@ -128,43 +129,6 @@ async def run_experiment(
128
129
  report({"event": "stopping run", "run": run})
129
130
  signal_run(run, signal.SIGTERM)
130
131
 
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
132
  ended: dict[str, dict[str, object]] = {}
169
133
  parents: dict[str, str] = {}
170
134
  attempts: dict[str, int] = {}
@@ -179,15 +143,6 @@ async def run_experiment(
179
143
  report({"event": "retried", "run": run, "retry": again, "params": ended[run]})
180
144
  fill()
181
145
 
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
146
  def retry_later(run: str) -> None:
192
147
  tried = attempts.get(run, 0)
193
148
  if tried >= experiment.retries:
@@ -266,6 +221,7 @@ async def run_experiment(
266
221
  stop=lambda: kill(run),
267
222
  stopped=lambda: run in stopped,
268
223
  meta=meta,
224
+ args=args or (),
269
225
  memory_interval=experiment.memory_interval,
270
226
  )
271
227
  del running[run], trees[run]
@@ -277,11 +233,7 @@ async def run_experiment(
277
233
  ):
278
234
  retry_later(run)
279
235
 
280
- listen(
281
- Controls(
282
- interrupt, run_next, set_concurrency, toggle, kill, retry, finish, move
283
- )
284
- )
236
+ listen(Controls(interrupt, set_concurrency, kill, finish))
285
237
  fill()
286
238
  await done.wait()
287
239
  await asyncio.gather(*started)
@@ -0,0 +1,12 @@
1
+ import shlex
2
+ from collections.abc import Sequence
3
+ from pathlib import Path
4
+
5
+ from labnotes.runner.param_args import param_args
6
+
7
+
8
+ def shell_command(
9
+ command: str, folder: Path, params: dict[str, object], args: Sequence[str] = ()
10
+ ) -> str:
11
+ filled = command.replace("{run_dir}", shlex.quote(str(folder)))
12
+ return f"{filled} {shlex.join([*param_args(params), *args])}".rstrip()
@@ -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 =="
@@ -1,9 +0,0 @@
1
- import shlex
2
- from pathlib import Path
3
-
4
- from labnotes.runner.param_args import param_args
5
-
6
-
7
- def shell_command(command: str, folder: Path, params: dict[str, object]) -> str:
8
- filled = command.replace("{run_dir}", shlex.quote(str(folder)))
9
- return f"{filled} {shlex.join(param_args(params))}".rstrip()
File without changes
File without changes
File without changes
File without changes
File without changes