labnotes 0.0.88__tar.gz → 0.0.89__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 (158) hide show
  1. {labnotes-0.0.88 → labnotes-0.0.89}/PKG-INFO +1 -1
  2. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/run_app.py +9 -39
  3. labnotes-0.0.89/src/labnotes/tui/run_output.py +41 -0
  4. {labnotes-0.0.88 → labnotes-0.0.89}/testing-conventions.toml +2 -2
  5. {labnotes-0.0.88 → labnotes-0.0.89}/.gitignore +0 -0
  6. {labnotes-0.0.88 → labnotes-0.0.89}/CHANGELOG.md +0 -0
  7. {labnotes-0.0.88 → labnotes-0.0.89}/MIGRATIONS.md +0 -0
  8. {labnotes-0.0.88 → labnotes-0.0.89}/README.md +0 -0
  9. {labnotes-0.0.88 → labnotes-0.0.89}/pyproject.toml +0 -0
  10. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/__init__.py +0 -0
  11. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/__main__.py +0 -0
  12. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/asset_url.py +0 -0
  13. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/check_counts.py +0 -0
  14. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/cli.py +0 -0
  15. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/context.md +0 -0
  16. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/context_guide.py +0 -0
  17. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/count_groups.py +0 -0
  18. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/count_runs.py +0 -0
  19. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/create_app.py +0 -0
  20. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/create_experiment.py +0 -0
  21. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/create_note.py +0 -0
  22. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/__init__.py +0 -0
  23. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/control_run.py +0 -0
  24. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/daemon_app.py +0 -0
  25. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/daemon_client.py +0 -0
  26. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/daemon_running.py +0 -0
  27. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/daemon_server.py +0 -0
  28. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/describe_status.py +0 -0
  29. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/ensure_daemon.py +0 -0
  30. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/exit_when_idle.py +0 -0
  31. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/remote_start.py +0 -0
  32. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/run_daemon.py +0 -0
  33. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/run_logs.py +0 -0
  34. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/serve_daemon.py +0 -0
  35. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/socket_path.py +0 -0
  36. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/stop_runs.py +0 -0
  37. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/watch_daemon.py +0 -0
  38. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon/watch_status.py +0 -0
  39. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/daemon_command.py +0 -0
  40. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/data_html.py +0 -0
  41. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/default_config_text.py +0 -0
  42. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/default_counts.py +0 -0
  43. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/drain_watch.py +0 -0
  44. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/experiment_notes.py +0 -0
  45. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/experiment_runs.py +0 -0
  46. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/experiments.py +0 -0
  47. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/file_changes.py +0 -0
  48. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/format_duration.py +0 -0
  49. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/format_started.py +0 -0
  50. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/format_value.py +0 -0
  51. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/group_runs.py +0 -0
  52. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/init.py +0 -0
  53. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/invocation_started.py +0 -0
  54. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/is_positive_int.py +0 -0
  55. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/list_experiments.py +0 -0
  56. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/list_runs.py +0 -0
  57. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/load_config.py +0 -0
  58. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/load_experiment.py +0 -0
  59. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/load_run.py +0 -0
  60. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/models.py +0 -0
  61. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/modes.py +0 -0
  62. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/nests_runs.py +0 -0
  63. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/note_path.py +0 -0
  64. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/notebook_kernel.py +0 -0
  65. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/output_html.py +0 -0
  66. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/parse_run_view.py +0 -0
  67. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/print_rows.py +0 -0
  68. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/python_blocks.py +0 -0
  69. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/read_config.py +0 -0
  70. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/read_path.py +0 -0
  71. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/render_markdown.py +0 -0
  72. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/run_command.py +0 -0
  73. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/run_control.py +0 -0
  74. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/run_output.py +0 -0
  75. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/run_status.py +0 -0
  76. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/__init__.py +0 -0
  77. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/describe_event.py +0 -0
  78. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/execute_run.py +0 -0
  79. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/hold_folder.py +0 -0
  80. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/new_param_run_id.py +0 -0
  81. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/new_run_id.py +0 -0
  82. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  83. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/param_args.py +0 -0
  84. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/param_grid.py +0 -0
  85. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/param_run_name.py +0 -0
  86. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/plan_runs.py +0 -0
  87. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/prepare_experiment.py +0 -0
  88. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/read_experiment_config.py +0 -0
  89. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/run_experiment.py +0 -0
  90. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/run_folder.py +0 -0
  91. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/run_headless.py +0 -0
  92. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/runs_parent.py +0 -0
  93. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/shell_command.py +0 -0
  94. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/signal_tree.py +0 -0
  95. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/stop_then_kill.py +0 -0
  96. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/wait_for_tree.py +0 -0
  97. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/runner/write_run_json.py +0 -0
  98. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/save_conflict_copy.py +0 -0
  99. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/save_note.py +0 -0
  100. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/saved_outputs.py +0 -0
  101. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/serve_app.py +0 -0
  102. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/server.py +0 -0
  103. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/shared_params.py +0 -0
  104. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/sort_href.py +0 -0
  105. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/sql_quote.py +0 -0
  106. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/static/elapsed.js +0 -0
  107. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/static/experiment.js +0 -0
  108. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/static/labnotes.css +0 -0
  109. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/static/local-time.js +0 -0
  110. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/static/new-note.js +0 -0
  111. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/static/note-edit.js +0 -0
  112. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/static/note-editor.js +0 -0
  113. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/static/row-link.js +0 -0
  114. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/static/run-cell.js +0 -0
  115. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/static/run-output.js +0 -0
  116. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/static/view-toggle.js +0 -0
  117. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/summarize_config.py +0 -0
  118. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/__init__.py +0 -0
  119. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/experiment_rows.py +0 -0
  120. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/experiment_run_ids.py +0 -0
  121. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/file_prefix.py +0 -0
  122. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/file_text.py +0 -0
  123. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/iso_time.py +0 -0
  124. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/list_experiment_runs.py +0 -0
  125. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/note_text.py +0 -0
  126. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/open_tables.py +0 -0
  127. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/path_glob.py +0 -0
  128. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/path_pattern.py +0 -0
  129. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/read_experiment.py +0 -0
  130. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/read_file.py +0 -0
  131. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/read_log.py +0 -0
  132. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/read_run.py +0 -0
  133. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tables/summarize_experiments.py +0 -0
  134. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/templates/base.html +0 -0
  135. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/templates/editor_imports.html +0 -0
  136. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/templates/experiment.html +0 -0
  137. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/templates/index.html +0 -0
  138. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/templates/note.html +0 -0
  139. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/templates/run.html +0 -0
  140. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/__init__.py +0 -0
  141. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/browse_app.py +0 -0
  142. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/browse_tui.py +0 -0
  143. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/dim.py +0 -0
  144. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/experiment_screen.py +0 -0
  145. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/experiments_screen.py +0 -0
  146. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/fill_table.py +0 -0
  147. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/format_elapsed.py +0 -0
  148. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/group_cells.py +0 -0
  149. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/invocation_columns.py +0 -0
  150. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/invocation_rows.py +0 -0
  151. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/reload_on_change.py +0 -0
  152. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/run_cells.py +0 -0
  153. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/run_tui.py +0 -0
  154. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/tui/runs_table.py +0 -0
  155. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/wait_for_note.py +0 -0
  156. {labnotes-0.0.88 → labnotes-0.0.89}/src/labnotes/watch_rows.py +0 -0
  157. {labnotes-0.0.88 → labnotes-0.0.89}/tests/integration/conftest.py +0 -0
  158. {labnotes-0.0.88 → labnotes-0.0.89}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.88
3
+ Version: 0.0.89
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>
@@ -1,5 +1,4 @@
1
1
  import asyncio
2
- import codecs
3
2
  import signal
4
3
  import time
5
4
  from pathlib import Path
@@ -7,7 +6,7 @@ from pathlib import Path
7
6
  from rich.text import Text
8
7
  from textual.app import App, ComposeResult
9
8
  from textual.binding import Binding
10
- from textual.widgets import DataTable, Footer, Header, Input, Log, Static
9
+ from textual.widgets import DataTable, Footer, Header, Input, Static
11
10
 
12
11
  from labnotes.models import Controls, Start
13
12
  from labnotes.runner.describe_event import describe_event
@@ -15,10 +14,10 @@ from labnotes.runner.stop_then_kill import stop_then_kill
15
14
  from labnotes.runner.param_args import param_args
16
15
  from labnotes.tui.dim import dim
17
16
  from labnotes.tui.format_elapsed import format_elapsed
17
+ from labnotes.tui.run_output import RunOutput
18
18
  from labnotes.tui.runs_table import RunsTable
19
19
 
20
20
  COLUMNS = ("run", "params", "status", "time")
21
- STREAMS = ("stdout", "stderr")
22
21
  PENDING, PASSED, FAILED = "○", "✓", "✗"
23
22
  SPINNER = "|/-\\"
24
23
  SKIPPED, DESELECTED = "–", "⊘"
@@ -80,18 +79,11 @@ class RunApp(App[None]):
80
79
  self.viewing: str | None = None
81
80
  self.grabbed: str | None = None
82
81
  self.grabbed_from: list[str] = []
83
- self.offsets: dict[str, int]
84
- self.decoders = {
85
- stream: codecs.getincrementaldecoder("utf-8")(errors="replace")
86
- for stream in STREAMS
87
- }
88
- self.last_stream: str | None
89
- self.mid_line: bool
90
82
 
91
83
  def compose(self) -> ComposeResult:
92
84
  yield Header()
93
85
  yield RunsTable(id="runs", cursor_type="row")
94
- yield Log(id="output")
86
+ yield RunOutput(id="output")
95
87
  yield Input(id="filter", placeholder="Filter params")
96
88
  yield Static(id="status")
97
89
  yield Footer()
@@ -101,7 +93,7 @@ class RunApp(App[None]):
101
93
  for column in COLUMNS:
102
94
  table.add_column(column, key=column)
103
95
  self.query_one(Input).display = False
104
- self.query_one(Log).display = False
96
+ self.query_one(RunOutput).display = False
105
97
  self.query_one(Header).mount(Static(id="concurrency"))
106
98
  table.set_interval(0.1, self.spin)
107
99
 
@@ -373,44 +365,22 @@ class RunApp(App[None]):
373
365
 
374
366
  def view(self, run: str) -> None:
375
367
  self.viewing = run
376
- self.offsets = dict.fromkeys(STREAMS, 0)
377
- for decoder in self.decoders.values():
378
- decoder.reset()
379
- self.last_stream = None
380
- self.mid_line = False
381
- self.query_one(Log).clear()
368
+ self.query_one(RunOutput).reset()
382
369
  self.query_one(DataTable).display = False
383
- self.query_one(Log).display = True
370
+ self.query_one(RunOutput).display = True
384
371
  self.sub_title = run
385
372
  self.show("")
386
373
  self.refresh_bindings()
387
374
  self.follow()
388
375
 
389
376
  def follow(self) -> None:
390
- if self.viewing not in self.folders:
391
- return
392
- for stream in STREAMS:
393
- path = self.folders[self.viewing] / f"{stream}.log"
394
- if not path.exists():
395
- continue
396
- with path.open("rb") as log:
397
- log.seek(self.offsets[stream])
398
- data = log.read()
399
- self.offsets[stream] += len(data)
400
- text = self.decoders[stream].decode(data)
401
- if not text:
402
- continue
403
- # Don't glue one stream's unfinished line onto the other's output.
404
- if self.mid_line and stream != self.last_stream:
405
- text = "\n" + text
406
- self.query_one(Log).write(text)
407
- self.last_stream = stream
408
- self.mid_line = not text.endswith("\n")
377
+ if self.viewing in self.folders:
378
+ self.query_one(RunOutput).follow(self.folders[self.viewing])
409
379
 
410
380
  def action_leave(self) -> None:
411
381
  if self.viewing is not None:
412
382
  self.viewing = None
413
- self.query_one(Log).display = False
383
+ self.query_one(RunOutput).display = False
414
384
  self.query_one(DataTable).display = True
415
385
  self.sub_title = ""
416
386
  self.refresh_bindings()
@@ -0,0 +1,41 @@
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")
@@ -5,9 +5,9 @@ lines = [12]
5
5
  reason = "LOCK_EX and LOCK_NB are disjoint bits, so | equals + and ^"
6
6
 
7
7
  [[python.exempt]]
8
- path = "src/labnotes/tui/run_app.py"
8
+ path = "src/labnotes/tui/run_output.py"
9
9
  rules = ["mutation"]
10
- lines = [311]
10
+ lines = [37]
11
11
  reason = "last_stream is always the same str object taken from STREAMS, so is not equals !="
12
12
 
13
13
  [[python.exempt]]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes