labnotes 0.0.104__tar.gz → 0.0.106__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 (172) hide show
  1. {labnotes-0.0.104 → labnotes-0.0.106}/PKG-INFO +1 -1
  2. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/context.md +7 -3
  3. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/execute_run.py +12 -1
  4. labnotes-0.0.106/src/labnotes/runner/exit_signal.py +11 -0
  5. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/run_experiment.py +1 -0
  6. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/run_output.py +17 -4
  7. {labnotes-0.0.104 → labnotes-0.0.106}/testing-conventions.toml +6 -0
  8. {labnotes-0.0.104 → labnotes-0.0.106}/.gitignore +0 -0
  9. {labnotes-0.0.104 → labnotes-0.0.106}/CHANGELOG.md +0 -0
  10. {labnotes-0.0.104 → labnotes-0.0.106}/MIGRATIONS.md +0 -0
  11. {labnotes-0.0.104 → labnotes-0.0.106}/README.md +0 -0
  12. {labnotes-0.0.104 → labnotes-0.0.106}/pyproject.toml +0 -0
  13. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/__init__.py +0 -0
  14. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/__main__.py +0 -0
  15. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/asset_url.py +0 -0
  16. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/check_counts.py +0 -0
  17. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/cli.py +0 -0
  18. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/compose_experiment.py +0 -0
  19. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/context_guide.py +0 -0
  20. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/count_groups.py +0 -0
  21. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/count_runs.py +0 -0
  22. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/create_app.py +0 -0
  23. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/create_experiment.py +0 -0
  24. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/create_note.py +0 -0
  25. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/__init__.py +0 -0
  26. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/control_run.py +0 -0
  27. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/daemon_app.py +0 -0
  28. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/daemon_client.py +0 -0
  29. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/daemon_running.py +0 -0
  30. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/daemon_server.py +0 -0
  31. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/describe_status.py +0 -0
  32. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/ensure_daemon.py +0 -0
  33. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/exit_when_idle.py +0 -0
  34. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/remote_start.py +0 -0
  35. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/run_daemon.py +0 -0
  36. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/run_logs.py +0 -0
  37. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/serve_daemon.py +0 -0
  38. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/socket_path.py +0 -0
  39. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/stop_runs.py +0 -0
  40. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/watch_daemon.py +0 -0
  41. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon/watch_status.py +0 -0
  42. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/daemon_command.py +0 -0
  43. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/data_html.py +0 -0
  44. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/default_config_text.py +0 -0
  45. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/default_counts.py +0 -0
  46. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/drain_watch.py +0 -0
  47. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/experiment_notes.py +0 -0
  48. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/experiment_runs.py +0 -0
  49. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/experiments.py +0 -0
  50. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/file_changes.py +0 -0
  51. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/format_duration.py +0 -0
  52. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/format_started.py +0 -0
  53. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/format_value.py +0 -0
  54. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/group_runs.py +0 -0
  55. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/init.py +0 -0
  56. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/invocation_started.py +0 -0
  57. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/is_non_negative_int.py +0 -0
  58. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/is_positive_int.py +0 -0
  59. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/list_experiments.py +0 -0
  60. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/list_runs.py +0 -0
  61. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/load_config.py +0 -0
  62. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/load_experiment.py +0 -0
  63. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/load_run.py +0 -0
  64. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/log.py +0 -0
  65. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/log_path.py +0 -0
  66. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/models.py +0 -0
  67. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/modes.py +0 -0
  68. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/nests_runs.py +0 -0
  69. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/note_path.py +0 -0
  70. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/notebook_kernel.py +0 -0
  71. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/output_html.py +0 -0
  72. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/parse_run_view.py +0 -0
  73. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/print_rows.py +0 -0
  74. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/python_blocks.py +0 -0
  75. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/read_config.py +0 -0
  76. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/read_path.py +0 -0
  77. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/render_markdown.py +0 -0
  78. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/retries.py +0 -0
  79. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/run_command.py +0 -0
  80. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/run_control.py +0 -0
  81. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/run_output.py +0 -0
  82. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/run_status.py +0 -0
  83. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/__init__.py +0 -0
  84. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/describe_event.py +0 -0
  85. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/hold_folder.py +0 -0
  86. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/new_param_run_id.py +0 -0
  87. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/new_run_id.py +0 -0
  88. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  89. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/param_args.py +0 -0
  90. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/param_grid.py +0 -0
  91. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/param_run_name.py +0 -0
  92. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/plan_runs.py +0 -0
  93. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/prepare_experiment.py +0 -0
  94. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/read_experiment_config.py +0 -0
  95. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/run_folder.py +0 -0
  96. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/run_headless.py +0 -0
  97. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/runs_parent.py +0 -0
  98. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/shell_command.py +0 -0
  99. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/signal_tree.py +0 -0
  100. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/stop_then_kill.py +0 -0
  101. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/wait_for_tree.py +0 -0
  102. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/runner/write_run_json.py +0 -0
  103. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/save_conflict_copy.py +0 -0
  104. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/save_note.py +0 -0
  105. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/saved_outputs.py +0 -0
  106. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/serve_app.py +0 -0
  107. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/server.py +0 -0
  108. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/shared_params.py +0 -0
  109. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/sort_href.py +0 -0
  110. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/sql_quote.py +0 -0
  111. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/static/elapsed.js +0 -0
  112. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/static/experiment.js +0 -0
  113. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/static/labnotes.css +0 -0
  114. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/static/local-time.js +0 -0
  115. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/static/new-note.js +0 -0
  116. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/static/note-edit.js +0 -0
  117. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/static/note-editor.js +0 -0
  118. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/static/row-link.js +0 -0
  119. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/static/run-cell.js +0 -0
  120. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/static/run-output.js +0 -0
  121. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/static/view-toggle.js +0 -0
  122. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/summarize_config.py +0 -0
  123. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/__init__.py +0 -0
  124. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/experiment_rows.py +0 -0
  125. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/experiment_run_ids.py +0 -0
  126. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/experiment_toml.py +0 -0
  127. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/experiments_ddl.py +0 -0
  128. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/file_prefix.py +0 -0
  129. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/file_text.py +0 -0
  130. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/iso_time.py +0 -0
  131. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/list_experiment_runs.py +0 -0
  132. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/note_text.py +0 -0
  133. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/open_tables.py +0 -0
  134. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/path_glob.py +0 -0
  135. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/path_pattern.py +0 -0
  136. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/read_blocks.py +0 -0
  137. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/read_experiment.py +0 -0
  138. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/read_file.py +0 -0
  139. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/read_log.py +0 -0
  140. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/read_run.py +0 -0
  141. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tables/summarize_experiments.py +0 -0
  142. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/templates/base.html +0 -0
  143. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/templates/editor_imports.html +0 -0
  144. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/templates/experiment.html +0 -0
  145. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/templates/index.html +0 -0
  146. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/templates/note.html +0 -0
  147. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/templates/run.html +0 -0
  148. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/__init__.py +0 -0
  149. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/browse_app.py +0 -0
  150. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/browse_tui.py +0 -0
  151. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/dim.py +0 -0
  152. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/experiment_screen.py +0 -0
  153. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/experiments_screen.py +0 -0
  154. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/fill_table.py +0 -0
  155. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/format_elapsed.py +0 -0
  156. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/group_cells.py +0 -0
  157. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/invocation_columns.py +0 -0
  158. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/invocation_rows.py +0 -0
  159. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/leave_prompt.py +0 -0
  160. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/log_block.py +0 -0
  161. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/log_blocks.py +0 -0
  162. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/reload_on_change.py +0 -0
  163. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/run_app.py +0 -0
  164. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/run_cells.py +0 -0
  165. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/run_screen.py +0 -0
  166. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/run_tui.py +0 -0
  167. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/runs_table.py +0 -0
  168. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/tui/stopped_title.py +0 -0
  169. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/wait_for_note.py +0 -0
  170. {labnotes-0.0.104 → labnotes-0.0.106}/src/labnotes/watch_rows.py +0 -0
  171. {labnotes-0.0.104 → labnotes-0.0.106}/tests/integration/conftest.py +0 -0
  172. {labnotes-0.0.104 → labnotes-0.0.106}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.104
3
+ Version: 0.0.106
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>
@@ -75,8 +75,11 @@ script so it writes its outputs there.
75
75
  Each param is appended as `--name value`, with the name as written (use
76
76
  `source-language` for a `--source-language` flag), booleans as 0 or 1, and lists
77
77
  and tables as JSON. labnotes writes `run.json` (params, created, ended, exit
78
- code), `stdout.log` and `stderr.log` to the
79
- run folder.
78
+ code, `ended_by`, `signal`), `stdout.log` and `stderr.log` to the
79
+ run folder. `ended_by` is `"timeout"`, `"stop"` (labnotes stopped it),
80
+ `"signal"` (a signal labnotes didn't send, like the out-of-memory killer) or
81
+ null (the script exited itself). `signal` is the signal number read from the
82
+ exit code (negative, or 128 + N from the shell), else null.
80
83
  A run counts when it exited 0 and its `run.json` doesn't hold
81
84
  `"excluded": true`, so excluding a run by hand means adding that key; the next
82
85
  `run` replaces it. Other keys you add to `run.json` are kept and show in `run`.
@@ -133,7 +136,8 @@ going. Elsewhere, it prints a line per event instead.
133
136
  - `-e PATH` reads another file instead of `experiment.toml`.
134
137
  - `--timeout SECONDS` stops any one run still going after that many seconds:
135
138
  SIGTERM, then SIGKILL 5 seconds later. Its
136
- `run.json` gets `"error": "timed out after ..."` next to its exit code.
139
+ `run.json` gets `"error": "timed out after ..."` and `"ended_by": "timeout"`
140
+ next to its exit code.
137
141
  Unset, runs have no time limit.
138
142
  - `run` hands its runs to the project's labnotes daemon, starting it if
139
143
  needed, and shows or waits for them. The daemon listens on a socket in
@@ -7,6 +7,7 @@ from pathlib import Path
7
7
  import psutil
8
8
 
9
9
  from labnotes.log_path import LOG_ENV
10
+ from labnotes.runner.exit_signal import exit_signal
10
11
  from labnotes.runner.shell_command import shell_command
11
12
  from labnotes.runner.stop_then_kill import GRACE_SECONDS
12
13
  from labnotes.runner.wait_for_tree import wait_for_tree
@@ -26,6 +27,7 @@ async def execute_run(
26
27
  retry_of: str | None = None,
27
28
  timeout: float | None = None,
28
29
  stop: Callable[[], None] = lambda: None,
30
+ stopped: Callable[[], bool] = lambda: False,
29
31
  ) -> dict[str, object]:
30
32
  run: dict[str, object] = {
31
33
  "params": params,
@@ -33,6 +35,8 @@ async def execute_run(
33
35
  "ended": None,
34
36
  "exit_code": None,
35
37
  "error": None,
38
+ "ended_by": None,
39
+ "signal": None,
36
40
  }
37
41
  if retry_of is not None:
38
42
  run["retry_of"] = retry_of
@@ -70,7 +74,14 @@ async def execute_run(
70
74
  kill_later = asyncio.get_running_loop().call_later(
71
75
  GRACE_SECONDS, stop
72
76
  )
73
- run["exit_code"] = await exited
77
+ run["exit_code"] = code = await exited
78
+ run["signal"] = exit_signal(code)
79
+ if kill_later is not None:
80
+ run["ended_by"] = "timeout"
81
+ elif stopping.is_set() or stopped():
82
+ run["ended_by"] = "stop"
83
+ elif run["signal"] is not None:
84
+ run["ended_by"] = "signal"
74
85
  # The shell can exit on SIGTERM before what it started. Once
75
86
  # stopping, the run stays killable until everything signalled
76
87
  # is gone. A normal exit ends the run even if helpers linger.
@@ -0,0 +1,11 @@
1
+ import signal
2
+
3
+
4
+ def exit_signal(exit_code: int | None) -> int | None:
5
+ if exit_code is None:
6
+ return None
7
+ if exit_code < 0:
8
+ return -exit_code
9
+ # A shell reports a child killed by signal N as exit 128 + N.
10
+ number = exit_code - 128
11
+ return number if 0 < number < signal.NSIG else None
@@ -255,6 +255,7 @@ async def run_experiment(
255
255
  parents.get(run),
256
256
  timeout=timeout,
257
257
  stop=lambda: kill(run),
258
+ stopped=lambda: run in stopped,
258
259
  )
259
260
  del running[run], trees[run]
260
261
  report({"event": "ended", "run": run, **result})
@@ -1,5 +1,6 @@
1
1
  import codecs
2
2
  from pathlib import Path
3
+ from typing import Any, Self
3
4
 
4
5
  from rich.text import Text
5
6
  from textual.geometry import Size
@@ -10,7 +11,7 @@ STREAMS = ("stdout", "stderr")
10
11
 
11
12
  class RunOutput(RichLog):
12
13
  def __init__(self, id: str | None = None) -> None:
13
- super().__init__(id=id)
14
+ super().__init__(id=id, wrap=True, min_width=1)
14
15
  self.reset()
15
16
 
16
17
  def reset(self) -> None:
@@ -21,7 +22,11 @@ class RunOutput(RichLog):
21
22
  }
22
23
  self.last_stream: str | None = None
23
24
  self.unfinished = ""
25
+ self.written_rows = 0
24
26
  self.clear()
27
+ # A hidden log has no width; writing now would wrap every line to one
28
+ # cell. Hold writes until it's shown and resized.
29
+ self._size_known = bool(self.scrollable_content_region.width)
25
30
 
26
31
  @property
27
32
  def mid_line(self) -> bool:
@@ -42,7 +47,7 @@ class RunOutput(RichLog):
42
47
  # A stream's unfinished line is redrawn as it grows, but the
43
48
  # other stream's output starts below it.
44
49
  if self.mid_line and stream == self.last_stream:
45
- self.drop_last_line()
50
+ self.drop_unfinished()
46
51
  text = self.unfinished + text
47
52
  *complete, self.unfinished = text.split("\n")
48
53
  if complete:
@@ -51,11 +56,19 @@ class RunOutput(RichLog):
51
56
  self.write(Text.from_ansi(self.unfinished))
52
57
  self.last_stream = stream
53
58
 
54
- def drop_last_line(self) -> None:
59
+ # Counted here, not in follow: writes made before the size is known
60
+ # render later, from on_resize.
61
+ def write(self, content: object, *args: Any, **kwargs: Any) -> Self:
62
+ rows = len(self.lines)
63
+ super().write(content, *args, **kwargs)
64
+ self.written_rows = len(self.lines) - rows
65
+ return self
66
+
67
+ def drop_unfinished(self) -> None:
55
68
  if not self._size_known:
56
69
  self._deferred_renders.pop()
57
70
  return
58
- del self.lines[-1]
71
+ del self.lines[-self.written_rows :]
59
72
  self._line_cache.clear()
60
73
  self.virtual_size = Size(self.virtual_size.width, len(self.lines))
61
74
  self.refresh()
@@ -69,3 +69,9 @@ path = "src/labnotes/runner/run_experiment.py"
69
69
  rules = ["mutation"]
70
70
  lines = [184, 263]
71
71
  reason = "a retry is only scheduled below the budget and each one adds one, so tried never passes retries: >= equals ==; both sides are small ints, which CPython caches, so is equals =="
72
+
73
+ [[python.exempt]]
74
+ path = "src/labnotes/runner/exit_signal.py"
75
+ rules = ["mutation"]
76
+ lines = [10]
77
+ reason = "exit_code is non-negative here; when bit 7 is set, - 128 equals ^ 128, and when it is clear ^ 128 lands above NSIG like a non-positive difference"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes