py-data-engine 0.1.0__py3-none-any.whl

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 (200) hide show
  1. data_engine/__init__.py +37 -0
  2. data_engine/application/__init__.py +39 -0
  3. data_engine/application/actions.py +42 -0
  4. data_engine/application/catalog.py +151 -0
  5. data_engine/application/control.py +213 -0
  6. data_engine/application/details.py +73 -0
  7. data_engine/application/runtime.py +449 -0
  8. data_engine/application/workspace.py +62 -0
  9. data_engine/authoring/__init__.py +14 -0
  10. data_engine/authoring/builder.py +31 -0
  11. data_engine/authoring/execution/__init__.py +6 -0
  12. data_engine/authoring/execution/app.py +6 -0
  13. data_engine/authoring/execution/context.py +82 -0
  14. data_engine/authoring/execution/continuous.py +176 -0
  15. data_engine/authoring/execution/grouped.py +106 -0
  16. data_engine/authoring/execution/logging.py +83 -0
  17. data_engine/authoring/execution/polling.py +135 -0
  18. data_engine/authoring/execution/runner.py +210 -0
  19. data_engine/authoring/execution/single.py +171 -0
  20. data_engine/authoring/flow.py +361 -0
  21. data_engine/authoring/helpers.py +160 -0
  22. data_engine/authoring/model.py +59 -0
  23. data_engine/authoring/primitives.py +430 -0
  24. data_engine/authoring/services.py +42 -0
  25. data_engine/devtools/__init__.py +3 -0
  26. data_engine/devtools/project_ast_map.py +503 -0
  27. data_engine/docs/__init__.py +1 -0
  28. data_engine/docs/sphinx_source/_static/custom.css +13 -0
  29. data_engine/docs/sphinx_source/api.rst +42 -0
  30. data_engine/docs/sphinx_source/conf.py +37 -0
  31. data_engine/docs/sphinx_source/guides/app-runtime-and-workspaces.md +397 -0
  32. data_engine/docs/sphinx_source/guides/authoring-flow-modules.md +215 -0
  33. data_engine/docs/sphinx_source/guides/configuring-flows.md +185 -0
  34. data_engine/docs/sphinx_source/guides/core-concepts.md +208 -0
  35. data_engine/docs/sphinx_source/guides/database-methods.md +107 -0
  36. data_engine/docs/sphinx_source/guides/duckdb-helpers.md +462 -0
  37. data_engine/docs/sphinx_source/guides/flow-context.md +538 -0
  38. data_engine/docs/sphinx_source/guides/flow-methods.md +206 -0
  39. data_engine/docs/sphinx_source/guides/getting-started.md +271 -0
  40. data_engine/docs/sphinx_source/guides/project-inventory.md +5683 -0
  41. data_engine/docs/sphinx_source/guides/project-map.md +118 -0
  42. data_engine/docs/sphinx_source/guides/recipes.md +268 -0
  43. data_engine/docs/sphinx_source/index.rst +22 -0
  44. data_engine/domain/__init__.py +92 -0
  45. data_engine/domain/actions.py +69 -0
  46. data_engine/domain/catalog.py +128 -0
  47. data_engine/domain/details.py +214 -0
  48. data_engine/domain/diagnostics.py +56 -0
  49. data_engine/domain/errors.py +104 -0
  50. data_engine/domain/inspection.py +99 -0
  51. data_engine/domain/logs.py +118 -0
  52. data_engine/domain/operations.py +172 -0
  53. data_engine/domain/operator.py +72 -0
  54. data_engine/domain/runs.py +155 -0
  55. data_engine/domain/runtime.py +279 -0
  56. data_engine/domain/source_state.py +17 -0
  57. data_engine/domain/support.py +54 -0
  58. data_engine/domain/time.py +23 -0
  59. data_engine/domain/workspace.py +159 -0
  60. data_engine/flow_modules/__init__.py +1 -0
  61. data_engine/flow_modules/flow_module_compiler.py +179 -0
  62. data_engine/flow_modules/flow_module_loader.py +201 -0
  63. data_engine/helpers/__init__.py +25 -0
  64. data_engine/helpers/duckdb.py +705 -0
  65. data_engine/hosts/__init__.py +1 -0
  66. data_engine/hosts/daemon/__init__.py +23 -0
  67. data_engine/hosts/daemon/app.py +221 -0
  68. data_engine/hosts/daemon/bootstrap.py +69 -0
  69. data_engine/hosts/daemon/client.py +465 -0
  70. data_engine/hosts/daemon/commands.py +64 -0
  71. data_engine/hosts/daemon/composition.py +310 -0
  72. data_engine/hosts/daemon/constants.py +15 -0
  73. data_engine/hosts/daemon/entrypoints.py +97 -0
  74. data_engine/hosts/daemon/lifecycle.py +191 -0
  75. data_engine/hosts/daemon/manager.py +272 -0
  76. data_engine/hosts/daemon/ownership.py +126 -0
  77. data_engine/hosts/daemon/runtime_commands.py +188 -0
  78. data_engine/hosts/daemon/runtime_control.py +31 -0
  79. data_engine/hosts/daemon/server.py +84 -0
  80. data_engine/hosts/daemon/shared_state.py +147 -0
  81. data_engine/hosts/daemon/state_sync.py +101 -0
  82. data_engine/platform/__init__.py +1 -0
  83. data_engine/platform/identity.py +35 -0
  84. data_engine/platform/local_settings.py +146 -0
  85. data_engine/platform/theme.py +259 -0
  86. data_engine/platform/workspace_models.py +190 -0
  87. data_engine/platform/workspace_policy.py +333 -0
  88. data_engine/runtime/__init__.py +1 -0
  89. data_engine/runtime/file_watch.py +185 -0
  90. data_engine/runtime/ledger_models.py +116 -0
  91. data_engine/runtime/runtime_db.py +938 -0
  92. data_engine/runtime/shared_state.py +523 -0
  93. data_engine/services/__init__.py +49 -0
  94. data_engine/services/daemon.py +64 -0
  95. data_engine/services/daemon_state.py +40 -0
  96. data_engine/services/flow_catalog.py +102 -0
  97. data_engine/services/flow_execution.py +48 -0
  98. data_engine/services/ledger.py +85 -0
  99. data_engine/services/logs.py +65 -0
  100. data_engine/services/runtime_binding.py +105 -0
  101. data_engine/services/runtime_execution.py +126 -0
  102. data_engine/services/runtime_history.py +62 -0
  103. data_engine/services/settings.py +58 -0
  104. data_engine/services/shared_state.py +28 -0
  105. data_engine/services/theme.py +59 -0
  106. data_engine/services/workspace_provisioning.py +224 -0
  107. data_engine/services/workspaces.py +74 -0
  108. data_engine/ui/__init__.py +3 -0
  109. data_engine/ui/cli/__init__.py +19 -0
  110. data_engine/ui/cli/app.py +161 -0
  111. data_engine/ui/cli/commands_doctor.py +178 -0
  112. data_engine/ui/cli/commands_run.py +80 -0
  113. data_engine/ui/cli/commands_start.py +100 -0
  114. data_engine/ui/cli/commands_workspace.py +97 -0
  115. data_engine/ui/cli/dependencies.py +44 -0
  116. data_engine/ui/cli/parser.py +56 -0
  117. data_engine/ui/gui/__init__.py +25 -0
  118. data_engine/ui/gui/app.py +116 -0
  119. data_engine/ui/gui/bootstrap.py +487 -0
  120. data_engine/ui/gui/bootstrapper.py +140 -0
  121. data_engine/ui/gui/cache_models.py +23 -0
  122. data_engine/ui/gui/control_support.py +185 -0
  123. data_engine/ui/gui/controllers/__init__.py +6 -0
  124. data_engine/ui/gui/controllers/flows.py +439 -0
  125. data_engine/ui/gui/controllers/runtime.py +245 -0
  126. data_engine/ui/gui/dialogs/__init__.py +12 -0
  127. data_engine/ui/gui/dialogs/messages.py +88 -0
  128. data_engine/ui/gui/dialogs/previews.py +222 -0
  129. data_engine/ui/gui/helpers/__init__.py +62 -0
  130. data_engine/ui/gui/helpers/inspection.py +81 -0
  131. data_engine/ui/gui/helpers/lifecycle.py +112 -0
  132. data_engine/ui/gui/helpers/scroll.py +28 -0
  133. data_engine/ui/gui/helpers/theming.py +87 -0
  134. data_engine/ui/gui/icons/dark_light.svg +12 -0
  135. data_engine/ui/gui/icons/documentation.svg +1 -0
  136. data_engine/ui/gui/icons/failed.svg +3 -0
  137. data_engine/ui/gui/icons/group.svg +4 -0
  138. data_engine/ui/gui/icons/home.svg +2 -0
  139. data_engine/ui/gui/icons/manual.svg +2 -0
  140. data_engine/ui/gui/icons/poll.svg +2 -0
  141. data_engine/ui/gui/icons/schedule.svg +4 -0
  142. data_engine/ui/gui/icons/settings.svg +2 -0
  143. data_engine/ui/gui/icons/started.svg +3 -0
  144. data_engine/ui/gui/icons/success.svg +3 -0
  145. data_engine/ui/gui/icons/view-log.svg +3 -0
  146. data_engine/ui/gui/icons.py +50 -0
  147. data_engine/ui/gui/launcher.py +48 -0
  148. data_engine/ui/gui/presenters/__init__.py +72 -0
  149. data_engine/ui/gui/presenters/docs.py +140 -0
  150. data_engine/ui/gui/presenters/logs.py +58 -0
  151. data_engine/ui/gui/presenters/runtime_projection.py +29 -0
  152. data_engine/ui/gui/presenters/sidebar.py +88 -0
  153. data_engine/ui/gui/presenters/steps.py +148 -0
  154. data_engine/ui/gui/presenters/workspace.py +39 -0
  155. data_engine/ui/gui/presenters/workspace_binding.py +75 -0
  156. data_engine/ui/gui/presenters/workspace_settings.py +182 -0
  157. data_engine/ui/gui/preview_models.py +37 -0
  158. data_engine/ui/gui/render_support.py +241 -0
  159. data_engine/ui/gui/rendering/__init__.py +12 -0
  160. data_engine/ui/gui/rendering/artifacts.py +95 -0
  161. data_engine/ui/gui/rendering/icons.py +50 -0
  162. data_engine/ui/gui/runtime.py +47 -0
  163. data_engine/ui/gui/state_support.py +193 -0
  164. data_engine/ui/gui/support.py +214 -0
  165. data_engine/ui/gui/surface.py +209 -0
  166. data_engine/ui/gui/theme.py +720 -0
  167. data_engine/ui/gui/widgets/__init__.py +34 -0
  168. data_engine/ui/gui/widgets/config.py +41 -0
  169. data_engine/ui/gui/widgets/logs.py +62 -0
  170. data_engine/ui/gui/widgets/panels.py +507 -0
  171. data_engine/ui/gui/widgets/sidebar.py +130 -0
  172. data_engine/ui/gui/widgets/steps.py +84 -0
  173. data_engine/ui/tui/__init__.py +5 -0
  174. data_engine/ui/tui/app.py +222 -0
  175. data_engine/ui/tui/bootstrap.py +475 -0
  176. data_engine/ui/tui/bootstrapper.py +117 -0
  177. data_engine/ui/tui/controllers/__init__.py +6 -0
  178. data_engine/ui/tui/controllers/flows.py +349 -0
  179. data_engine/ui/tui/controllers/runtime.py +167 -0
  180. data_engine/ui/tui/runtime.py +34 -0
  181. data_engine/ui/tui/state_support.py +141 -0
  182. data_engine/ui/tui/support.py +63 -0
  183. data_engine/ui/tui/theme.py +204 -0
  184. data_engine/ui/tui/widgets.py +123 -0
  185. data_engine/views/__init__.py +109 -0
  186. data_engine/views/actions.py +80 -0
  187. data_engine/views/artifacts.py +58 -0
  188. data_engine/views/flow_display.py +69 -0
  189. data_engine/views/logs.py +54 -0
  190. data_engine/views/models.py +96 -0
  191. data_engine/views/presentation.py +133 -0
  192. data_engine/views/runs.py +62 -0
  193. data_engine/views/state.py +39 -0
  194. data_engine/views/status.py +13 -0
  195. data_engine/views/text.py +109 -0
  196. py_data_engine-0.1.0.dist-info/METADATA +330 -0
  197. py_data_engine-0.1.0.dist-info/RECORD +200 -0
  198. py_data_engine-0.1.0.dist-info/WHEEL +5 -0
  199. py_data_engine-0.1.0.dist-info/entry_points.txt +2 -0
  200. py_data_engine-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,141 @@
1
+ """State helpers for the terminal UI surface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING
7
+
8
+ from data_engine.domain import (
9
+ FlowCatalogState,
10
+ OperationSessionState,
11
+ OperatorSessionState,
12
+ RuntimeSessionState,
13
+ WorkspaceControlState,
14
+ WorkspaceSessionState,
15
+ )
16
+ from data_engine.views.models import QtFlowCard, flow_catalog_entry_from_qt_card, qt_flow_cards_from_entries
17
+
18
+ if TYPE_CHECKING:
19
+ from data_engine.ui.tui.app import DataEngineTui
20
+
21
+
22
+ class TuiStateMixin:
23
+ """Session-state helpers separated from the main TUI shell."""
24
+
25
+ @property
26
+ def runtime_ledger(self: "DataEngineTui"):
27
+ """Compatibility facade over the current runtime binding ledger."""
28
+ return self.runtime_binding.runtime_ledger
29
+
30
+ @property
31
+ def log_store(self: "DataEngineTui"):
32
+ """Compatibility facade over the current runtime binding log store."""
33
+ return self.runtime_binding.log_store
34
+
35
+ @property
36
+ def _daemon_manager(self: "DataEngineTui"):
37
+ """Compatibility facade over the current runtime binding daemon manager."""
38
+ return self.runtime_binding.daemon_manager
39
+
40
+ @property
41
+ def runtime_session(self: "DataEngineTui") -> RuntimeSessionState:
42
+ """Return the current terminal runtime/control session state."""
43
+ return self._operator_session_state.runtime
44
+
45
+ @runtime_session.setter
46
+ def runtime_session(self: "DataEngineTui", value: RuntimeSessionState) -> None:
47
+ self._operator_session_state = self._operator_session_state.with_runtime(value)
48
+
49
+ @property
50
+ def flow_catalog_state(self: "DataEngineTui") -> FlowCatalogState:
51
+ """Return the current discovered flow catalog state."""
52
+ return self._operator_session_state.catalog
53
+
54
+ @flow_catalog_state.setter
55
+ def flow_catalog_state(self: "DataEngineTui", value: FlowCatalogState) -> None:
56
+ self._operator_session_state = self._operator_session_state.with_catalog(value)
57
+
58
+ @property
59
+ def flow_cards(self: "DataEngineTui") -> tuple[QtFlowCard, ...]:
60
+ return qt_flow_cards_from_entries(self.flow_catalog_state.entries)
61
+
62
+ @flow_cards.setter
63
+ def flow_cards(self: "DataEngineTui", value: tuple[QtFlowCard, ...]) -> None:
64
+ self.flow_catalog_state = self.flow_catalog_state.with_entries(
65
+ tuple(flow_catalog_entry_from_qt_card(card) for card in value)
66
+ )
67
+
68
+ @property
69
+ def flow_states(self: "DataEngineTui") -> dict[str, str]:
70
+ return self.flow_catalog_state.flow_states or {}
71
+
72
+ @flow_states.setter
73
+ def flow_states(self: "DataEngineTui", value: dict[str, str]) -> None:
74
+ self.flow_catalog_state = self.flow_catalog_state.with_flow_states(value)
75
+
76
+ @property
77
+ def selected_flow_name(self: "DataEngineTui") -> str | None:
78
+ return self.flow_catalog_state.selected_flow_name
79
+
80
+ @selected_flow_name.setter
81
+ def selected_flow_name(self: "DataEngineTui", value: str | None) -> None:
82
+ self.flow_catalog_state = self.flow_catalog_state.with_selected_flow_name(value)
83
+
84
+ @property
85
+ def workspace_control_state(self: "DataEngineTui") -> WorkspaceControlState:
86
+ """Return the current structured workspace control state."""
87
+ return self._operator_session_state.workspace_control
88
+
89
+ @workspace_control_state.setter
90
+ def workspace_control_state(self: "DataEngineTui", value: WorkspaceControlState) -> None:
91
+ self._operator_session_state = self._operator_session_state.with_workspace_control(value)
92
+
93
+ @property
94
+ def workspace_session_state(self: "DataEngineTui") -> WorkspaceSessionState:
95
+ """Return the current workspace selection/root session state."""
96
+ return self._operator_session_state.workspace
97
+
98
+ @workspace_session_state.setter
99
+ def workspace_session_state(self: "DataEngineTui", value: WorkspaceSessionState) -> None:
100
+ self._operator_session_state = self._operator_session_state.with_workspace(value)
101
+
102
+ @property
103
+ def operator_session_state(self: "DataEngineTui") -> OperatorSessionState:
104
+ """Return the top-level operator session state for this surface."""
105
+ return self._operator_session_state
106
+
107
+ @property
108
+ def operation_tracker(self: "DataEngineTui") -> OperationSessionState:
109
+ """Return the current operation/step session state."""
110
+ return self._operator_session_state.operations
111
+
112
+ @operation_tracker.setter
113
+ def operation_tracker(self: "DataEngineTui", value: OperationSessionState) -> None:
114
+ self._operator_session_state = self._operator_session_state.with_operations(value)
115
+
116
+ @property
117
+ def workspace_collection_root_override(self: "DataEngineTui") -> Path | None:
118
+ return self.workspace_session_state.workspace_collection_root_override
119
+
120
+ @workspace_collection_root_override.setter
121
+ def workspace_collection_root_override(self: "DataEngineTui", value: Path | None) -> None:
122
+ self.workspace_session_state = self.workspace_session_state.with_override_root(value)
123
+
124
+ @property
125
+ def discovered_workspace_ids(self: "DataEngineTui") -> tuple[str, ...]:
126
+ return self.workspace_session_state.discovered_workspace_ids
127
+
128
+ @discovered_workspace_ids.setter
129
+ def discovered_workspace_ids(self: "DataEngineTui", value: tuple[str, ...]) -> None:
130
+ self.workspace_session_state = self.workspace_session_state.with_discovered_workspace_ids(value)
131
+
132
+ def _selected_card(self: "DataEngineTui") -> QtFlowCard | None:
133
+ if self.selected_flow_name is None:
134
+ return None
135
+ for card in self.flow_cards:
136
+ if card.name == self.selected_flow_name:
137
+ return card
138
+ return None
139
+
140
+
141
+ __all__ = ["TuiStateMixin"]
@@ -0,0 +1,63 @@
1
+ """Window support helpers for the terminal UI surface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from time import monotonic
7
+ from typing import TYPE_CHECKING
8
+
9
+ from data_engine.platform.workspace_models import authored_workspace_is_available
10
+
11
+ if TYPE_CHECKING:
12
+ from data_engine.ui.tui.app import DataEngineTui
13
+
14
+
15
+ class TuiWindowSupportMixin:
16
+ """Session, workspace, and daemon plumbing separated from the main TUI shell."""
17
+
18
+ def _has_authored_workspace(self: "DataEngineTui") -> bool:
19
+ """Return whether the selected workspace currently has authored flow modules."""
20
+ return authored_workspace_is_available(self.workspace_paths)
21
+
22
+ def _daemon_request(self: "DataEngineTui", paths, payload, *, timeout: float = 0.0):
23
+ return self.daemon_service.request(paths, payload, timeout=timeout)
24
+
25
+ def _is_daemon_live(self: "DataEngineTui", paths) -> bool:
26
+ return self.daemon_service.is_live(paths)
27
+
28
+ def _resolve_workspace_paths(self: "DataEngineTui", *, workspace_id: str | None = None):
29
+ return self.workspace_service.resolve_paths(
30
+ workspace_id=workspace_id,
31
+ workspace_collection_root=self.workspace_collection_root_override,
32
+ )
33
+
34
+ def _monotonic(self: "DataEngineTui") -> float:
35
+ return monotonic()
36
+
37
+ def _register_client_session(self: "DataEngineTui") -> None:
38
+ """Register this TUI process as one active local client for the workspace."""
39
+ self.runtime_binding_service.register_client_session(
40
+ self.runtime_binding,
41
+ client_id=self.client_session_id,
42
+ client_kind="tui",
43
+ pid=os.getpid(),
44
+ )
45
+
46
+ def _unregister_client_session_and_check_for_shutdown(self: "DataEngineTui") -> bool:
47
+ """Remove this TUI session and return whether no local clients remain."""
48
+ try:
49
+ self.runtime_binding_service.remove_client_session(self.runtime_binding, self.client_session_id)
50
+ remaining = self.runtime_binding_service.count_live_client_sessions(self.runtime_binding)
51
+ return remaining == 0
52
+ except Exception:
53
+ return False
54
+
55
+ def _shutdown_daemon_on_close(self: "DataEngineTui") -> None:
56
+ """Best-effort local daemon shutdown when the last local client closes."""
57
+ try:
58
+ if not self._is_daemon_live(self.workspace_paths):
59
+ return
60
+ self._daemon_request(self.workspace_paths, {"command": "shutdown_daemon"}, timeout=1.5)
61
+ except Exception:
62
+ pass
63
+ __all__ = ["TuiWindowSupportMixin"]
@@ -0,0 +1,204 @@
1
+ """Textual theme adapter over the shared Data Engine theme tokens."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from data_engine.platform.theme import DEFAULT_THEME, THEMES, resolve_theme_name, system_theme_name
6
+
7
+
8
+ def stylesheet(theme_name: str = DEFAULT_THEME) -> str:
9
+ """Return the TUI stylesheet for the requested theme."""
10
+ palette = THEMES[resolve_theme_name(theme_name)]
11
+ return f"""
12
+ Screen {{
13
+ layout: vertical;
14
+ background: {palette.window_bg};
15
+ }}
16
+
17
+ #header {{
18
+ height: 7;
19
+ padding: 1 1 0 1;
20
+ align: left middle;
21
+ }}
22
+
23
+ #header-copy {{
24
+ width: 1fr;
25
+ height: 1fr;
26
+ layout: vertical;
27
+ }}
28
+
29
+ #header-actions {{
30
+ width: auto;
31
+ height: 1fr;
32
+ align: right middle;
33
+ }}
34
+
35
+ #header-controls {{
36
+ width: auto;
37
+ height: auto;
38
+ border: round {palette.panel_border};
39
+ padding: 0 1;
40
+ align: left middle;
41
+ background: {palette.panel_bg};
42
+ }}
43
+
44
+ #screen-title {{
45
+ width: 1fr;
46
+ height: auto;
47
+ text-style: bold;
48
+ color: {palette.text};
49
+ }}
50
+
51
+ #screen-subtitle {{
52
+ width: 1fr;
53
+ height: auto;
54
+ color: {palette.section_text};
55
+ }}
56
+
57
+ #status-line {{
58
+ width: 1fr;
59
+ height: auto;
60
+ color: {palette.text};
61
+ padding-top: 1;
62
+ }}
63
+
64
+ #control-status {{
65
+ width: 1fr;
66
+ height: auto;
67
+ color: {palette.muted_text};
68
+ }}
69
+
70
+ #workspace-select {{
71
+ width: 18;
72
+ margin-left: 1;
73
+ }}
74
+
75
+ .pane-title {{
76
+ height: auto;
77
+ color: {palette.section_text};
78
+ text-style: bold;
79
+ padding-bottom: 1;
80
+ }}
81
+
82
+ .pane-toolbar {{
83
+ height: 3;
84
+ align: right middle;
85
+ }}
86
+
87
+ #body {{
88
+ layout: grid;
89
+ grid-size: 3 1;
90
+ grid-columns: 40 1.7fr 1.3fr;
91
+ grid-rows: 1fr;
92
+ height: 1fr;
93
+ padding: 1;
94
+ grid-gutter: 1;
95
+ }}
96
+
97
+ #flow-list-pane {{
98
+ height: 1fr;
99
+ layout: vertical;
100
+ border: round $surface;
101
+ padding: 1;
102
+ background: {palette.panel_bg};
103
+ }}
104
+
105
+ #detail-pane {{
106
+ height: 1fr;
107
+ layout: vertical;
108
+ border: round $surface;
109
+ padding: 1;
110
+ background: {palette.panel_bg};
111
+ }}
112
+
113
+ #log-pane {{
114
+ height: 1fr;
115
+ layout: vertical;
116
+ border: round $surface;
117
+ padding: 1;
118
+ background: {palette.panel_bg};
119
+ }}
120
+
121
+ #flow-list,
122
+ #detail-view {{
123
+ height: 1fr;
124
+ }}
125
+
126
+ #detail-view {{
127
+ color: {palette.text};
128
+ }}
129
+
130
+ #flow-list {{
131
+ padding: 0 1;
132
+ }}
133
+
134
+ #log-run-list {{
135
+ height: 1fr;
136
+ border: round {palette.panel_border};
137
+ padding: 0 1;
138
+ background: {palette.panel_bg};
139
+ }}
140
+
141
+ #flow-list .label {{
142
+ height: auto;
143
+ color: {palette.text};
144
+ }}
145
+
146
+ #flow-list > GroupHeaderListItem {{
147
+ background: transparent;
148
+ color: {palette.muted_text};
149
+ text-style: bold;
150
+ margin-top: 1;
151
+ border-bottom: solid {palette.panel_border};
152
+ }}
153
+
154
+ #flow-list > GroupHeaderListItem.-disabled {{
155
+ opacity: 1;
156
+ }}
157
+
158
+ #flow-list > FlowListItem {{
159
+ padding: 0 1;
160
+ }}
161
+
162
+ #log-run-list .label {{
163
+ height: auto;
164
+ color: {palette.text};
165
+ }}
166
+
167
+ #log-run-list > .list-view--item-highlight {{
168
+ background: {palette.selection_bg};
169
+ color: {palette.selection_text};
170
+ }}
171
+
172
+ #flow-list > .list-view--item-highlight {{
173
+ background: {palette.selection_bg};
174
+ color: {palette.selection_text};
175
+ }}
176
+
177
+ Button {{
178
+ margin-right: 1;
179
+ height: 3;
180
+ background: {palette.button_bg};
181
+ color: {palette.text};
182
+ border: round {palette.panel_border};
183
+ }}
184
+
185
+ Button:hover {{
186
+ background: {palette.button_hover};
187
+ }}
188
+
189
+ Button:disabled {{
190
+ background: {palette.button_disabled_bg};
191
+ color: {palette.button_disabled_text};
192
+ border: round {palette.button_disabled_border};
193
+ }}
194
+
195
+ #clear-flow-log {{
196
+ margin-right: 0;
197
+ }}
198
+ """
199
+
200
+
201
+ TUI_CSS = stylesheet(DEFAULT_THEME)
202
+
203
+
204
+ __all__ = ["DEFAULT_THEME", "TUI_CSS", "resolve_theme_name", "stylesheet", "system_theme_name"]
@@ -0,0 +1,123 @@
1
+ """Textual widget classes for the terminal UI surface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.app import ComposeResult
6
+ from textual.binding import Binding
7
+ from textual.containers import Container, Vertical
8
+ from textual.screen import ModalScreen
9
+ from textual.widgets import Button, Label, ListItem, Static
10
+
11
+ from data_engine.domain import FlowRunState
12
+ from data_engine.views.flow_display import FlowRowDisplay, GroupRowDisplay
13
+ from data_engine.views.models import QtFlowCard
14
+ from data_engine.views.text import run_group_row_text
15
+
16
+
17
+ class FlowListItem(ListItem):
18
+ """One flow entry in the TUI sidebar."""
19
+
20
+ def __init__(self, card: QtFlowCard, state: str) -> None:
21
+ self.card = card
22
+ self.card_state = state
23
+ self.label = Label()
24
+ super().__init__(self.label)
25
+ self.refresh_view(state)
26
+
27
+ def refresh_view(self, state: str) -> None:
28
+ self.card_state = state
29
+ self.label.update(self._render_text(state))
30
+
31
+ def _render_text(self, state: str) -> str:
32
+ display = FlowRowDisplay.from_card(self.card, state, primary="title")
33
+ return f"{display.dot} {display.primary}\n {display.secondary}"
34
+
35
+
36
+ class GroupHeaderListItem(ListItem):
37
+ """Non-selectable group header for configured flows."""
38
+
39
+ def __init__(self, group_name: str, count: int) -> None:
40
+ self.group_name = group_name
41
+ flow_label = "flow" if count == 1 else "flows"
42
+ display = GroupRowDisplay.from_group(group_name, [], {})
43
+ self.label = Label(f"{display.uppercase_title} {count} {flow_label}")
44
+ super().__init__(self.label, disabled=True)
45
+
46
+
47
+ class RunGroupListItem(ListItem):
48
+ """One grouped flow run entry in the logs pane."""
49
+
50
+ def __init__(self, run_group: FlowRunState) -> None:
51
+ self.run_group = run_group
52
+ self.label = Label()
53
+ super().__init__(self.label)
54
+ self.refresh_view()
55
+
56
+ def refresh_view(self, run_group: FlowRunState | None = None) -> None:
57
+ if run_group is not None:
58
+ self.run_group = run_group
59
+ self.label.update(run_group_row_text(self.run_group))
60
+
61
+
62
+ class InfoModal(ModalScreen[None]):
63
+ """Simple centered information modal for TUI drill-ins."""
64
+
65
+ def __init__(self, *, title: str, body: str) -> None:
66
+ super().__init__()
67
+ self.title = title
68
+ self.body = body
69
+
70
+ CSS = """
71
+ Screen {
72
+ background: $background 70%;
73
+ }
74
+
75
+ #modal-shell {
76
+ width: 1fr;
77
+ height: 1fr;
78
+ align: center middle;
79
+ }
80
+
81
+ #modal-card {
82
+ width: 62;
83
+ max-width: 72%;
84
+ height: auto;
85
+ max-height: 70%;
86
+ border: round $surface;
87
+ background: $panel;
88
+ padding: 1 2;
89
+ }
90
+
91
+ #modal-title {
92
+ height: auto;
93
+ text-style: bold;
94
+ padding-bottom: 1;
95
+ }
96
+
97
+ #modal-body {
98
+ height: auto;
99
+ max-height: 20;
100
+ }
101
+ """
102
+
103
+ BINDINGS = [
104
+ Binding("escape", "dismiss", "Close"),
105
+ Binding("enter", "dismiss", "Close"),
106
+ ]
107
+
108
+ def compose(self) -> ComposeResult:
109
+ with Container(id="modal-shell"):
110
+ with Vertical(id="modal-card"):
111
+ yield Static(self.title, id="modal-title")
112
+ yield Static(self.body, id="modal-body")
113
+ yield Button("Close", id="close-modal")
114
+
115
+ def on_button_pressed(self, event: Button.Pressed) -> None:
116
+ if event.button.id == "close-modal":
117
+ self.dismiss(None)
118
+
119
+ def action_dismiss(self) -> None:
120
+ self.dismiss(None)
121
+
122
+
123
+ __all__ = ["FlowListItem", "GroupHeaderListItem", "InfoModal", "RunGroupListItem"]
@@ -0,0 +1,109 @@
1
+ """Shared presentation models and helpers across Data Engine surfaces."""
2
+
3
+ from data_engine.domain import (
4
+ FlowLogEntry,
5
+ FlowRunState,
6
+ LogKind,
7
+ RunKey,
8
+ RuntimeStepEvent,
9
+ format_log_line,
10
+ format_runtime_message,
11
+ parse_runtime_event,
12
+ parse_runtime_message,
13
+ short_source_label,
14
+ )
15
+ from data_engine.views.logs import FlowLogStore
16
+ from data_engine.views.actions import GuiActionState, TuiActionState
17
+ from data_engine.views.artifacts import ArtifactPreviewSpec, classify_artifact_preview, is_text_artifact
18
+ from data_engine.views.flow_display import FlowRowDisplay, GroupRowDisplay
19
+ from data_engine.views.models import (
20
+ QtFlowCard,
21
+ default_flow_state,
22
+ flow_category,
23
+ load_qt_flow_cards,
24
+ qt_flow_card_from_entry,
25
+ qt_flow_cards_from_entries,
26
+ )
27
+ from data_engine.views.presentation import (
28
+ flow_group_name,
29
+ flow_secondary_text,
30
+ format_seconds,
31
+ group_cards,
32
+ group_label,
33
+ group_secondary_text,
34
+ operation_marker,
35
+ state_dot,
36
+ status_color_name,
37
+ )
38
+ from data_engine.views.state import (
39
+ OperationDisplayState,
40
+ OperationRowState,
41
+ artifact_key_for_operation,
42
+ build_flow_summary,
43
+ capture_step_outputs,
44
+ is_inspectable_operation,
45
+ )
46
+ from data_engine.views.status import WORKSPACE_UNAVAILABLE_TEXT, surface_control_status_text
47
+ from data_engine.views.runs import RunGroupDisplay, format_raw_log_message
48
+ from data_engine.views.text import (
49
+ format_optional_seconds,
50
+ pad,
51
+ render_operation_lines,
52
+ render_run_group_lines,
53
+ render_selected_flow_lines,
54
+ run_group_row_text,
55
+ short_datetime,
56
+ )
57
+
58
+ __all__ = [
59
+ "FlowLogStore",
60
+ "FlowRowDisplay",
61
+ "FlowLogEntry",
62
+ "FlowRunState",
63
+ "GroupRowDisplay",
64
+ "GuiActionState",
65
+ "ArtifactPreviewSpec",
66
+ "LogKind",
67
+ "OperationDisplayState",
68
+ "OperationRowState",
69
+ "QtFlowCard",
70
+ "RunGroupDisplay",
71
+ "RunKey",
72
+ "RuntimeStepEvent",
73
+ "TuiActionState",
74
+ "artifact_key_for_operation",
75
+ "build_flow_summary",
76
+ "capture_step_outputs",
77
+ "classify_artifact_preview",
78
+ "default_flow_state",
79
+ "flow_category",
80
+ "flow_group_name",
81
+ "flow_secondary_text",
82
+ "format_log_line",
83
+ "format_raw_log_message",
84
+ "format_runtime_message",
85
+ "format_optional_seconds",
86
+ "format_seconds",
87
+ "group_cards",
88
+ "group_label",
89
+ "group_secondary_text",
90
+ "is_text_artifact",
91
+ "is_inspectable_operation",
92
+ "load_qt_flow_cards",
93
+ "operation_marker",
94
+ "pad",
95
+ "parse_runtime_event",
96
+ "parse_runtime_message",
97
+ "render_operation_lines",
98
+ "render_run_group_lines",
99
+ "render_selected_flow_lines",
100
+ "run_group_row_text",
101
+ "short_datetime",
102
+ "short_source_label",
103
+ "surface_control_status_text",
104
+ "state_dot",
105
+ "status_color_name",
106
+ "WORKSPACE_UNAVAILABLE_TEXT",
107
+ "qt_flow_card_from_entry",
108
+ "qt_flow_cards_from_entries",
109
+ ]