labnotes 0.0.87__tar.gz → 0.0.88__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.87 → labnotes-0.0.88}/PKG-INFO +1 -1
  2. labnotes-0.0.88/src/labnotes/file_changes.py +11 -0
  3. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/experiment_screen.py +21 -9
  4. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/experiments_screen.py +21 -11
  5. labnotes-0.0.88/src/labnotes/tui/fill_table.py +26 -0
  6. labnotes-0.0.88/src/labnotes/tui/reload_on_change.py +22 -0
  7. labnotes-0.0.88/src/labnotes/watch_rows.py +13 -0
  8. labnotes-0.0.87/src/labnotes/file_changes.py +0 -11
  9. {labnotes-0.0.87 → labnotes-0.0.88}/.gitignore +0 -0
  10. {labnotes-0.0.87 → labnotes-0.0.88}/CHANGELOG.md +0 -0
  11. {labnotes-0.0.87 → labnotes-0.0.88}/MIGRATIONS.md +0 -0
  12. {labnotes-0.0.87 → labnotes-0.0.88}/README.md +0 -0
  13. {labnotes-0.0.87 → labnotes-0.0.88}/pyproject.toml +0 -0
  14. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/__init__.py +0 -0
  15. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/__main__.py +0 -0
  16. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/asset_url.py +0 -0
  17. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/check_counts.py +0 -0
  18. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/cli.py +0 -0
  19. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/context.md +0 -0
  20. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/context_guide.py +0 -0
  21. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/count_groups.py +0 -0
  22. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/count_runs.py +0 -0
  23. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/create_app.py +0 -0
  24. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/create_experiment.py +0 -0
  25. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/create_note.py +0 -0
  26. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/__init__.py +0 -0
  27. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/control_run.py +0 -0
  28. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/daemon_app.py +0 -0
  29. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/daemon_client.py +0 -0
  30. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/daemon_running.py +0 -0
  31. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/daemon_server.py +0 -0
  32. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/describe_status.py +0 -0
  33. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/ensure_daemon.py +0 -0
  34. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/exit_when_idle.py +0 -0
  35. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/remote_start.py +0 -0
  36. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/run_daemon.py +0 -0
  37. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/run_logs.py +0 -0
  38. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/serve_daemon.py +0 -0
  39. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/socket_path.py +0 -0
  40. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/stop_runs.py +0 -0
  41. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/watch_daemon.py +0 -0
  42. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon/watch_status.py +0 -0
  43. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/daemon_command.py +0 -0
  44. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/data_html.py +0 -0
  45. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/default_config_text.py +0 -0
  46. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/default_counts.py +0 -0
  47. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/drain_watch.py +0 -0
  48. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/experiment_notes.py +0 -0
  49. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/experiment_runs.py +0 -0
  50. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/experiments.py +0 -0
  51. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/format_duration.py +0 -0
  52. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/format_started.py +0 -0
  53. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/format_value.py +0 -0
  54. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/group_runs.py +0 -0
  55. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/init.py +0 -0
  56. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/invocation_started.py +0 -0
  57. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/is_positive_int.py +0 -0
  58. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/list_experiments.py +0 -0
  59. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/list_runs.py +0 -0
  60. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/load_config.py +0 -0
  61. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/load_experiment.py +0 -0
  62. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/load_run.py +0 -0
  63. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/models.py +0 -0
  64. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/modes.py +0 -0
  65. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/nests_runs.py +0 -0
  66. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/note_path.py +0 -0
  67. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/notebook_kernel.py +0 -0
  68. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/output_html.py +0 -0
  69. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/parse_run_view.py +0 -0
  70. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/print_rows.py +0 -0
  71. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/python_blocks.py +0 -0
  72. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/read_config.py +0 -0
  73. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/read_path.py +0 -0
  74. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/render_markdown.py +0 -0
  75. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/run_command.py +0 -0
  76. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/run_control.py +0 -0
  77. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/run_output.py +0 -0
  78. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/run_status.py +0 -0
  79. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/__init__.py +0 -0
  80. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/describe_event.py +0 -0
  81. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/execute_run.py +0 -0
  82. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/hold_folder.py +0 -0
  83. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/new_param_run_id.py +0 -0
  84. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/new_run_id.py +0 -0
  85. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  86. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/param_args.py +0 -0
  87. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/param_grid.py +0 -0
  88. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/param_run_name.py +0 -0
  89. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/plan_runs.py +0 -0
  90. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/prepare_experiment.py +0 -0
  91. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/read_experiment_config.py +0 -0
  92. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/run_experiment.py +0 -0
  93. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/run_folder.py +0 -0
  94. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/run_headless.py +0 -0
  95. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/runs_parent.py +0 -0
  96. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/shell_command.py +0 -0
  97. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/signal_tree.py +0 -0
  98. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/stop_then_kill.py +0 -0
  99. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/wait_for_tree.py +0 -0
  100. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/runner/write_run_json.py +0 -0
  101. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/save_conflict_copy.py +0 -0
  102. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/save_note.py +0 -0
  103. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/saved_outputs.py +0 -0
  104. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/serve_app.py +0 -0
  105. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/server.py +0 -0
  106. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/shared_params.py +0 -0
  107. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/sort_href.py +0 -0
  108. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/sql_quote.py +0 -0
  109. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/static/elapsed.js +0 -0
  110. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/static/experiment.js +0 -0
  111. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/static/labnotes.css +0 -0
  112. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/static/local-time.js +0 -0
  113. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/static/new-note.js +0 -0
  114. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/static/note-edit.js +0 -0
  115. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/static/note-editor.js +0 -0
  116. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/static/row-link.js +0 -0
  117. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/static/run-cell.js +0 -0
  118. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/static/run-output.js +0 -0
  119. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/static/view-toggle.js +0 -0
  120. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/summarize_config.py +0 -0
  121. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/__init__.py +0 -0
  122. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/experiment_rows.py +0 -0
  123. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/experiment_run_ids.py +0 -0
  124. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/file_prefix.py +0 -0
  125. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/file_text.py +0 -0
  126. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/iso_time.py +0 -0
  127. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/list_experiment_runs.py +0 -0
  128. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/note_text.py +0 -0
  129. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/open_tables.py +0 -0
  130. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/path_glob.py +0 -0
  131. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/path_pattern.py +0 -0
  132. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/read_experiment.py +0 -0
  133. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/read_file.py +0 -0
  134. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/read_log.py +0 -0
  135. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/read_run.py +0 -0
  136. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tables/summarize_experiments.py +0 -0
  137. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/templates/base.html +0 -0
  138. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/templates/editor_imports.html +0 -0
  139. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/templates/experiment.html +0 -0
  140. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/templates/index.html +0 -0
  141. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/templates/note.html +0 -0
  142. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/templates/run.html +0 -0
  143. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/__init__.py +0 -0
  144. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/browse_app.py +0 -0
  145. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/browse_tui.py +0 -0
  146. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/dim.py +0 -0
  147. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/format_elapsed.py +0 -0
  148. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/group_cells.py +0 -0
  149. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/invocation_columns.py +0 -0
  150. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/invocation_rows.py +0 -0
  151. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/run_app.py +0 -0
  152. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/run_cells.py +0 -0
  153. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/run_tui.py +0 -0
  154. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/tui/runs_table.py +0 -0
  155. {labnotes-0.0.87 → labnotes-0.0.88}/src/labnotes/wait_for_note.py +0 -0
  156. {labnotes-0.0.87 → labnotes-0.0.88}/testing-conventions.toml +0 -0
  157. {labnotes-0.0.87 → labnotes-0.0.88}/tests/integration/conftest.py +0 -0
  158. {labnotes-0.0.87 → labnotes-0.0.88}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.87
3
+ Version: 0.0.88
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,11 @@
1
+ import json
2
+ from collections.abc import AsyncIterator
3
+
4
+ from dirsql import DirSQL
5
+
6
+ from labnotes.watch_rows import watch_rows
7
+
8
+
9
+ async def file_changes(tables: DirSQL, experiment: str) -> AsyncIterator[str]:
10
+ async for row in watch_rows(tables, {"files"}, experiment):
11
+ yield f"data: {json.dumps(row['filename'])}\n\n"
@@ -10,9 +10,14 @@ from textual.worker import Worker, WorkerState
10
10
 
11
11
  from labnotes.load_experiment import load_experiment
12
12
  from labnotes.models import LabnotesConfig
13
+ from labnotes.tui.fill_table import fill_table
13
14
  from labnotes.tui.invocation_columns import invocation_columns
14
15
  from labnotes.tui.invocation_rows import invocation_rows
16
+ from labnotes.tui.reload_on_change import reload_on_change
15
17
  from labnotes.tui.runs_table import RunsTable
18
+ from labnotes.watch_rows import watch_rows
19
+
20
+ WATCHED = {"runs", "experiment_configs"}
16
21
 
17
22
 
18
23
  class ExperimentScreen(Screen[None]):
@@ -36,7 +41,12 @@ class ExperimentScreen(Screen[None]):
36
41
 
37
42
  def on_mount(self) -> None:
38
43
  self.sub_title = self.experiment_name
39
- self.run_worker(self.load(), exit_on_error=False)
44
+ self.run_worker(
45
+ reload_on_change(
46
+ watch_rows(self.tables, WATCHED, self.experiment_name), self.load
47
+ ),
48
+ exit_on_error=False,
49
+ )
40
50
 
41
51
  def on_worker_state_changed(self, event: Worker.StateChanged) -> None:
42
52
  if event.state == WorkerState.ERROR:
@@ -50,20 +60,22 @@ class ExperimentScreen(Screen[None]):
50
60
  self.experiment_name,
51
61
  QueryParams(),
52
62
  )
53
- if loaded is None:
63
+ if loaded is None and not self.experiment:
54
64
  raise ValueError(f"No experiment named {self.experiment_name}.")
65
+ if loaded is None:
66
+ self.sub_title = f"{self.experiment_name} was removed."
67
+ fill_table(self.query_one(DataTable), [], [])
68
+ return
55
69
  self.experiment = loaded
56
70
  self.sub_title = str(loaded["display_name"])
57
- self.query_one(DataTable).add_columns(*invocation_columns(loaded))
58
71
  self.redraw()
59
72
 
60
73
  def redraw(self) -> None:
61
- table = self.query_one(DataTable)
62
- row = table.cursor_row
63
- table.clear()
64
- for key, cells in invocation_rows(self.experiment, self.opened):
65
- table.add_row(*cells, key=key)
66
- table.move_cursor(row=row)
74
+ fill_table(
75
+ self.query_one(DataTable),
76
+ invocation_columns(self.experiment),
77
+ invocation_rows(self.experiment, self.opened),
78
+ )
67
79
 
68
80
  def action_toggle_invocation(self) -> None:
69
81
  table = self.query_one(DataTable)
@@ -7,7 +7,12 @@ from textual.worker import Worker, WorkerState
7
7
  from labnotes.models import LabnotesConfig
8
8
  from labnotes.tables.summarize_experiments import summarize_experiments
9
9
  from labnotes.tui.experiment_screen import ExperimentScreen
10
+ from labnotes.tui.fill_table import fill_table
11
+ from labnotes.tui.reload_on_change import reload_on_change
10
12
  from labnotes.tui.runs_table import RunsTable
13
+ from labnotes.watch_rows import watch_rows
14
+
15
+ WATCHED = {"files", "runs", "experiment_configs"}
11
16
 
12
17
 
13
18
  class ExperimentsScreen(Screen[None]):
@@ -22,7 +27,10 @@ class ExperimentsScreen(Screen[None]):
22
27
  yield Footer()
23
28
 
24
29
  def on_mount(self) -> None:
25
- self.run_worker(self.load(), exit_on_error=False)
30
+ self.run_worker(
31
+ reload_on_change(watch_rows(self.tables, WATCHED), self.load),
32
+ exit_on_error=False,
33
+ )
26
34
 
27
35
  def on_worker_state_changed(self, event: Worker.StateChanged) -> None:
28
36
  if event.state == WorkerState.ERROR:
@@ -30,16 +38,18 @@ class ExperimentsScreen(Screen[None]):
30
38
 
31
39
  async def load(self) -> None:
32
40
  rows = await summarize_experiments(self.tables)
33
- if not rows:
34
- self.sub_title = "No experiments."
35
- return
36
- table = self.query_one(DataTable)
37
- table.add_columns(*rows[0])
38
- for row in rows:
39
- table.add_row(
40
- *("" if value is None else str(value) for value in row.values()),
41
- key=str(row["name"]),
42
- )
41
+ self.sub_title = "" if rows else "No experiments."
42
+ fill_table(
43
+ self.query_one(DataTable),
44
+ list(next(iter(rows), ())),
45
+ [
46
+ (
47
+ str(row["name"]),
48
+ ["" if value is None else str(value) for value in row.values()],
49
+ )
50
+ for row in rows
51
+ ],
52
+ )
43
53
 
44
54
  def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
45
55
  self.app.push_screen(
@@ -0,0 +1,26 @@
1
+ from collections.abc import Sequence
2
+
3
+ from textual.widgets import DataTable
4
+
5
+
6
+ def fill_table(
7
+ table: DataTable[object],
8
+ columns: Sequence[str],
9
+ rows: Sequence[tuple[str, Sequence[object]]],
10
+ ) -> None:
11
+ # The cursor follows its row by key, so rows arriving above it don't move it.
12
+ row = table.cursor_row
13
+ key = (
14
+ table.coordinate_to_cell_key(table.cursor_coordinate).row_key.value
15
+ if table.row_count
16
+ else None
17
+ )
18
+ if [str(column.label) for column in table.columns.values()] == list(columns):
19
+ table.clear()
20
+ else:
21
+ table.clear(columns=True)
22
+ table.add_columns(*columns)
23
+ keys = [each for each, _ in rows]
24
+ for each, cells in rows:
25
+ table.add_row(*cells, key=each)
26
+ table.move_cursor(row=keys.index(key) if key in keys else row)
@@ -0,0 +1,22 @@
1
+ import asyncio
2
+ from collections.abc import AsyncIterable, Awaitable, Callable
3
+
4
+
5
+ async def reload_on_change(
6
+ changes: AsyncIterable[object], reload: Callable[[], Awaitable[None]]
7
+ ) -> None:
8
+ # Changes that land during a reload collapse into one more reload.
9
+ stale = asyncio.Event()
10
+
11
+ async def mark() -> None:
12
+ async for _ in changes:
13
+ stale.set()
14
+
15
+ marker = asyncio.create_task(mark())
16
+ try:
17
+ while True:
18
+ await reload()
19
+ await stale.wait()
20
+ stale.clear()
21
+ finally:
22
+ marker.cancel()
@@ -0,0 +1,13 @@
1
+ from collections.abc import AsyncIterator, Collection
2
+ from typing import Any
3
+
4
+ from dirsql import DirSQL
5
+
6
+
7
+ async def watch_rows(
8
+ tables: DirSQL, names: Collection[str], experiment: str | None = None
9
+ ) -> AsyncIterator[dict[str, Any]]:
10
+ async for event in tables.watch():
11
+ row = event.row or event.old_row
12
+ if event.table in names and row and experiment in (None, row["experiment"]):
13
+ yield row
@@ -1,11 +0,0 @@
1
- import json
2
- from collections.abc import AsyncIterator
3
-
4
- from dirsql import DirSQL
5
-
6
-
7
- async def file_changes(tables: DirSQL, experiment: str) -> AsyncIterator[str]:
8
- async for event in tables.watch():
9
- row = event.row or event.old_row
10
- if event.table == "files" and row and row["experiment"] == experiment:
11
- yield f"data: {json.dumps(row['filename'])}\n\n"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes