labnotes 0.0.95__tar.gz → 0.0.96__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 (163) hide show
  1. {labnotes-0.0.95 → labnotes-0.0.96}/PKG-INFO +1 -1
  2. labnotes-0.0.96/src/labnotes/tui/run_output.py +61 -0
  3. {labnotes-0.0.95 → labnotes-0.0.96}/testing-conventions.toml +2 -2
  4. labnotes-0.0.95/src/labnotes/tui/run_output.py +0 -41
  5. {labnotes-0.0.95 → labnotes-0.0.96}/.gitignore +0 -0
  6. {labnotes-0.0.95 → labnotes-0.0.96}/CHANGELOG.md +0 -0
  7. {labnotes-0.0.95 → labnotes-0.0.96}/MIGRATIONS.md +0 -0
  8. {labnotes-0.0.95 → labnotes-0.0.96}/README.md +0 -0
  9. {labnotes-0.0.95 → labnotes-0.0.96}/pyproject.toml +0 -0
  10. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/__init__.py +0 -0
  11. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/__main__.py +0 -0
  12. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/asset_url.py +0 -0
  13. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/check_counts.py +0 -0
  14. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/cli.py +0 -0
  15. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/context.md +0 -0
  16. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/context_guide.py +0 -0
  17. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/count_groups.py +0 -0
  18. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/count_runs.py +0 -0
  19. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/create_app.py +0 -0
  20. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/create_experiment.py +0 -0
  21. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/create_note.py +0 -0
  22. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/__init__.py +0 -0
  23. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/control_run.py +0 -0
  24. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/daemon_app.py +0 -0
  25. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/daemon_client.py +0 -0
  26. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/daemon_running.py +0 -0
  27. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/daemon_server.py +0 -0
  28. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/describe_status.py +0 -0
  29. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/ensure_daemon.py +0 -0
  30. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/exit_when_idle.py +0 -0
  31. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/remote_start.py +0 -0
  32. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/run_daemon.py +0 -0
  33. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/run_logs.py +0 -0
  34. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/serve_daemon.py +0 -0
  35. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/socket_path.py +0 -0
  36. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/stop_runs.py +0 -0
  37. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/watch_daemon.py +0 -0
  38. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon/watch_status.py +0 -0
  39. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/daemon_command.py +0 -0
  40. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/data_html.py +0 -0
  41. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/default_config_text.py +0 -0
  42. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/default_counts.py +0 -0
  43. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/drain_watch.py +0 -0
  44. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/experiment_notes.py +0 -0
  45. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/experiment_runs.py +0 -0
  46. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/experiments.py +0 -0
  47. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/file_changes.py +0 -0
  48. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/format_duration.py +0 -0
  49. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/format_started.py +0 -0
  50. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/format_value.py +0 -0
  51. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/group_runs.py +0 -0
  52. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/init.py +0 -0
  53. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/invocation_started.py +0 -0
  54. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/is_positive_int.py +0 -0
  55. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/list_experiments.py +0 -0
  56. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/list_runs.py +0 -0
  57. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/load_config.py +0 -0
  58. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/load_experiment.py +0 -0
  59. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/load_run.py +0 -0
  60. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/log.py +0 -0
  61. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/log_path.py +0 -0
  62. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/models.py +0 -0
  63. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/modes.py +0 -0
  64. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/nests_runs.py +0 -0
  65. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/note_path.py +0 -0
  66. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/notebook_kernel.py +0 -0
  67. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/output_html.py +0 -0
  68. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/parse_run_view.py +0 -0
  69. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/print_rows.py +0 -0
  70. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/python_blocks.py +0 -0
  71. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/read_config.py +0 -0
  72. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/read_path.py +0 -0
  73. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/render_markdown.py +0 -0
  74. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/run_command.py +0 -0
  75. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/run_control.py +0 -0
  76. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/run_output.py +0 -0
  77. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/run_status.py +0 -0
  78. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/__init__.py +0 -0
  79. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/describe_event.py +0 -0
  80. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/execute_run.py +0 -0
  81. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/hold_folder.py +0 -0
  82. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/new_param_run_id.py +0 -0
  83. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/new_run_id.py +0 -0
  84. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  85. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/param_args.py +0 -0
  86. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/param_grid.py +0 -0
  87. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/param_run_name.py +0 -0
  88. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/plan_runs.py +0 -0
  89. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/prepare_experiment.py +0 -0
  90. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/read_experiment_config.py +0 -0
  91. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/run_experiment.py +0 -0
  92. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/run_folder.py +0 -0
  93. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/run_headless.py +0 -0
  94. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/runs_parent.py +0 -0
  95. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/shell_command.py +0 -0
  96. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/signal_tree.py +0 -0
  97. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/stop_then_kill.py +0 -0
  98. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/wait_for_tree.py +0 -0
  99. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/runner/write_run_json.py +0 -0
  100. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/save_conflict_copy.py +0 -0
  101. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/save_note.py +0 -0
  102. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/saved_outputs.py +0 -0
  103. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/serve_app.py +0 -0
  104. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/server.py +0 -0
  105. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/shared_params.py +0 -0
  106. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/sort_href.py +0 -0
  107. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/sql_quote.py +0 -0
  108. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/static/elapsed.js +0 -0
  109. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/static/experiment.js +0 -0
  110. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/static/labnotes.css +0 -0
  111. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/static/local-time.js +0 -0
  112. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/static/new-note.js +0 -0
  113. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/static/note-edit.js +0 -0
  114. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/static/note-editor.js +0 -0
  115. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/static/row-link.js +0 -0
  116. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/static/run-cell.js +0 -0
  117. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/static/run-output.js +0 -0
  118. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/static/view-toggle.js +0 -0
  119. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/summarize_config.py +0 -0
  120. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/__init__.py +0 -0
  121. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/experiment_rows.py +0 -0
  122. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/experiment_run_ids.py +0 -0
  123. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/file_prefix.py +0 -0
  124. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/file_text.py +0 -0
  125. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/iso_time.py +0 -0
  126. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/list_experiment_runs.py +0 -0
  127. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/note_text.py +0 -0
  128. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/open_tables.py +0 -0
  129. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/path_glob.py +0 -0
  130. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/path_pattern.py +0 -0
  131. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/read_experiment.py +0 -0
  132. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/read_file.py +0 -0
  133. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/read_log.py +0 -0
  134. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/read_run.py +0 -0
  135. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tables/summarize_experiments.py +0 -0
  136. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/templates/base.html +0 -0
  137. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/templates/editor_imports.html +0 -0
  138. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/templates/experiment.html +0 -0
  139. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/templates/index.html +0 -0
  140. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/templates/note.html +0 -0
  141. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/templates/run.html +0 -0
  142. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/__init__.py +0 -0
  143. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/browse_app.py +0 -0
  144. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/browse_tui.py +0 -0
  145. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/dim.py +0 -0
  146. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/experiment_screen.py +0 -0
  147. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/experiments_screen.py +0 -0
  148. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/fill_table.py +0 -0
  149. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/format_elapsed.py +0 -0
  150. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/group_cells.py +0 -0
  151. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/invocation_columns.py +0 -0
  152. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/invocation_rows.py +0 -0
  153. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/reload_on_change.py +0 -0
  154. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/run_app.py +0 -0
  155. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/run_cells.py +0 -0
  156. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/run_screen.py +0 -0
  157. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/run_tui.py +0 -0
  158. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/runs_table.py +0 -0
  159. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/tui/stopped_title.py +0 -0
  160. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/wait_for_note.py +0 -0
  161. {labnotes-0.0.95 → labnotes-0.0.96}/src/labnotes/watch_rows.py +0 -0
  162. {labnotes-0.0.95 → labnotes-0.0.96}/tests/integration/conftest.py +0 -0
  163. {labnotes-0.0.95 → labnotes-0.0.96}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.95
3
+ Version: 0.0.96
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>
@@ -0,0 +1,61 @@
1
+ import codecs
2
+ from pathlib import Path
3
+
4
+ from rich.text import Text
5
+ from textual.geometry import Size
6
+ from textual.widgets import RichLog
7
+
8
+ STREAMS = ("stdout", "stderr")
9
+
10
+
11
+ class RunOutput(RichLog):
12
+ def __init__(self, id: str | None = None) -> None:
13
+ super().__init__(id=id)
14
+ self.reset()
15
+
16
+ def reset(self) -> None:
17
+ self.offsets = dict.fromkeys(STREAMS, 0)
18
+ self.decoders = {
19
+ stream: codecs.getincrementaldecoder("utf-8")(errors="replace")
20
+ for stream in STREAMS
21
+ }
22
+ self.last_stream: str | None = None
23
+ self.unfinished = ""
24
+ self.clear()
25
+
26
+ @property
27
+ def mid_line(self) -> bool:
28
+ return bool(self.unfinished)
29
+
30
+ def follow(self, folder: Path) -> None:
31
+ for stream in STREAMS:
32
+ path = folder / f"{stream}.log"
33
+ if not path.exists():
34
+ continue
35
+ with path.open("rb") as log:
36
+ log.seek(self.offsets[stream])
37
+ data = log.read()
38
+ self.offsets[stream] += len(data)
39
+ text = self.decoders[stream].decode(data)
40
+ if not text:
41
+ continue
42
+ # A stream's unfinished line is redrawn as it grows, but the
43
+ # other stream's output starts below it.
44
+ if self.mid_line and stream == self.last_stream:
45
+ self.drop_last_line()
46
+ text = self.unfinished + text
47
+ *complete, self.unfinished = text.split("\n")
48
+ if complete:
49
+ self.write(Text.from_ansi("\n".join(complete)))
50
+ if self.unfinished:
51
+ self.write(Text.from_ansi(self.unfinished))
52
+ self.last_stream = stream
53
+
54
+ def drop_last_line(self) -> None:
55
+ if not self._size_known:
56
+ self._deferred_renders.pop()
57
+ return
58
+ del self.lines[-1]
59
+ self._line_cache.clear()
60
+ self.virtual_size = Size(self.virtual_size.width, len(self.lines))
61
+ self.refresh()
@@ -7,8 +7,8 @@ reason = "LOCK_EX and LOCK_NB are disjoint bits, so | equals + and ^"
7
7
  [[python.exempt]]
8
8
  path = "src/labnotes/tui/run_output.py"
9
9
  rules = ["mutation"]
10
- lines = [37]
11
- reason = "last_stream is always the same str object taken from STREAMS, so is not equals !="
10
+ lines = [44]
11
+ reason = "last_stream is always the same str object taken from STREAMS, so is equals =="
12
12
 
13
13
  [[python.exempt]]
14
14
  path = "src/labnotes/load_experiment.py"
@@ -1,41 +0,0 @@
1
- import codecs
2
- from pathlib import Path
3
-
4
- from textual.widgets import Log
5
-
6
- STREAMS = ("stdout", "stderr")
7
-
8
-
9
- class RunOutput(Log):
10
- def __init__(self, id: str | None = None) -> None:
11
- super().__init__(id=id)
12
- self.reset()
13
-
14
- def reset(self) -> None:
15
- self.offsets = dict.fromkeys(STREAMS, 0)
16
- self.decoders = {
17
- stream: codecs.getincrementaldecoder("utf-8")(errors="replace")
18
- for stream in STREAMS
19
- }
20
- self.last_stream: str | None = None
21
- self.mid_line = False
22
- self.clear()
23
-
24
- def follow(self, folder: Path) -> None:
25
- for stream in STREAMS:
26
- path = folder / f"{stream}.log"
27
- if not path.exists():
28
- continue
29
- with path.open("rb") as log:
30
- log.seek(self.offsets[stream])
31
- data = log.read()
32
- self.offsets[stream] += len(data)
33
- text = self.decoders[stream].decode(data)
34
- if not text:
35
- continue
36
- # Don't glue one stream's unfinished line onto the other's output.
37
- if self.mid_line and stream != self.last_stream:
38
- text = "\n" + text
39
- self.write(text)
40
- self.last_stream = stream
41
- self.mid_line = not text.endswith("\n")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes