labnotes 0.2.8__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 (231) hide show
  1. {labnotes-0.2.8 → labnotes-0.2.10}/PKG-INFO +1 -1
  2. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/experiments.py +1 -1
  3. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/load_experiment.py +8 -2
  4. labnotes-0.2.10/src/labnotes/param_names.py +9 -0
  5. labnotes-0.2.10/src/labnotes/run_columns.py +7 -0
  6. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/read_experiment_config.py +10 -4
  7. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/browse_app.py +2 -16
  8. labnotes-0.2.10/src/labnotes/tui/experiment_screen.py +104 -0
  9. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/experiments_screen.py +2 -10
  10. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/format_cell.py +4 -2
  11. labnotes-0.2.10/src/labnotes/tui/guard_view.py +35 -0
  12. labnotes-0.2.10/src/labnotes/tui/load_view.py +27 -0
  13. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/native_rows.py +3 -3
  14. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/run_app.py +185 -60
  15. labnotes-0.2.10/src/labnotes/tui/run_cells.py +12 -0
  16. labnotes-0.2.8/src/labnotes/tui/run_screen.py → labnotes-0.2.10/src/labnotes/tui/run_panel.py +5 -8
  17. labnotes-0.2.10/src/labnotes/tui/run_tui.py +20 -0
  18. labnotes-0.2.10/src/labnotes/tui/runs_browser.py +132 -0
  19. labnotes-0.2.10/src/labnotes/tui/table_columns.py +8 -0
  20. labnotes-0.2.10/src/labnotes/tui/table_rows.py +12 -0
  21. labnotes-0.2.10/src/labnotes/tui/view_pane.py +54 -0
  22. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/view_process.py +1 -1
  23. labnotes-0.2.10/src/labnotes/tui/view_switcher.py +53 -0
  24. labnotes-0.2.10/src/labnotes/view/__init__.py +22 -0
  25. labnotes-0.2.10/src/labnotes/view/changed.py +19 -0
  26. labnotes-0.2.10/src/labnotes/view/experiment.py +20 -0
  27. labnotes-0.2.10/src/labnotes/view_specs.py +10 -0
  28. {labnotes-0.2.8 → labnotes-0.2.10}/testing-conventions.toml +9 -15
  29. labnotes-0.2.8/src/labnotes/tui/experiment_screen.py +0 -154
  30. labnotes-0.2.8/src/labnotes/tui/group_cells.py +0 -36
  31. labnotes-0.2.8/src/labnotes/tui/invocation_columns.py +0 -9
  32. labnotes-0.2.8/src/labnotes/tui/invocation_rows.py +0 -32
  33. labnotes-0.2.8/src/labnotes/tui/run_cells.py +0 -22
  34. labnotes-0.2.8/src/labnotes/tui/run_tui.py +0 -11
  35. labnotes-0.2.8/src/labnotes/view/__init__.py +0 -3
  36. {labnotes-0.2.8 → labnotes-0.2.10}/.gitignore +0 -0
  37. {labnotes-0.2.8 → labnotes-0.2.10}/CHANGELOG.md +0 -0
  38. {labnotes-0.2.8 → labnotes-0.2.10}/MIGRATIONS.md +0 -0
  39. {labnotes-0.2.8 → labnotes-0.2.10}/README.md +0 -0
  40. {labnotes-0.2.8 → labnotes-0.2.10}/pyproject.toml +0 -0
  41. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/__init__.py +0 -0
  42. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/__main__.py +0 -0
  43. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/asset_url.py +0 -0
  44. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/check_counts.py +0 -0
  45. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/cli.py +0 -0
  46. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/command_line_group.py +0 -0
  47. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/compose_experiment.py +0 -0
  48. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/config_migration.py +0 -0
  49. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/context.md +0 -0
  50. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/context_guide.py +0 -0
  51. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/copy_experiment.py +0 -0
  52. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/count_groups.py +0 -0
  53. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/count_runs.py +0 -0
  54. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/create_app.py +0 -0
  55. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/create_experiment.py +0 -0
  56. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/create_note.py +0 -0
  57. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/data_html.py +0 -0
  58. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/default_config_text.py +0 -0
  59. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/default_counts.py +0 -0
  60. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/drain_watch.py +0 -0
  61. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/experiment_definition.py +0 -0
  62. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/experiment_notes.py +0 -0
  63. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/experiment_runs.py +0 -0
  64. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/file_changes.py +0 -0
  65. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/format_duration.py +0 -0
  66. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/format_started.py +0 -0
  67. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/format_value.py +0 -0
  68. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/get_state.py +0 -0
  69. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/grid_cell.py +0 -0
  70. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/group_runs.py +0 -0
  71. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/init.py +0 -0
  72. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/invocation_started.py +0 -0
  73. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/is_non_negative_int.py +0 -0
  74. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/is_non_negative_number.py +0 -0
  75. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/is_positive_int.py +0 -0
  76. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/latest_log.py +0 -0
  77. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/list_experiments.py +0 -0
  78. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/list_runs.py +0 -0
  79. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/load_config.py +0 -0
  80. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/load_run.py +0 -0
  81. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/log.py +0 -0
  82. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/log_path.py +0 -0
  83. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/memory_interval.py +0 -0
  84. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/models.py +0 -0
  85. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/nests_runs.py +0 -0
  86. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/note_path.py +0 -0
  87. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/notebook_kernel.py +0 -0
  88. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/output_html.py +0 -0
  89. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/parse_param_value.py +0 -0
  90. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/parse_params.py +0 -0
  91. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/parse_run_view.py +0 -0
  92. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/parse_subset.py +0 -0
  93. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/pivot_runs.py +0 -0
  94. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/print_rows.py +0 -0
  95. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/python_blocks.py +0 -0
  96. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/read_config.py +0 -0
  97. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/read_path.py +0 -0
  98. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/render_markdown.py +0 -0
  99. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/retries.py +0 -0
  100. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/run_output.py +0 -0
  101. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/__init__.py +0 -0
  102. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/cgroup_memory.py +0 -0
  103. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/check_args.py +0 -0
  104. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/check_hook_placeholders.py +0 -0
  105. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/check_subset.py +0 -0
  106. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/claim_run.py +0 -0
  107. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/command_flags.py +0 -0
  108. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/container_cgroup.py +0 -0
  109. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/container_memory.py +0 -0
  110. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/continue_run.py +0 -0
  111. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/describe_event.py +0 -0
  112. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/docker_output.py +0 -0
  113. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/execute_run.py +0 -0
  114. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/exit_signal.py +0 -0
  115. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/fill_dirs.py +0 -0
  116. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/fill_experiment_env.py +0 -0
  117. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/finalize_lost_runs.py +0 -0
  118. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/first_invocation.py +0 -0
  119. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/flag_name.py +0 -0
  120. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/group_args.py +0 -0
  121. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/hold_experiment.py +0 -0
  122. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/in_subset.py +0 -0
  123. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/invocation_hook.py +0 -0
  124. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/invocation_meta.py +0 -0
  125. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/new_run_id.py +0 -0
  126. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  127. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/override_flags.py +0 -0
  128. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/param_args.py +0 -0
  129. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/param_grid.py +0 -0
  130. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/placeholders.py +0 -0
  131. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/plan_runs.py +0 -0
  132. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/prepare_experiment.py +0 -0
  133. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/project_snapshot.py +0 -0
  134. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/record_memory.py +0 -0
  135. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/run_experiment.py +0 -0
  136. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/run_folder.py +0 -0
  137. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/run_git.py +0 -0
  138. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/run_headless.py +0 -0
  139. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/run_locked.py +0 -0
  140. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/runs_parent.py +0 -0
  141. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/shell_command.py +0 -0
  142. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/signal_tree.py +0 -0
  143. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/start_experiment.py +0 -0
  144. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/stop_then_kill.py +0 -0
  145. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/untracked_diff.py +0 -0
  146. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/wait_for_tree.py +0 -0
  147. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runner/write_run_json.py +0 -0
  148. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/runs_view_command.py +0 -0
  149. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/save_conflict_copy.py +0 -0
  150. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/save_note.py +0 -0
  151. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/saved_outputs.py +0 -0
  152. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/serve_app.py +0 -0
  153. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/server.py +0 -0
  154. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/set_state.py +0 -0
  155. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/shared_params.py +0 -0
  156. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/sort_href.py +0 -0
  157. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/sql_quote.py +0 -0
  158. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/state_path.py +0 -0
  159. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/elapsed.js +0 -0
  160. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/experiment.js +0 -0
  161. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/labnotes.css +0 -0
  162. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/local-time.js +0 -0
  163. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/new-note.js +0 -0
  164. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/note-edit.js +0 -0
  165. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/note-editor.js +0 -0
  166. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/row-link.js +0 -0
  167. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/run-cell.js +0 -0
  168. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/run-output.js +0 -0
  169. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/static/view-toggle.js +0 -0
  170. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/summarize_config.py +0 -0
  171. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/__init__.py +0 -0
  172. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/experiment_rows.py +0 -0
  173. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/experiment_run_ids.py +0 -0
  174. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/experiment_toml.py +0 -0
  175. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/experiments_ddl.py +0 -0
  176. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/file_prefix.py +0 -0
  177. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/file_text.py +0 -0
  178. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/iso_time.py +0 -0
  179. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/list_experiment_runs.py +0 -0
  180. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/list_resumable_runs.py +0 -0
  181. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/note_text.py +0 -0
  182. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/open_tables.py +0 -0
  183. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/path_glob.py +0 -0
  184. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/path_pattern.py +0 -0
  185. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_blocks.py +0 -0
  186. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_experiment.py +0 -0
  187. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_file.py +0 -0
  188. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_log.py +0 -0
  189. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_memory.py +0 -0
  190. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_run.py +0 -0
  191. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/read_run_file.py +0 -0
  192. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/runs_columns.py +0 -0
  193. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tables/summarize_experiments.py +0 -0
  194. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/base.html +0 -0
  195. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/editor_imports.html +0 -0
  196. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/experiment.html +0 -0
  197. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/index.html +0 -0
  198. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/note.html +0 -0
  199. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/templates/run.html +0 -0
  200. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/__init__.py +0 -0
  201. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/browse_tui.py +0 -0
  202. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/check_view_response.py +0 -0
  203. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/config.py +0 -0
  204. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/dim.py +0 -0
  205. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/fill_table.py +0 -0
  206. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/format_elapsed.py +0 -0
  207. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/leave_prompt.py +0 -0
  208. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/log_block.py +0 -0
  209. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/log_blocks.py +0 -0
  210. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/reload_on_change.py +0 -0
  211. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/run_output.py +0 -0
  212. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/runs_table.py +0 -0
  213. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/runs_view.py +0 -0
  214. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/stopped_title.py +0 -0
  215. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/tui/view_column.py +0 -0
  216. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/__init__.py +0 -0
  217. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/format_bytes.py +0 -0
  218. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/process_alive.py +0 -0
  219. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/run_usage.py +0 -0
  220. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/tree_rss.py +0 -0
  221. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/utils/tree_usage.py +0 -0
  222. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/view/answer.py +0 -0
  223. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/view/build.py +0 -0
  224. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/view/respond.py +0 -0
  225. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/view/serve.py +0 -0
  226. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/wait_for_note.py +0 -0
  227. {labnotes-0.2.8 → labnotes-0.2.10}/src/labnotes/watch_rows.py +0 -0
  228. {labnotes-0.2.8 → labnotes-0.2.10}/tests/conftest.py +0 -0
  229. {labnotes-0.2.8 → labnotes-0.2.10}/tests/e2e/conftest.py +0 -0
  230. {labnotes-0.2.8 → labnotes-0.2.10}/tests/integration/conftest.py +0 -0
  231. {labnotes-0.2.8 → 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.8
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:
@@ -19,9 +19,11 @@ from labnotes.parse_run_view import parse_run_view
19
19
  from labnotes.pivot_runs import pivot_runs
20
20
  from labnotes.python_blocks import python_blocks
21
21
  from labnotes.render_markdown import render_markdown
22
+ from labnotes.run_columns import run_columns
22
23
  from labnotes.runs_view_command import runs_view_command
23
24
  from labnotes.sort_href import sort_href
24
25
  from labnotes.sql_quote import quote_string
26
+ from labnotes.view_specs import view_specs
25
27
  from labnotes.summarize_config import summarize_config
26
28
  from labnotes.utils.process_alive import process_alive
27
29
 
@@ -49,7 +51,7 @@ async def load_experiment(
49
51
  varies = cast(dict[str, list[object]], summary["varies"])
50
52
  source = experiment_runs(name, list(varies))
51
53
  summary = await check_counts(tables, source, summary)
52
- shown = ["id", *(key for key in varies if key != "id"), "started", "duration"]
54
+ shown = run_columns(config)
53
55
  choices = {
54
56
  key: {format_value(value): json.dumps(value) for value in values}
55
57
  for key, values in varies.items()
@@ -70,7 +72,9 @@ async def load_experiment(
70
72
  pid, created if isinstance(created, int | float) else None
71
73
  )
72
74
 
73
- runs_live = any(is_running(run) for run in runs)
75
+ for run in runs:
76
+ run["labnotes_running"] = is_running(run)
77
+ runs_live = any(run["labnotes_running"] for run in runs)
74
78
  nested = await nests_runs(tables, source)
75
79
  groups = (
76
80
  group_runs(runs, list(choices), await count_groups(tables, source))
@@ -92,6 +96,7 @@ async def load_experiment(
92
96
  return {
93
97
  "name": row["name"],
94
98
  "display_name": row["display_name"],
99
+ "config": config,
95
100
  "config_source": row["source"],
96
101
  "counts_set": "counts" in (config or {}),
97
102
  **summary,
@@ -115,6 +120,7 @@ async def load_experiment(
115
120
  "run_view": view,
116
121
  "run_choices": choices,
117
122
  "runs_view": runs_view_command(config),
123
+ "views": view_specs(config),
118
124
  "sort_hrefs": {
119
125
  column: sort_href(view, column) for column in ["status", *shown]
120
126
  },
@@ -0,0 +1,9 @@
1
+ from collections.abc import Mapping
2
+ from typing import cast
3
+
4
+ from labnotes.summarize_config import summarize_config
5
+
6
+
7
+ def param_names(config: Mapping[str, object] | None) -> list[str]:
8
+ summary = summarize_config(None if config is None else dict(config))
9
+ return [key for key in cast(dict[str, object], summary["varies"]) if key != "id"]
@@ -0,0 +1,7 @@
1
+ from collections.abc import Mapping
2
+
3
+ from labnotes.param_names import param_names
4
+
5
+
6
+ def run_columns(config: Mapping[str, object] | None) -> list[str]:
7
+ return ["id", *param_names(config), "started", "duration"]
@@ -30,7 +30,7 @@ SETTINGS = {
30
30
  "from",
31
31
  }
32
32
  HOOKS = ("setup", "teardown")
33
- VIEWS = {"runs"}
33
+ MAX_VIEWS = 8
34
34
 
35
35
 
36
36
  def read_experiment_config(
@@ -107,12 +107,18 @@ def read_experiment_config(
107
107
  views = values.get("views", {})
108
108
  if not isinstance(views, dict):
109
109
  raise ValueError(f"{path}: views must be a table, like [views]")
110
- unknown = sorted(views.keys() - VIEWS)
111
- if unknown:
112
- raise ValueError(f"{path}: unknown keys in views: {', '.join(unknown)}")
113
110
  runs = views.get("runs")
114
111
  if runs is not None and (not isinstance(runs, str) or not runs.strip()):
115
112
  raise ValueError(f"{path}: views.runs must be a command")
113
+ shown = {name: spec for name, spec in views.items() if name != "runs"}
114
+ if len(shown) > MAX_VIEWS:
115
+ raise ValueError(f"{path}: views holds at most {MAX_VIEWS} views besides runs")
116
+ for name, spec in shown.items():
117
+ source, _, cls = spec.rpartition(":") if isinstance(spec, str) else ("", "", "")
118
+ if not source.strip() or not cls.strip():
119
+ raise ValueError(
120
+ f'{path}: views.{name} must be "path/to/file.py:ClassName"'
121
+ )
116
122
  return ExperimentConfig(
117
123
  command,
118
124
  repeats,
@@ -5,8 +5,6 @@ from labnotes.models import LabnotesConfig
5
5
  from labnotes.tables.open_tables import open_tables
6
6
  from labnotes.tui.experiment_screen import ExperimentScreen
7
7
  from labnotes.tui.experiments_screen import ExperimentsScreen
8
- from labnotes.tui.runs_view import Request
9
- from labnotes.tui.view_process import ViewProcess
10
8
 
11
9
 
12
10
  class BrowseApp(App[BaseException | None]):
@@ -17,23 +15,11 @@ class BrowseApp(App[BaseException | None]):
17
15
  self.config = config
18
16
  self.experiment_name = name
19
17
  self.title = config.root.name
20
- self.views: dict[str, ViewProcess] = {}
21
18
 
22
19
  def on_mount(self) -> None:
23
20
  tables = open_tables(self.config, run_files=True)
24
- self.push_screen(ExperimentsScreen(tables, self.config, self.view_request))
21
+ self.push_screen(ExperimentsScreen(tables, self.config))
25
22
  if self.experiment_name is not None:
26
23
  self.push_screen(
27
- ExperimentScreen(
28
- tables, self.config, self.experiment_name, self.view_request
29
- )
24
+ ExperimentScreen(tables, self.config, self.experiment_name)
30
25
  )
31
-
32
- def view_request(self, command: str) -> Request:
33
- if command not in self.views:
34
- self.views[command] = ViewProcess(command, self.config.root)
35
- return self.views[command].request
36
-
37
- async def on_unmount(self) -> None:
38
- for process in self.views.values():
39
- await process.close()
@@ -0,0 +1,104 @@
1
+ from types import MappingProxyType
2
+ from typing import Any
3
+
4
+ from dirsql import DirSQL
5
+ from starlette.datastructures import QueryParams
6
+ from textual.app import ComposeResult
7
+ from textual.binding import Binding
8
+ from textual.screen import Screen
9
+ from textual.widget import Widget
10
+ from textual.widgets import ContentSwitcher, Footer, Header
11
+ from textual.worker import Worker, WorkerState
12
+
13
+ from labnotes.load_experiment import load_experiment
14
+ from labnotes.models import LabnotesConfig
15
+ from labnotes.tui.native_rows import native_rows
16
+ from labnotes.tui.reload_on_change import reload_on_change
17
+ from labnotes.tui.stopped_title import stopped_title
18
+ from labnotes.tui.view_pane import ViewPane
19
+ from labnotes.tui.view_switcher import ViewSwitcher
20
+ from labnotes.view.experiment import Experiment
21
+ from labnotes.watch_rows import watch_rows
22
+
23
+ WATCHED = {"runs", "experiment_configs"}
24
+ BUILT_IN = "labnotes.tui.runs_browser:RunsBrowser"
25
+
26
+
27
+ class ExperimentScreen(ViewSwitcher, Screen[None]):
28
+ BINDINGS = [Binding("escape", "back", "Back")]
29
+
30
+ def __init__(
31
+ self,
32
+ tables: DirSQL,
33
+ config: LabnotesConfig,
34
+ name: str,
35
+ ) -> None:
36
+ super().__init__()
37
+ self.tables = tables
38
+ self.config = config
39
+ self.experiment_name = name
40
+ self.found = False
41
+ self.shown: Experiment | None = None
42
+ self.left: dict[str, Widget] = {}
43
+
44
+ def compose(self) -> ComposeResult:
45
+ yield Header()
46
+ with ContentSwitcher(initial="view-1"):
47
+ yield ViewPane("runs", BUILT_IN, self.config.root, id="view-1")
48
+ yield Footer()
49
+
50
+ def on_mount(self) -> None:
51
+ self.sub_title = self.experiment_name
52
+ self.run_worker(
53
+ reload_on_change(
54
+ watch_rows(self.tables, WATCHED, self.experiment_name),
55
+ self.load,
56
+ self.stopped,
57
+ ),
58
+ exit_on_error=False,
59
+ )
60
+ self.run_worker(self.watch_files(), exit_on_error=False)
61
+
62
+ def on_worker_state_changed(self, event: Worker.StateChanged) -> None:
63
+ if event.state == WorkerState.ERROR:
64
+ self.app.exit(event.worker.error)
65
+
66
+ def stopped(self, error: BaseException | None) -> None:
67
+ self.sub_title = stopped_title(self.sub_title, error)
68
+
69
+ async def load(self) -> None:
70
+ loaded: dict[str, Any] | None = await load_experiment(
71
+ self.tables,
72
+ self.config.root,
73
+ self.experiment_name,
74
+ QueryParams(),
75
+ )
76
+ if loaded is None and not self.found:
77
+ raise ValueError(f"No experiment named {self.experiment_name}.")
78
+ if loaded is None:
79
+ self.sub_title = f"{self.experiment_name} was removed."
80
+ self.shown = None
81
+ self.deliver(None)
82
+ return
83
+ if not self.found:
84
+ await self.add_views(loaded["views"], self.config.root)
85
+ self.found = True
86
+ self.sub_title = str(loaded["display_name"])
87
+ self.shown = Experiment(
88
+ self.config.root,
89
+ self.experiment_name,
90
+ native_rows(loaded),
91
+ MappingProxyType(loaded["config"] or {}),
92
+ )
93
+ self.deliver(self.shown)
94
+
95
+ async def watch_files(self) -> None:
96
+ async for row in watch_rows(self.tables, {"run_files"}, self.experiment_name):
97
+ if self.shown is not None:
98
+ self.deliver(self.shown, frozenset({str(row["path"])}))
99
+
100
+ def action_back(self) -> None:
101
+ if self.query_one(ContentSwitcher).current == "view-1":
102
+ self.app.pop_screen()
103
+ else:
104
+ self.action_show_view(1)
@@ -1,5 +1,3 @@
1
- from collections.abc import Callable
2
-
3
1
  from dirsql import DirSQL
4
2
  from textual.app import ComposeResult
5
3
  from textual.screen import Screen
@@ -12,7 +10,6 @@ from labnotes.tui.experiment_screen import ExperimentScreen
12
10
  from labnotes.tui.fill_table import fill_table
13
11
  from labnotes.tui.reload_on_change import reload_on_change
14
12
  from labnotes.tui.runs_table import RunsTable
15
- from labnotes.tui.runs_view import Request
16
13
  from labnotes.tui.stopped_title import stopped_title
17
14
  from labnotes.watch_rows import watch_rows
18
15
 
@@ -20,13 +17,10 @@ WATCHED = {"files", "runs", "experiment_configs"}
20
17
 
21
18
 
22
19
  class ExperimentsScreen(Screen[None]):
23
- def __init__(
24
- self, tables: DirSQL, config: LabnotesConfig, views: Callable[[str], Request]
25
- ) -> None:
20
+ def __init__(self, tables: DirSQL, config: LabnotesConfig) -> None:
26
21
  super().__init__()
27
22
  self.tables = tables
28
23
  self.config = config
29
- self.views = views
30
24
 
31
25
  def compose(self) -> ComposeResult:
32
26
  yield Header()
@@ -63,7 +57,5 @@ class ExperimentsScreen(Screen[None]):
63
57
 
64
58
  def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
65
59
  self.app.push_screen(
66
- ExperimentScreen(
67
- self.tables, self.config, str(event.row_key.value), self.views
68
- )
60
+ ExperimentScreen(self.tables, self.config, str(event.row_key.value))
69
61
  )
@@ -7,13 +7,15 @@ from labnotes.tui.dim import dim
7
7
  from labnotes.tui.format_elapsed import format_elapsed
8
8
 
9
9
 
10
- def format_cell(key: str, value: object, live: bool) -> object:
10
+ def format_cell(key: str, value: object) -> object:
11
11
  if key == "status" and value == "passed":
12
12
  return Text("✓", style="green")
13
13
  if key == "status" and value == "failed":
14
14
  return Text("✕", style="red")
15
+ if key == "status" and value == "running":
16
+ return Text("◐", style="yellow")
15
17
  if key == "status" and value == "unfinished":
16
- return Text("◐", style="yellow") if live else dim("–")
18
+ return dim("–")
17
19
  if key == "duration" and isinstance(value, int) and not isinstance(value, bool):
18
20
  return format_elapsed(value)
19
21
  if key == "started" and isinstance(value, str):
@@ -0,0 +1,35 @@
1
+ from collections.abc import Callable, Iterable
2
+ from typing import cast
3
+
4
+ from textual.message import Message
5
+ from textual.widget import Widget
6
+
7
+
8
+ def guard_view(view: type[Widget], failed: Callable[[Exception], None]) -> type[Widget]:
9
+ """Subclass a view so errors in its compose and handlers reach `failed`.
10
+
11
+ Textual ends the app on any error a widget raises. A view is someone else's
12
+ code, so its errors stay in its pane instead.
13
+ """
14
+
15
+ def compose(self: Widget) -> Iterable[Widget]:
16
+ try:
17
+ yield from view.compose(self)
18
+ except Exception as error:
19
+ failed(error)
20
+
21
+ # _dispatch_message is private to Textual, but every handler runs under it.
22
+ async def _dispatch_message(self: Widget, message: Message) -> None:
23
+ try:
24
+ await view._dispatch_message(self, message)
25
+ except Exception as error:
26
+ failed(error)
27
+
28
+ namespace = {
29
+ "__module__": view.__module__,
30
+ "__qualname__": view.__qualname__,
31
+ "compose": compose,
32
+ "_dispatch_message": _dispatch_message,
33
+ }
34
+ # type() builds a subclass of view, so it is a Widget subclass.
35
+ return cast(type[Widget], type(view.__name__, (view,), namespace))
@@ -0,0 +1,27 @@
1
+ import importlib
2
+ import sys
3
+ from importlib.machinery import SourceFileLoader
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+ from textual.widget import Widget
8
+
9
+
10
+ def load_view(spec: str, root: Path) -> type[Widget]:
11
+ """Import the widget class `spec` names: `file.py:Class` from `root`, or `module:Class`."""
12
+ source, _, name = spec.rpartition(":")
13
+ if source.endswith(".py"):
14
+ path = root / source
15
+ module = ModuleType(f"labnotes_view_{path.stem}")
16
+ module.__file__ = str(path)
17
+ # Dataclasses and pickling look a class's module up by name.
18
+ sys.modules[module.__name__] = module
19
+ SourceFileLoader(module.__name__, str(path)).exec_module(module)
20
+ else:
21
+ module = importlib.import_module(source)
22
+ view = getattr(module, name, None)
23
+ if view is None:
24
+ raise ImportError(f"{source} has no {name}")
25
+ if not isinstance(view, type) or not issubclass(view, Widget):
26
+ raise TypeError(f"{spec} is not a Textual widget")
27
+ return view
@@ -11,7 +11,7 @@ def native_row(run: Mapping[str, Any]) -> dict[str, Any]:
11
11
  if isinstance(row.get(key), str):
12
12
  row[key] = json.loads(row[key])
13
13
  if run["labnotes_unfinished"]:
14
- row["status"] = "unfinished"
14
+ row["status"] = "running" if run["labnotes_running"] else "unfinished"
15
15
  elif run["labnotes_completed"]:
16
16
  row["status"] = "passed"
17
17
  else:
@@ -20,5 +20,5 @@ def native_row(run: Mapping[str, Any]) -> dict[str, Any]:
20
20
  return row
21
21
 
22
22
 
23
- def native_rows(experiment: Mapping[str, Any]) -> dict[str, dict[str, Any]]:
24
- return {str(run["folder"]): native_row(run) for run in experiment["runs_list"]}
23
+ def native_rows(experiment: Mapping[str, Any]) -> tuple[dict[str, Any], ...]:
24
+ return tuple(native_row(run) for run in experiment["runs_list"])