labnotes 0.2.9__tar.gz → 0.2.10__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 (225) hide show
  1. {labnotes-0.2.9 → labnotes-0.2.10}/PKG-INFO +1 -1
  2. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/experiments.py +1 -1
  3. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/experiment_screen.py +3 -42
  4. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/run_app.py +185 -60
  5. labnotes-0.2.10/src/labnotes/tui/run_tui.py +20 -0
  6. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/view_process.py +1 -1
  7. labnotes-0.2.10/src/labnotes/tui/view_switcher.py +53 -0
  8. {labnotes-0.2.9 → labnotes-0.2.10}/testing-conventions.toml +6 -0
  9. labnotes-0.2.9/src/labnotes/tui/run_tui.py +0 -11
  10. {labnotes-0.2.9 → labnotes-0.2.10}/.gitignore +0 -0
  11. {labnotes-0.2.9 → labnotes-0.2.10}/CHANGELOG.md +0 -0
  12. {labnotes-0.2.9 → labnotes-0.2.10}/MIGRATIONS.md +0 -0
  13. {labnotes-0.2.9 → labnotes-0.2.10}/README.md +0 -0
  14. {labnotes-0.2.9 → labnotes-0.2.10}/pyproject.toml +0 -0
  15. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/__init__.py +0 -0
  16. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/__main__.py +0 -0
  17. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/asset_url.py +0 -0
  18. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/check_counts.py +0 -0
  19. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/cli.py +0 -0
  20. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/command_line_group.py +0 -0
  21. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/compose_experiment.py +0 -0
  22. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/config_migration.py +0 -0
  23. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/context.md +0 -0
  24. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/context_guide.py +0 -0
  25. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/copy_experiment.py +0 -0
  26. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/count_groups.py +0 -0
  27. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/count_runs.py +0 -0
  28. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/create_app.py +0 -0
  29. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/create_experiment.py +0 -0
  30. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/create_note.py +0 -0
  31. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/data_html.py +0 -0
  32. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/default_config_text.py +0 -0
  33. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/default_counts.py +0 -0
  34. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/drain_watch.py +0 -0
  35. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/experiment_definition.py +0 -0
  36. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/experiment_notes.py +0 -0
  37. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/experiment_runs.py +0 -0
  38. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/file_changes.py +0 -0
  39. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/format_duration.py +0 -0
  40. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/format_started.py +0 -0
  41. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/format_value.py +0 -0
  42. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/get_state.py +0 -0
  43. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/grid_cell.py +0 -0
  44. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/group_runs.py +0 -0
  45. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/init.py +0 -0
  46. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/invocation_started.py +0 -0
  47. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/is_non_negative_int.py +0 -0
  48. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/is_non_negative_number.py +0 -0
  49. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/is_positive_int.py +0 -0
  50. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/latest_log.py +0 -0
  51. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/list_experiments.py +0 -0
  52. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/list_runs.py +0 -0
  53. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/load_config.py +0 -0
  54. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/load_experiment.py +0 -0
  55. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/load_run.py +0 -0
  56. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/log.py +0 -0
  57. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/log_path.py +0 -0
  58. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/memory_interval.py +0 -0
  59. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/models.py +0 -0
  60. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/nests_runs.py +0 -0
  61. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/note_path.py +0 -0
  62. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/notebook_kernel.py +0 -0
  63. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/output_html.py +0 -0
  64. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/param_names.py +0 -0
  65. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/parse_param_value.py +0 -0
  66. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/parse_params.py +0 -0
  67. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/parse_run_view.py +0 -0
  68. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/parse_subset.py +0 -0
  69. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/pivot_runs.py +0 -0
  70. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/print_rows.py +0 -0
  71. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/python_blocks.py +0 -0
  72. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/read_config.py +0 -0
  73. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/read_path.py +0 -0
  74. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/render_markdown.py +0 -0
  75. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/retries.py +0 -0
  76. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/run_columns.py +0 -0
  77. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/run_output.py +0 -0
  78. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/__init__.py +0 -0
  79. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/cgroup_memory.py +0 -0
  80. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/check_args.py +0 -0
  81. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/check_hook_placeholders.py +0 -0
  82. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/check_subset.py +0 -0
  83. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/claim_run.py +0 -0
  84. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/command_flags.py +0 -0
  85. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/container_cgroup.py +0 -0
  86. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/container_memory.py +0 -0
  87. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/continue_run.py +0 -0
  88. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/describe_event.py +0 -0
  89. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/docker_output.py +0 -0
  90. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/execute_run.py +0 -0
  91. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/exit_signal.py +0 -0
  92. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/fill_dirs.py +0 -0
  93. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/fill_experiment_env.py +0 -0
  94. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/finalize_lost_runs.py +0 -0
  95. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/first_invocation.py +0 -0
  96. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/flag_name.py +0 -0
  97. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/group_args.py +0 -0
  98. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/hold_experiment.py +0 -0
  99. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/in_subset.py +0 -0
  100. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/invocation_hook.py +0 -0
  101. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/invocation_meta.py +0 -0
  102. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/new_run_id.py +0 -0
  103. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  104. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/override_flags.py +0 -0
  105. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/param_args.py +0 -0
  106. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/param_grid.py +0 -0
  107. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/placeholders.py +0 -0
  108. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/plan_runs.py +0 -0
  109. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/prepare_experiment.py +0 -0
  110. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/project_snapshot.py +0 -0
  111. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/read_experiment_config.py +0 -0
  112. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/record_memory.py +0 -0
  113. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/run_experiment.py +0 -0
  114. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/run_folder.py +0 -0
  115. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/run_git.py +0 -0
  116. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/run_headless.py +0 -0
  117. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/run_locked.py +0 -0
  118. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/runs_parent.py +0 -0
  119. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/shell_command.py +0 -0
  120. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/signal_tree.py +0 -0
  121. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/start_experiment.py +0 -0
  122. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/stop_then_kill.py +0 -0
  123. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/untracked_diff.py +0 -0
  124. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/wait_for_tree.py +0 -0
  125. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runner/write_run_json.py +0 -0
  126. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/runs_view_command.py +0 -0
  127. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/save_conflict_copy.py +0 -0
  128. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/save_note.py +0 -0
  129. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/saved_outputs.py +0 -0
  130. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/serve_app.py +0 -0
  131. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/server.py +0 -0
  132. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/set_state.py +0 -0
  133. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/shared_params.py +0 -0
  134. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/sort_href.py +0 -0
  135. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/sql_quote.py +0 -0
  136. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/state_path.py +0 -0
  137. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/static/elapsed.js +0 -0
  138. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/static/experiment.js +0 -0
  139. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/static/labnotes.css +0 -0
  140. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/static/local-time.js +0 -0
  141. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/static/new-note.js +0 -0
  142. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/static/note-edit.js +0 -0
  143. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/static/note-editor.js +0 -0
  144. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/static/row-link.js +0 -0
  145. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/static/run-cell.js +0 -0
  146. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/static/run-output.js +0 -0
  147. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/static/view-toggle.js +0 -0
  148. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/summarize_config.py +0 -0
  149. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/__init__.py +0 -0
  150. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/experiment_rows.py +0 -0
  151. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/experiment_run_ids.py +0 -0
  152. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/experiment_toml.py +0 -0
  153. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/experiments_ddl.py +0 -0
  154. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/file_prefix.py +0 -0
  155. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/file_text.py +0 -0
  156. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/iso_time.py +0 -0
  157. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/list_experiment_runs.py +0 -0
  158. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/list_resumable_runs.py +0 -0
  159. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/note_text.py +0 -0
  160. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/open_tables.py +0 -0
  161. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/path_glob.py +0 -0
  162. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/path_pattern.py +0 -0
  163. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/read_blocks.py +0 -0
  164. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/read_experiment.py +0 -0
  165. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/read_file.py +0 -0
  166. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/read_log.py +0 -0
  167. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/read_memory.py +0 -0
  168. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/read_run.py +0 -0
  169. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/read_run_file.py +0 -0
  170. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/runs_columns.py +0 -0
  171. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tables/summarize_experiments.py +0 -0
  172. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/templates/base.html +0 -0
  173. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/templates/editor_imports.html +0 -0
  174. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/templates/experiment.html +0 -0
  175. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/templates/index.html +0 -0
  176. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/templates/note.html +0 -0
  177. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/templates/run.html +0 -0
  178. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/__init__.py +0 -0
  179. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/browse_app.py +0 -0
  180. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/browse_tui.py +0 -0
  181. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/check_view_response.py +0 -0
  182. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/config.py +0 -0
  183. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/dim.py +0 -0
  184. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/experiments_screen.py +0 -0
  185. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/fill_table.py +0 -0
  186. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/format_cell.py +0 -0
  187. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/format_elapsed.py +0 -0
  188. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/guard_view.py +0 -0
  189. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/leave_prompt.py +0 -0
  190. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/load_view.py +0 -0
  191. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/log_block.py +0 -0
  192. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/log_blocks.py +0 -0
  193. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/native_rows.py +0 -0
  194. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/reload_on_change.py +0 -0
  195. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/run_cells.py +0 -0
  196. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/run_output.py +0 -0
  197. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/run_panel.py +0 -0
  198. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/runs_browser.py +0 -0
  199. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/runs_table.py +0 -0
  200. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/runs_view.py +0 -0
  201. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/stopped_title.py +0 -0
  202. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/table_columns.py +0 -0
  203. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/table_rows.py +0 -0
  204. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/view_column.py +0 -0
  205. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/tui/view_pane.py +0 -0
  206. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/utils/__init__.py +0 -0
  207. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/utils/format_bytes.py +0 -0
  208. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/utils/process_alive.py +0 -0
  209. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/utils/run_usage.py +0 -0
  210. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/utils/tree_rss.py +0 -0
  211. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/utils/tree_usage.py +0 -0
  212. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/view/__init__.py +0 -0
  213. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/view/answer.py +0 -0
  214. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/view/build.py +0 -0
  215. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/view/changed.py +0 -0
  216. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/view/experiment.py +0 -0
  217. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/view/respond.py +0 -0
  218. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/view/serve.py +0 -0
  219. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/view_specs.py +0 -0
  220. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/wait_for_note.py +0 -0
  221. {labnotes-0.2.9 → labnotes-0.2.10}/src/labnotes/watch_rows.py +0 -0
  222. {labnotes-0.2.9 → labnotes-0.2.10}/tests/conftest.py +0 -0
  223. {labnotes-0.2.9 → labnotes-0.2.10}/tests/e2e/conftest.py +0 -0
  224. {labnotes-0.2.9 → labnotes-0.2.10}/tests/integration/conftest.py +0 -0
  225. {labnotes-0.2.9 → labnotes-0.2.10}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.2.9
3
+ Version: 0.2.10
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>
@@ -168,7 +168,7 @@ def experiments(
168
168
  )
169
169
  try:
170
170
  if sys.stdout.isatty() and sys.stderr.isatty():
171
- results = run_tui(start, name)
171
+ results = run_tui(start, name, config, experiment_config)
172
172
  else:
173
173
  results = run_headless(start)
174
174
  except ValueError as error:
@@ -1,4 +1,3 @@
1
- from collections.abc import Mapping
2
1
  from types import MappingProxyType
3
2
  from typing import Any
4
3
 
@@ -17,15 +16,15 @@ from labnotes.tui.native_rows import native_rows
17
16
  from labnotes.tui.reload_on_change import reload_on_change
18
17
  from labnotes.tui.stopped_title import stopped_title
19
18
  from labnotes.tui.view_pane import ViewPane
19
+ from labnotes.tui.view_switcher import ViewSwitcher
20
20
  from labnotes.view.experiment import Experiment
21
21
  from labnotes.watch_rows import watch_rows
22
22
 
23
23
  WATCHED = {"runs", "experiment_configs"}
24
24
  BUILT_IN = "labnotes.tui.runs_browser:RunsBrowser"
25
- MAX_VIEWS = 8
26
25
 
27
26
 
28
- class ExperimentScreen(Screen[None]):
27
+ class ExperimentScreen(ViewSwitcher, Screen[None]):
29
28
  BINDINGS = [Binding("escape", "back", "Back")]
30
29
 
31
30
  def __init__(
@@ -82,7 +81,7 @@ class ExperimentScreen(Screen[None]):
82
81
  self.deliver(None)
83
82
  return
84
83
  if not self.found:
85
- await self.add_views(loaded["views"])
84
+ await self.add_views(loaded["views"], self.config.root)
86
85
  self.found = True
87
86
  self.sub_title = str(loaded["display_name"])
88
87
  self.shown = Experiment(
@@ -93,49 +92,11 @@ class ExperimentScreen(Screen[None]):
93
92
  )
94
93
  self.deliver(self.shown)
95
94
 
96
- async def add_views(self, specs: Mapping[str, str]) -> None:
97
- names = list(specs)[:MAX_VIEWS]
98
- if not names:
99
- return
100
- panes = [
101
- ViewPane(name, specs[name], self.config.root, id=f"view-{number}")
102
- for number, name in enumerate(names, 2)
103
- ]
104
- for pane in panes:
105
- pane.display = False
106
- await self.query_one(ContentSwitcher).mount_all(panes)
107
- # Screen bindings are fixed per class; each screen's view names differ.
108
- for number, name in enumerate(["runs", *names], 1):
109
- self._bindings.bind(str(number), f"show_view({number})", name)
110
- self._bindings.bind("0", "show_view(1)")
111
- self.refresh_bindings()
112
-
113
- def deliver(
114
- self, experiment: Experiment | None, paths: frozenset[str] = frozenset()
115
- ) -> None:
116
- for pane in self.query(ViewPane):
117
- pane.deliver(experiment, paths)
118
-
119
95
  async def watch_files(self) -> None:
120
96
  async for row in watch_rows(self.tables, {"run_files"}, self.experiment_name):
121
97
  if self.shown is not None:
122
98
  self.deliver(self.shown, frozenset({str(row["path"])}))
123
99
 
124
- def action_show_view(self, number: int) -> None:
125
- switcher = self.query_one(ContentSwitcher)
126
- if switcher.current is not None and self.focused is not None:
127
- self.left[switcher.current] = self.focused
128
- switcher.current = f"view-{number}"
129
- self.call_after_refresh(self.refocus, switcher.current)
130
-
131
- def refocus(self, current: str) -> None:
132
- widget = self.left.get(current)
133
- if widget is not None and widget.focusable:
134
- widget.focus()
135
- return
136
- self.set_focus(None)
137
- self.focus_next()
138
-
139
100
  def action_back(self) -> None:
140
101
  if self.query_one(ContentSwitcher).current == "view-1":
141
102
  self.app.pop_screen()
@@ -1,38 +1,58 @@
1
1
  import signal
2
2
  import time
3
- from collections.abc import Sequence
3
+ import tomllib
4
+ from collections.abc import Mapping, Sequence
4
5
  from pathlib import Path
6
+ from types import MappingProxyType
7
+ from typing import Any, cast
5
8
 
6
9
  from rich.text import Text
10
+ from starlette.datastructures import QueryParams
7
11
  from textual import work
8
12
  from textual.app import App, ComposeResult
9
13
  from textual.binding import Binding
10
- from textual.widgets import DataTable, Footer, Header, Input, Static
14
+ from textual.css.query import NoMatches
15
+ from textual.widget import Widget
16
+ from textual.widgets import ContentSwitcher, DataTable, Footer, Header, Input, Static
11
17
 
12
- from labnotes.utils.format_bytes import format_bytes
13
- from labnotes.models import Controls, Start
18
+ from labnotes.compose_experiment import compose_experiment
19
+ from labnotes.load_experiment import load_experiment
20
+ from labnotes.models import Controls, LabnotesConfig, Start
14
21
  from labnotes.retries import RETRY_EXIT_CODE
15
- from labnotes.utils.run_usage import run_usage
16
22
  from labnotes.runner.describe_event import describe_event
17
- from labnotes.runner.stop_then_kill import GRACE_SECONDS, stop_then_kill
18
23
  from labnotes.runner.param_args import param_args
24
+ from labnotes.runner.stop_then_kill import GRACE_SECONDS, stop_then_kill
25
+ from labnotes.tables.open_tables import open_tables
26
+ from labnotes.tui.config import RUN_COLUMNS
19
27
  from labnotes.tui.dim import dim
20
28
  from labnotes.tui.format_elapsed import format_elapsed
21
29
  from labnotes.tui.leave_prompt import LeavePrompt
22
30
  from labnotes.tui.log_blocks import LogBlocks
31
+ from labnotes.tui.native_rows import native_rows
32
+ from labnotes.tui.reload_on_change import reload_on_change
23
33
  from labnotes.tui.run_output import RunOutput
24
34
  from labnotes.tui.runs_table import RunsTable
35
+ from labnotes.tui.view_pane import ViewPane
36
+ from labnotes.tui.view_switcher import ViewSwitcher
37
+ from labnotes.utils.format_bytes import format_bytes
38
+ from labnotes.utils.run_usage import run_usage
39
+ from labnotes.view.experiment import Experiment
40
+ from labnotes.view_specs import view_specs
41
+ from labnotes.watch_rows import watch_rows
25
42
 
26
43
  PENDING, PASSED, FAILED = "○", "✓", "✗"
27
44
  SPINNER = "|/-\\"
28
45
  SKIPPED = "–"
29
46
  VIEWING_ACTIONS = {"leave", "quit", "interrupt", "command_palette"}
47
+ BUILT_IN = "labnotes.tui.run_app:RunLiveView"
30
48
 
31
49
 
32
- class RunApp(App[None]):
33
- CSS = "#concurrency { dock: right; width: auto; padding: 0 1; }"
50
+ class RunLiveView(Widget):
51
+ DEFAULT_CSS = """
52
+ RunLiveView { height: 1fr; }
53
+ RunLiveView RunsTable { height: 1fr; }
54
+ """
34
55
  BINDINGS = [
35
- Binding("ctrl+c", "interrupt", "Quit"),
36
56
  Binding("x", "kill_run", "Stop run"),
37
57
  Binding("slash", "filter", "Filter", key_display="/"),
38
58
  Binding("escape", "leave", "Back"),
@@ -40,13 +60,9 @@ class RunApp(App[None]):
40
60
  Binding("minus", "concurrency(-1)", "Fewer at once", key_display="-"),
41
61
  ]
42
62
 
43
- def __init__(self, start: Start, title: str, columns: Sequence[str]) -> None:
63
+ def __init__(self, columns: Sequence[str] = ()) -> None:
44
64
  super().__init__()
45
- self.columns = tuple(columns)
46
- self.start = start
47
- self.title = title
48
- self.results: list[dict[str, object]] = []
49
- self.error: Exception | None = None
65
+ self.columns = tuple(columns or RUN_COLUMNS)
50
66
  self.controls = Controls(
51
67
  lambda: None, lambda count: None, lambda run: None, lambda: None
52
68
  )
@@ -67,13 +83,11 @@ class RunApp(App[None]):
67
83
  self.unmeasured: dict[str, None] = {}
68
84
 
69
85
  def compose(self) -> ComposeResult:
70
- yield Header()
71
86
  yield RunsTable(id="runs", cursor_type="row")
72
87
  yield LogBlocks(id="blocks")
73
88
  yield RunOutput(id="output")
74
89
  yield Input(id="filter", placeholder="Filter params")
75
90
  yield Static(id="status")
76
- yield Footer()
77
91
 
78
92
  def on_mount(self) -> None:
79
93
  table = self.query_one(DataTable)
@@ -81,32 +95,16 @@ class RunApp(App[None]):
81
95
  table.add_column(column, key=column)
82
96
  self.query_one(Input).display = False
83
97
  self.query_one(RunOutput).display = False
84
- self.query_one(Header).mount(Static(id="concurrency"))
85
98
  table.set_interval(0.1, self.spin)
86
-
87
- def on_ready(self) -> None:
88
99
  self.set_interval(1, self.tick)
89
100
  self.set_interval(0.25, self.follow)
90
- self.run_worker(self.drive())
91
-
92
- async def drive(self) -> None:
93
- try:
94
- self.results = await self.start(self.show_event, self.listen)
95
- except Exception as error:
96
- self.error = error
97
- self.exit()
98
-
99
- def listen(self, controls: Controls) -> None:
100
- self.controls = controls
101
- stop_then_kill(self.stop_runs, signal.SIGTERM)
102
- stop_then_kill(self.stop_runs, signal.SIGHUP)
103
101
 
104
102
  def show_event(self, event: dict[str, object]) -> None:
105
103
  table = self.query_one(DataTable)
106
104
  run = str(event.get("run"))
107
105
  match event["event"]:
108
106
  case "planned":
109
- self.title = str(event["title"])
107
+ self.app.title = str(event["title"])
110
108
  for planned in event["runs"]: # type: ignore[attr-defined]
111
109
  args = " ".join(param_args(planned["params"]))
112
110
  self.cells[planned["run"]] = self.waiting_cells(
@@ -219,7 +217,7 @@ class RunApp(App[None]):
219
217
  table.update_cell(run, column, cell, update_width=True)
220
218
 
221
219
  def check_action(self, action: str, parameters: tuple[object, ...]) -> bool:
222
- if isinstance(self.screen, LeavePrompt):
220
+ if isinstance(self.app.screen, LeavePrompt):
223
221
  return False
224
222
  if action == "leave" and self.viewing is None:
225
223
  return self.query_one(Input).display
@@ -227,7 +225,9 @@ class RunApp(App[None]):
227
225
 
228
226
  def show_concurrency(self, event: dict[str, object]) -> None:
229
227
  self.concurrency = int(event["concurrency"]) # type: ignore[call-overload]
230
- self.query_one("#concurrency", Static).update(f"{self.concurrency} at a time")
228
+ self.app.query_one("#concurrency", Static).update(
229
+ f"{self.concurrency} at a time"
230
+ )
231
231
 
232
232
  def tick(self) -> None:
233
233
  now = time.monotonic()
@@ -271,28 +271,6 @@ class RunApp(App[None]):
271
271
  def show(self, text: str) -> None:
272
272
  self.query_one("#status", Static).update(text)
273
273
 
274
- def stop_runs(self) -> None:
275
- if self.idle:
276
- self.controls.finish()
277
- else:
278
- self.controls.interrupt()
279
-
280
- def action_interrupt(self) -> None:
281
- if self.started:
282
- self.push_screen(LeavePrompt(len(self.started)), self.answer)
283
- elif self.idle:
284
- self.controls.finish()
285
- else:
286
- self.exit()
287
-
288
- async def action_quit(self) -> None:
289
- self.action_interrupt()
290
-
291
- def answer(self, choice: str | None) -> None:
292
- if choice == "stop":
293
- self.controls.interrupt()
294
- self.set_timer(GRACE_SECONDS, self.controls.interrupt)
295
-
296
274
  def cursor_run(self) -> str | None:
297
275
  table = self.query_one(DataTable)
298
276
  if not table.row_count:
@@ -322,7 +300,7 @@ class RunApp(App[None]):
322
300
  self.query_one(RunOutput).reset()
323
301
  self.query_one(DataTable).display = False
324
302
  self.query_one(RunOutput).display = True
325
- self.sub_title = run
303
+ self.app.sub_title = run
326
304
  self.show("")
327
305
  self.refresh_bindings()
328
306
  self.follow()
@@ -338,7 +316,7 @@ class RunApp(App[None]):
338
316
  self.query_one(LogBlocks).display = False
339
317
  self.query_one(RunOutput).display = False
340
318
  self.query_one(DataTable).display = True
341
- self.sub_title = ""
319
+ self.app.sub_title = ""
342
320
  self.refresh_bindings()
343
321
  if self.idle:
344
322
  self.show_summary()
@@ -359,3 +337,150 @@ class RunApp(App[None]):
359
337
  event.input.display = bool(event.value)
360
338
  self.query_one(DataTable).focus()
361
339
  self.refresh_bindings()
340
+
341
+
342
+ class RunApp(ViewSwitcher, App[None]):
343
+ CSS = "#concurrency { dock: right; width: auto; padding: 0 1; }"
344
+ BINDINGS = [Binding("ctrl+c", "interrupt", "Quit")]
345
+
346
+ def __init__(
347
+ self,
348
+ start: Start,
349
+ title: str,
350
+ columns: Sequence[str],
351
+ config: LabnotesConfig | None = None,
352
+ experiment_config: Path | None = None,
353
+ ) -> None:
354
+ super().__init__()
355
+ self.start = start
356
+ self.title = title
357
+ self.config = config
358
+ self.experiment_config = experiment_config
359
+ self.experiment_name = title
360
+ self.view_specs = self.configured_views()
361
+ self.results: list[dict[str, object]] = []
362
+ self.error: Exception | None = None
363
+ self.columns = tuple(columns)
364
+ self.body = RunLiveView(columns)
365
+ self.body.id = "view-1"
366
+ self.body_ready = True
367
+ self.loaded_views = False
368
+ self.shown: Experiment | None = None
369
+ self.left: dict[str, Widget] = {}
370
+ self.controls = self.body.controls
371
+
372
+ def compose(self) -> ComposeResult:
373
+ yield Header()
374
+ with ContentSwitcher(initial="view-1"):
375
+ if self.view_specs:
376
+ assert self.config is not None
377
+ yield ViewPane("runs", BUILT_IN, self.config.root, id="view-1")
378
+ else:
379
+ yield self.body
380
+ yield Footer()
381
+
382
+ async def on_mount(self) -> None:
383
+ await self.query_one(Header).mount(Static(id="concurrency"))
384
+ if self.view_specs:
385
+ assert self.config is not None
386
+ self._bindings.bind("escape", "leave", "Back")
387
+ try:
388
+ self.body = self.query_one(RunLiveView)
389
+ except NoMatches:
390
+ # ViewPane already displays the built-in view's loading error.
391
+ self.body_ready = False
392
+ tables = open_tables(self.config, run_files=True)
393
+ self.run_worker(
394
+ reload_on_change(
395
+ watch_rows(
396
+ tables, {"runs", "experiment_configs"}, self.experiment_name
397
+ ),
398
+ lambda: self.load_data(tables),
399
+ lambda error: None,
400
+ ),
401
+ exit_on_error=False,
402
+ )
403
+ self.run_worker(self.watch_files(tables), exit_on_error=False)
404
+ self.run_worker(self.drive())
405
+
406
+ def configured_views(self) -> dict[str, str]:
407
+ if self.config is None:
408
+ return {}
409
+ folder = self.config.experiments.replace("{experiment}", self.experiment_name)
410
+ path = self.experiment_config or self.config.root / folder / "experiment.toml"
411
+ try:
412
+ source = path.read_text()
413
+ layer = tomllib.loads(source)
414
+ except (OSError, tomllib.TOMLDecodeError):
415
+ # The runner reports missing or invalid config through its normal path.
416
+ layer = {}
417
+ return view_specs(compose_experiment(self.config.defaults, layer))
418
+
419
+ async def load_data(self, tables: Any) -> None:
420
+ assert self.config is not None
421
+ loaded = await load_experiment(
422
+ tables, self.config.root, self.experiment_name, QueryParams()
423
+ )
424
+ if loaded is None:
425
+ return
426
+ if not self.loaded_views:
427
+ await self.add_views(
428
+ cast(Mapping[str, str], loaded["views"]), self.config.root
429
+ )
430
+ self.loaded_views = True
431
+ self.shown = Experiment(
432
+ self.config.root,
433
+ self.experiment_name,
434
+ native_rows(loaded),
435
+ MappingProxyType(cast(dict[str, Any], loaded["config"] or {})),
436
+ )
437
+ self.deliver(self.shown)
438
+
439
+ async def watch_files(self, tables: Any) -> None:
440
+ async for row in watch_rows(tables, {"run_files"}, self.experiment_name):
441
+ if self.shown is not None:
442
+ self.deliver(self.shown, frozenset({str(row["path"])}))
443
+
444
+ async def drive(self) -> None:
445
+ try:
446
+ self.results = await self.start(self.show_event, self.listen)
447
+ except Exception as error:
448
+ self.error = error
449
+ self.exit()
450
+
451
+ def listen(self, controls: Controls) -> None:
452
+ self.controls = controls
453
+ if self.body_ready:
454
+ self.body.controls = controls
455
+ stop_then_kill(self.stop_runs, signal.SIGTERM)
456
+ stop_then_kill(self.stop_runs, signal.SIGHUP)
457
+
458
+ def show_event(self, event: dict[str, object]) -> None:
459
+ if self.body_ready:
460
+ self.body.show_event(event)
461
+
462
+ def stop_runs(self) -> None:
463
+ if self.body_ready and self.body.idle:
464
+ self.controls.finish()
465
+ else:
466
+ self.controls.interrupt()
467
+
468
+ def action_interrupt(self) -> None:
469
+ if self.body_ready and self.body.started:
470
+ self.push_screen(LeavePrompt(len(self.body.started)), self.answer)
471
+ elif self.body_ready and self.body.idle:
472
+ self.controls.finish()
473
+ else:
474
+ self.exit()
475
+
476
+ async def action_quit(self) -> None:
477
+ self.action_interrupt()
478
+
479
+ def answer(self, choice: str | None) -> None:
480
+ if choice == "stop":
481
+ self.controls.interrupt()
482
+ self.set_timer(GRACE_SECONDS, self.controls.interrupt)
483
+
484
+ def action_leave(self) -> None:
485
+ if self.query_one(ContentSwitcher).current != "view-1":
486
+ self.action_show_view(1)
@@ -0,0 +1,20 @@
1
+ from pathlib import Path
2
+
3
+ from labnotes.models import LabnotesConfig, Start
4
+ from labnotes.tui.config import RUN_COLUMNS
5
+ from labnotes.tui.run_app import RunApp
6
+
7
+
8
+ def run_tui(
9
+ start: Start,
10
+ title: str,
11
+ config: LabnotesConfig | None = None,
12
+ experiment_config: Path | None = None,
13
+ ) -> list[dict[str, object]]:
14
+ app = RunApp(
15
+ start, title, RUN_COLUMNS, config=config, experiment_config=experiment_config
16
+ )
17
+ app.run()
18
+ if app.error is not None:
19
+ raise app.error
20
+ return app.results
@@ -13,7 +13,7 @@ GRACE_SECONDS = 1.0
13
13
 
14
14
 
15
15
  class ViewProcess:
16
- """Hold one view worker for the life of labnotes, one request at a time."""
16
+ """Hold one view worker for a screen's life, one request at a time."""
17
17
 
18
18
  def __init__(self, command: str, root: Path) -> None:
19
19
  self.command = command
@@ -0,0 +1,53 @@
1
+ from collections.abc import Mapping
2
+ from pathlib import Path
3
+
4
+ from textual.widget import Widget
5
+ from textual.widgets import ContentSwitcher
6
+
7
+ from labnotes.tui.view_pane import ViewPane
8
+ from labnotes.view.experiment import Experiment
9
+
10
+ MAX_VIEWS = 8
11
+
12
+
13
+ class ViewSwitcher:
14
+ """Mount and switch the same view panes in browse and run screens."""
15
+
16
+ left: dict[str, Widget]
17
+
18
+ async def add_views(self, specs: Mapping[str, str], root: Path) -> None:
19
+ names = list(specs)[:MAX_VIEWS]
20
+ if not names:
21
+ return
22
+ panes = [
23
+ ViewPane(name, specs[name], root, id=f"view-{number}")
24
+ for number, name in enumerate(names, 2)
25
+ ]
26
+ for pane in panes:
27
+ pane.display = False
28
+ await self.query_one(ContentSwitcher).mount_all(panes) # type: ignore[attr-defined]
29
+ for number, name in enumerate(["runs", *names], 1):
30
+ self._bindings.bind(str(number), f"show_view({number})", name) # type: ignore[attr-defined]
31
+ self._bindings.bind("0", "show_view(1)") # type: ignore[attr-defined]
32
+ self.refresh_bindings() # type: ignore[attr-defined]
33
+
34
+ def deliver(
35
+ self, experiment: Experiment | None, paths: frozenset[str] = frozenset()
36
+ ) -> None:
37
+ for pane in self.query(ViewPane): # type: ignore[attr-defined]
38
+ pane.deliver(experiment, paths)
39
+
40
+ def action_show_view(self, number: int) -> None:
41
+ switcher = self.query_one(ContentSwitcher) # type: ignore[attr-defined]
42
+ if switcher.current is not None and self.focused is not None: # type: ignore[attr-defined]
43
+ self.left[switcher.current] = self.focused # type: ignore[attr-defined]
44
+ switcher.current = f"view-{number}"
45
+ self.call_after_refresh(self.refocus, switcher.current) # type: ignore[attr-defined]
46
+
47
+ def refocus(self, current: str) -> None:
48
+ widget = self.left.get(current)
49
+ if widget is not None and widget.focusable:
50
+ widget.focus()
51
+ return
52
+ self.set_focus(None) # type: ignore[attr-defined]
53
+ self.screen.focus_next() # type: ignore[attr-defined]
@@ -40,6 +40,12 @@ rules = ["mutation"]
40
40
  lines = [221]
41
41
  reason = "the leave action name is the string literal from the Binding, which CPython interns, so is equals =="
42
42
 
43
+ [[python.exempt]]
44
+ path = "src/labnotes/tui/run_app.py"
45
+ rules = ["mutation"]
46
+ lines = [485]
47
+ reason = "the switcher contains only view-1 through view-9; every non-built-in view id sorts above view-1, so > equals !="
48
+
43
49
  [[python.exempt]]
44
50
  path = "src/labnotes/runner/hold_experiment.py"
45
51
  rules = ["mutation"]
@@ -1,11 +0,0 @@
1
- from labnotes.models import Start
2
- from labnotes.tui.config import RUN_COLUMNS
3
- from labnotes.tui.run_app import RunApp
4
-
5
-
6
- def run_tui(start: Start, title: str) -> list[dict[str, object]]:
7
- app = RunApp(start, title, RUN_COLUMNS)
8
- app.run()
9
- if app.error is not None:
10
- raise app.error
11
- return app.results
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes