labnotes 0.2.18__tar.gz → 0.2.20__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 (235) hide show
  1. {labnotes-0.2.18 → labnotes-0.2.20}/PKG-INFO +1 -1
  2. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/daemon/server.py +2 -0
  3. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/experiments.py +29 -1
  4. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/models.py +2 -0
  5. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/retries.py +1 -1
  6. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/describe_event.py +4 -0
  7. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/prepare_experiment.py +6 -0
  8. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/run_experiment.py +18 -8
  9. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/start_experiment.py +12 -1
  10. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/list_resumable_runs.py +6 -1
  11. labnotes-0.2.20/tests/perf/conftest.py +17 -0
  12. {labnotes-0.2.18 → labnotes-0.2.20}/.gitignore +0 -0
  13. {labnotes-0.2.18 → labnotes-0.2.20}/CHANGELOG.md +0 -0
  14. {labnotes-0.2.18 → labnotes-0.2.20}/MIGRATIONS.md +0 -0
  15. {labnotes-0.2.18 → labnotes-0.2.20}/README.md +0 -0
  16. {labnotes-0.2.18 → labnotes-0.2.20}/conftest.py +0 -0
  17. {labnotes-0.2.18 → labnotes-0.2.20}/pyproject.toml +0 -0
  18. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/__init__.py +0 -0
  19. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/__main__.py +0 -0
  20. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/asset_url.py +0 -0
  21. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/check_counts.py +0 -0
  22. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/cli.py +0 -0
  23. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/command_line_group.py +0 -0
  24. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/compose_experiment.py +0 -0
  25. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/config_migration.py +0 -0
  26. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/context.md +0 -0
  27. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/context_guide.py +0 -0
  28. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/copy_experiment.py +0 -0
  29. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/count_groups.py +0 -0
  30. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/count_runs.py +0 -0
  31. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/create_app.py +0 -0
  32. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/create_experiment.py +0 -0
  33. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/create_note.py +0 -0
  34. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/daemon/__init__.py +0 -0
  35. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/daemon/client.py +0 -0
  36. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/daemon/command.py +0 -0
  37. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/daemon/ensure.py +0 -0
  38. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/daemon/marker.py +0 -0
  39. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/daemon/socket_path.py +0 -0
  40. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/daemon_command.py +0 -0
  41. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/data_html.py +0 -0
  42. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/default_config_text.py +0 -0
  43. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/default_counts.py +0 -0
  44. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/drain_watch.py +0 -0
  45. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/experiment_definition.py +0 -0
  46. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/experiment_notes.py +0 -0
  47. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/experiment_runs.py +0 -0
  48. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/file_changes.py +0 -0
  49. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/format_duration.py +0 -0
  50. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/format_started.py +0 -0
  51. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/format_value.py +0 -0
  52. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/get_state.py +0 -0
  53. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/grid_cell.py +0 -0
  54. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/group_runs.py +0 -0
  55. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/init.py +0 -0
  56. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/invocation_started.py +0 -0
  57. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/is_non_negative_int.py +0 -0
  58. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/is_non_negative_number.py +0 -0
  59. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/is_positive_int.py +0 -0
  60. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/latest_log.py +0 -0
  61. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/list_experiments.py +0 -0
  62. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/list_runs.py +0 -0
  63. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/load_config.py +0 -0
  64. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/load_experiment.py +0 -0
  65. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/load_run.py +0 -0
  66. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/log.py +0 -0
  67. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/log_path.py +0 -0
  68. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/memory_interval.py +0 -0
  69. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/nests_runs.py +0 -0
  70. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/note_path.py +0 -0
  71. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/notebook_kernel.py +0 -0
  72. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/output_html.py +0 -0
  73. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/param_names.py +0 -0
  74. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/parse_param_value.py +0 -0
  75. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/parse_params.py +0 -0
  76. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/parse_run_view.py +0 -0
  77. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/parse_subset.py +0 -0
  78. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/pivot_runs.py +0 -0
  79. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/print_rows.py +0 -0
  80. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/python_blocks.py +0 -0
  81. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/read_config.py +0 -0
  82. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/read_path.py +0 -0
  83. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/render_markdown.py +0 -0
  84. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/run_columns.py +0 -0
  85. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/run_output.py +0 -0
  86. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/__init__.py +0 -0
  87. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/cgroup_memory.py +0 -0
  88. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/check_args.py +0 -0
  89. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/check_hook_placeholders.py +0 -0
  90. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/check_subset.py +0 -0
  91. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/claim_run.py +0 -0
  92. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/command_flags.py +0 -0
  93. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/container_cgroup.py +0 -0
  94. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/container_memory.py +0 -0
  95. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/continue_run.py +0 -0
  96. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/docker_output.py +0 -0
  97. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/execute_run.py +0 -0
  98. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/exit_signal.py +0 -0
  99. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/fill_dirs.py +0 -0
  100. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/fill_experiment_env.py +0 -0
  101. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/finalize_lost_runs.py +0 -0
  102. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/first_invocation.py +0 -0
  103. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/flag_name.py +0 -0
  104. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/group_args.py +0 -0
  105. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/hold_experiment.py +0 -0
  106. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/in_subset.py +0 -0
  107. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/invocation_hook.py +0 -0
  108. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/invocation_meta.py +0 -0
  109. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/new_run_id.py +0 -0
  110. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  111. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/override_flags.py +0 -0
  112. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/param_args.py +0 -0
  113. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/param_grid.py +0 -0
  114. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/placeholders.py +0 -0
  115. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/plan_runs.py +0 -0
  116. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/project_snapshot.py +0 -0
  117. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/read_experiment_config.py +0 -0
  118. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/record_memory.py +0 -0
  119. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/run_folder.py +0 -0
  120. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/run_git.py +0 -0
  121. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/run_headless.py +0 -0
  122. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/run_locked.py +0 -0
  123. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/run_state.py +0 -0
  124. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/runs_parent.py +0 -0
  125. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/shell_command.py +0 -0
  126. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/signal_tree.py +0 -0
  127. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/stop_then_kill.py +0 -0
  128. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/untracked_diff.py +0 -0
  129. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/wait_for_tree.py +0 -0
  130. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runner/write_run_json.py +0 -0
  131. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/runs_view_command.py +0 -0
  132. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/save_conflict_copy.py +0 -0
  133. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/save_note.py +0 -0
  134. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/saved_outputs.py +0 -0
  135. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/serve_app.py +0 -0
  136. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/server.py +0 -0
  137. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/set_state.py +0 -0
  138. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/shared_params.py +0 -0
  139. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/sort_href.py +0 -0
  140. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/sql_quote.py +0 -0
  141. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/state_path.py +0 -0
  142. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/static/elapsed.js +0 -0
  143. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/static/experiment.js +0 -0
  144. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/static/labnotes.css +0 -0
  145. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/static/local-time.js +0 -0
  146. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/static/new-note.js +0 -0
  147. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/static/note-edit.js +0 -0
  148. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/static/note-editor.js +0 -0
  149. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/static/row-link.js +0 -0
  150. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/static/run-cell.js +0 -0
  151. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/static/run-output.js +0 -0
  152. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/static/view-toggle.js +0 -0
  153. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/summarize_config.py +0 -0
  154. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/__init__.py +0 -0
  155. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/experiment_rows.py +0 -0
  156. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/experiment_run_ids.py +0 -0
  157. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/experiment_toml.py +0 -0
  158. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/experiments_ddl.py +0 -0
  159. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/file_prefix.py +0 -0
  160. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/file_text.py +0 -0
  161. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/iso_time.py +0 -0
  162. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/list_experiment_runs.py +0 -0
  163. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/note_text.py +0 -0
  164. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/open_tables.py +0 -0
  165. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/path_glob.py +0 -0
  166. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/path_pattern.py +0 -0
  167. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/read_blocks.py +0 -0
  168. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/read_experiment.py +0 -0
  169. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/read_file.py +0 -0
  170. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/read_log.py +0 -0
  171. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/read_memory.py +0 -0
  172. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/read_run.py +0 -0
  173. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/read_run_file.py +0 -0
  174. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/runs_columns.py +0 -0
  175. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tables/summarize_experiments.py +0 -0
  176. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/templates/base.html +0 -0
  177. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/templates/editor_imports.html +0 -0
  178. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/templates/experiment.html +0 -0
  179. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/templates/index.html +0 -0
  180. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/templates/note.html +0 -0
  181. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/templates/run.html +0 -0
  182. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/__init__.py +0 -0
  183. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/browse_app.py +0 -0
  184. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/browse_tui.py +0 -0
  185. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/check_view_response.py +0 -0
  186. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/config.py +0 -0
  187. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/dim.py +0 -0
  188. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/experiment_screen.py +0 -0
  189. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/experiments_screen.py +0 -0
  190. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/fill_table.py +0 -0
  191. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/format_cell.py +0 -0
  192. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/format_elapsed.py +0 -0
  193. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/guard_view.py +0 -0
  194. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/leave_prompt.py +0 -0
  195. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/load_view.py +0 -0
  196. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/log_block.py +0 -0
  197. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/log_blocks.py +0 -0
  198. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/native_rows.py +0 -0
  199. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/reload_on_change.py +0 -0
  200. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/run_app.py +0 -0
  201. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/run_cells.py +0 -0
  202. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/run_output.py +0 -0
  203. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/run_panel.py +0 -0
  204. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/run_tui.py +0 -0
  205. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/runs_browser.py +0 -0
  206. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/runs_table.py +0 -0
  207. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/runs_view.py +0 -0
  208. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/stopped_title.py +0 -0
  209. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/table_columns.py +0 -0
  210. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/table_rows.py +0 -0
  211. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/view_column.py +0 -0
  212. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/view_pane.py +0 -0
  213. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/view_process.py +0 -0
  214. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/tui/view_switcher.py +0 -0
  215. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/utils/__init__.py +0 -0
  216. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/utils/format_bytes.py +0 -0
  217. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/utils/process_alive.py +0 -0
  218. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/utils/run_usage.py +0 -0
  219. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/utils/tree_rss.py +0 -0
  220. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/utils/tree_usage.py +0 -0
  221. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/view/__init__.py +0 -0
  222. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/view/answer.py +0 -0
  223. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/view/build.py +0 -0
  224. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/view/changed.py +0 -0
  225. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/view/experiment.py +0 -0
  226. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/view/respond.py +0 -0
  227. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/view/serve.py +0 -0
  228. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/view_specs.py +0 -0
  229. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/wait_for_note.py +0 -0
  230. {labnotes-0.2.18 → labnotes-0.2.20}/src/labnotes/watch_rows.py +0 -0
  231. {labnotes-0.2.18 → labnotes-0.2.20}/testing-conventions.toml +0 -0
  232. {labnotes-0.2.18 → labnotes-0.2.20}/tests/conftest.py +0 -0
  233. {labnotes-0.2.18 → labnotes-0.2.20}/tests/e2e/conftest.py +0 -0
  234. {labnotes-0.2.18 → labnotes-0.2.20}/tests/integration/conftest.py +0 -0
  235. {labnotes-0.2.18 → labnotes-0.2.20}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.2.18
3
+ Version: 0.2.20
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>
@@ -61,6 +61,8 @@ class ExperimentDaemon:
61
61
  cast(str | None, options.get("teardown")),
62
62
  cast(str | None, options.get("command_line")),
63
63
  cast(str | None, options.get("run_id")),
64
+ cast(int | None, options.get("retries")),
65
+ bool(options.get("replace_capped")),
64
66
  )
65
67
  path = str(selected.run_state.path)
66
68
  invocation = Invocation()
@@ -60,7 +60,23 @@ class PassthroughCommand(click.Command):
60
60
  help="With run: how many runs at once. Overrides experiment.toml and"
61
61
  " .labnotes.toml; 1 if none sets it.",
62
62
  )
63
- @click.option("--run-id", help="With run: restart only this failed run by id.")
63
+ @click.option(
64
+ "--run-id",
65
+ help="With run: restart only this failed run by id, even if out of retries.",
66
+ )
67
+ @click.option(
68
+ "--retries",
69
+ type=click.IntRange(min=0),
70
+ help="With run: how many times a failed run is tried again, on exit 75 or,"
71
+ " with continues, by a later run. Overrides experiment.toml and"
72
+ " .labnotes.toml; 3 if none sets it.",
73
+ )
74
+ @click.option(
75
+ "--replace-capped",
76
+ is_flag=True,
77
+ help="With run: start a fresh run for a cell whose run is out of retries."
78
+ " Unset, that cell stays short.",
79
+ )
64
80
  @click.option(
65
81
  "--headless",
66
82
  is_flag=True,
@@ -124,6 +140,8 @@ def experiments(
124
140
  output_format: str | None,
125
141
  concurrency: int | None,
126
142
  run_id: str | None,
143
+ retries: int | None,
144
+ replace_capped: bool,
127
145
  headless: bool,
128
146
  experiment_config: Path | None,
129
147
  timeout: int | None,
@@ -191,6 +209,8 @@ def experiments(
191
209
  "teardown": teardown,
192
210
  "command_line": ctx.meta.get(COMMAND_LINE),
193
211
  "run_id": run_id,
212
+ "retries": retries,
213
+ "replace_capped": replace_capped,
194
214
  },
195
215
  },
196
216
  )
@@ -220,6 +240,8 @@ def experiments(
220
240
  teardown,
221
241
  ctx.meta.get(COMMAND_LINE),
222
242
  run_id,
243
+ retries,
244
+ replace_capped,
223
245
  )
224
246
  )
225
247
  except ValueError as error:
@@ -239,6 +261,8 @@ def experiments(
239
261
  output_format is not None
240
262
  or concurrency is not None
241
263
  or run_id is not None
264
+ or retries is not None
265
+ or replace_capped
242
266
  or experiment_config is not None
243
267
  or timeout is not None
244
268
  or setup is not None
@@ -261,6 +285,10 @@ def experiments(
261
285
  raise click.UsageError("-- ARGS needs run.")
262
286
  if run_id is not None:
263
287
  raise click.UsageError("--run-id needs run.")
288
+ if retries is not None:
289
+ raise click.UsageError("--retries needs run.")
290
+ if replace_capped:
291
+ raise click.UsageError("--replace-capped needs run.")
264
292
  if ctx.args:
265
293
  raise click.UsageError(f"Unexpected {ctx.args[0]}. --PARAM=VALUE needs run.")
266
294
  if action not in (None, "runs"):
@@ -65,3 +65,5 @@ class ExperimentConfig:
65
65
  teardown: str | None = None
66
66
  env: dict[str, str] = field(default_factory=dict)
67
67
  continues: bool = False
68
+ # A run option, not a config key: lets a run that used up `retries` free its slot.
69
+ replace_capped: bool = False
@@ -1,4 +1,4 @@
1
1
  # EX_TEMPFAIL from sysexits.h: the only exit labnotes retries on its own.
2
2
  RETRY_EXIT_CODE = 75
3
- DEFAULT_RETRIES = 2
3
+ DEFAULT_RETRIES = 3
4
4
  RETRY_SECONDS = 30.0
@@ -33,6 +33,10 @@ def describe_event(event: dict[str, object]) -> str:
33
33
  case "gave up":
34
34
  tried = f"after {event['retries']} retries"
35
35
  return f"{event['run']} gave up (exit {RETRY_EXIT_CODE}) {tried}."
36
+ case "capped":
37
+ cells = len(event["runs"]) # type: ignore[arg-type]
38
+ noun = "cell" if cells == 1 else "cells"
39
+ return f"{cells} {noun} gave up after {event['retries']} retries."
36
40
  case "retry cancelled":
37
41
  return f"{event['run']} won't retry."
38
42
  case "idle":
@@ -17,6 +17,8 @@ async def prepare_experiment(
17
17
  args: list[str] | None = None,
18
18
  setup: str | None = None,
19
19
  teardown: str | None = None,
20
+ retries: int | None = None,
21
+ replace_capped: bool = False,
20
22
  ) -> tuple[ExperimentConfig, int]:
21
23
  if config.experiment not in (None, name):
22
24
  raise ValueError(
@@ -39,6 +41,10 @@ async def prepare_experiment(
39
41
  experiment = dataclasses.replace(experiment, setup=setup)
40
42
  if teardown is not None:
41
43
  experiment = dataclasses.replace(experiment, teardown=teardown)
44
+ if retries is not None:
45
+ experiment = dataclasses.replace(experiment, retries=retries)
46
+ if replace_capped:
47
+ experiment = dataclasses.replace(experiment, replace_capped=True)
42
48
  if only:
43
49
  check_subset(experiment.params, only)
44
50
  if args:
@@ -84,6 +84,17 @@ async def run_experiment(
84
84
  for r in counted
85
85
  if r["counted"] or str(r.get("id")) in held_ids
86
86
  ]
87
+ resumable: list[Entry] = []
88
+ capped: list[Entry] = []
89
+ tries: dict[str, int] = {}
90
+ if experiment.continues:
91
+ for row in await list_resumable_runs(tables, name, experiment.counts):
92
+ entry = (str(row["id"]), json.loads(str(row["params"])))
93
+ tries[entry[0]] = int(str(row["invocations"]))
94
+ over = tries[entry[0]] > experiment.retries
95
+ (capped if over else resumable).append(entry)
96
+ if not experiment.replace_capped:
97
+ finished += [params for _, params in capped]
87
98
  planned: set[str] = set()
88
99
 
89
100
  def taken(run: str) -> bool:
@@ -112,12 +123,6 @@ async def run_experiment(
112
123
  def release(run: str) -> None:
113
124
  os.close(locks.pop(run))
114
125
 
115
- resumable: list[Entry] = []
116
- if experiment.continues:
117
- resumable = [
118
- (str(row["id"]), json.loads(str(row["params"])))
119
- for row in await list_resumable_runs(tables, name, experiment.counts)
120
- ]
121
126
  plan: list[Entry] = []
122
127
  target_folder: Path | None = None
123
128
  if run_id is not None:
@@ -155,6 +160,8 @@ async def run_experiment(
155
160
  run = new_stamped_run_id(taken) if resumed is None else resumed[0]
156
161
  planned.add(run)
157
162
  plan.append((run, params))
163
+ # A resumed run's earlier invocations spend the same retries as exit 75 does.
164
+ earlier = {run: tries[run] for run, _ in plan if run in tries and run != run_id}
158
165
  hooks = {
159
166
  "setup": experiment.setup,
160
167
  "teardown": experiment.teardown,
@@ -180,6 +187,9 @@ async def run_experiment(
180
187
  )
181
188
  for row in held:
182
189
  emit({"event": "held", "run": str(row["id"])})
190
+ gave_up = [run for run, params in capped if in_subset(params, only or {})]
191
+ if gave_up and run_id is None and not experiment.replace_capped:
192
+ emit({"event": "capped", "runs": gave_up, "retries": experiment.retries})
183
193
  running: dict[str, set[asyncio.subprocess.Process]] = {}
184
194
  trees: dict[str, set[psutil.Process]] = {}
185
195
  stopped: set[str] = set()
@@ -240,11 +250,11 @@ async def run_experiment(
240
250
  fill()
241
251
 
242
252
  def retry_later(run: str) -> None:
243
- tried = attempts.get(run, 0)
253
+ tried = attempts.get(run, earlier.get(run, 0))
244
254
  if tried >= experiment.retries:
245
255
  emit({"event": "gave up", "run": run, "retries": tried})
246
256
  return
247
- seconds = retry_seconds * 2**tried
257
+ seconds = retry_seconds * 2 ** (tried - earlier.get(run, 0))
248
258
 
249
259
  def due() -> None:
250
260
  del waiting[run]
@@ -32,12 +32,23 @@ def start_experiment(
32
32
  teardown: str | None,
33
33
  command_line: str | None = None,
34
34
  run_id: str | None = None,
35
+ retries: int | None = None,
36
+ replace_capped: bool = False,
35
37
  ) -> ExperimentStart:
36
38
  state = RunState.create(config, name)
37
39
 
38
40
  async def start(report: Report, listen: Listen) -> list[dict[str, object]]:
39
41
  experiment, limit = await prepare_experiment(
40
- config, name, experiment_config, concurrency, only, args, setup, teardown
42
+ config,
43
+ name,
44
+ experiment_config,
45
+ concurrency,
46
+ only,
47
+ args,
48
+ setup,
49
+ teardown,
50
+ retries,
51
+ replace_capped,
41
52
  )
42
53
  await finalize_lost_runs(config)
43
54
  with project_snapshot(config.root) as snapshot:
@@ -8,8 +8,13 @@ async def list_resumable_runs(
8
8
  tables: DirSQL, name: str, counts: str
9
9
  ) -> list[dict[str, object]]:
10
10
  # Ended badly: a nonzero or missing exit code, not merely uncounted.
11
+ # Invocations stopped by hand don't spend retries.
11
12
  return await tables.query(
12
- f"SELECT id, params FROM runs WHERE experiment = {quote_string(name)}"
13
+ "SELECT id, params, CASE WHEN json_type(run, '$.invocations') = 'array'"
14
+ " THEN (SELECT count(*) FROM json_each(run, '$.invocations')"
15
+ " WHERE json_extract(value, '$.ended_by') IS NOT 'stop')"
16
+ " ELSE json_extract(run, '$.ended_by') IS NOT 'stop' END AS invocations"
17
+ f" FROM runs WHERE experiment = {quote_string(name)}"
13
18
  f" AND NOT coalesce(({counts}), false) AND NOT {UNFINISHED}"
14
19
  " AND (exit_code IS NULL OR exit_code <> 0)"
15
20
  " AND NOT coalesce(run ->> 'excluded', false) ORDER BY created, id"
@@ -0,0 +1,17 @@
1
+ import faulthandler
2
+ from collections.abc import Iterator
3
+
4
+ import pytest
5
+
6
+ TIMEOUT_SECONDS = 120
7
+
8
+
9
+ @pytest.fixture(autouse=True)
10
+ def hard_timeout() -> Iterator[None]:
11
+ # A frozen event loop never reaches an assertion. The watchdog thread
12
+ # still fires, dumps every stack, and exits.
13
+ faulthandler.dump_traceback_later(TIMEOUT_SECONDS, exit=True)
14
+ try:
15
+ yield
16
+ finally:
17
+ faulthandler.cancel_dump_traceback_later()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes