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.
- data_engine/__init__.py +37 -0
- data_engine/application/__init__.py +39 -0
- data_engine/application/actions.py +42 -0
- data_engine/application/catalog.py +151 -0
- data_engine/application/control.py +213 -0
- data_engine/application/details.py +73 -0
- data_engine/application/runtime.py +449 -0
- data_engine/application/workspace.py +62 -0
- data_engine/authoring/__init__.py +14 -0
- data_engine/authoring/builder.py +31 -0
- data_engine/authoring/execution/__init__.py +6 -0
- data_engine/authoring/execution/app.py +6 -0
- data_engine/authoring/execution/context.py +82 -0
- data_engine/authoring/execution/continuous.py +176 -0
- data_engine/authoring/execution/grouped.py +106 -0
- data_engine/authoring/execution/logging.py +83 -0
- data_engine/authoring/execution/polling.py +135 -0
- data_engine/authoring/execution/runner.py +210 -0
- data_engine/authoring/execution/single.py +171 -0
- data_engine/authoring/flow.py +361 -0
- data_engine/authoring/helpers.py +160 -0
- data_engine/authoring/model.py +59 -0
- data_engine/authoring/primitives.py +430 -0
- data_engine/authoring/services.py +42 -0
- data_engine/devtools/__init__.py +3 -0
- data_engine/devtools/project_ast_map.py +503 -0
- data_engine/docs/__init__.py +1 -0
- data_engine/docs/sphinx_source/_static/custom.css +13 -0
- data_engine/docs/sphinx_source/api.rst +42 -0
- data_engine/docs/sphinx_source/conf.py +37 -0
- data_engine/docs/sphinx_source/guides/app-runtime-and-workspaces.md +397 -0
- data_engine/docs/sphinx_source/guides/authoring-flow-modules.md +215 -0
- data_engine/docs/sphinx_source/guides/configuring-flows.md +185 -0
- data_engine/docs/sphinx_source/guides/core-concepts.md +208 -0
- data_engine/docs/sphinx_source/guides/database-methods.md +107 -0
- data_engine/docs/sphinx_source/guides/duckdb-helpers.md +462 -0
- data_engine/docs/sphinx_source/guides/flow-context.md +538 -0
- data_engine/docs/sphinx_source/guides/flow-methods.md +206 -0
- data_engine/docs/sphinx_source/guides/getting-started.md +271 -0
- data_engine/docs/sphinx_source/guides/project-inventory.md +5683 -0
- data_engine/docs/sphinx_source/guides/project-map.md +118 -0
- data_engine/docs/sphinx_source/guides/recipes.md +268 -0
- data_engine/docs/sphinx_source/index.rst +22 -0
- data_engine/domain/__init__.py +92 -0
- data_engine/domain/actions.py +69 -0
- data_engine/domain/catalog.py +128 -0
- data_engine/domain/details.py +214 -0
- data_engine/domain/diagnostics.py +56 -0
- data_engine/domain/errors.py +104 -0
- data_engine/domain/inspection.py +99 -0
- data_engine/domain/logs.py +118 -0
- data_engine/domain/operations.py +172 -0
- data_engine/domain/operator.py +72 -0
- data_engine/domain/runs.py +155 -0
- data_engine/domain/runtime.py +279 -0
- data_engine/domain/source_state.py +17 -0
- data_engine/domain/support.py +54 -0
- data_engine/domain/time.py +23 -0
- data_engine/domain/workspace.py +159 -0
- data_engine/flow_modules/__init__.py +1 -0
- data_engine/flow_modules/flow_module_compiler.py +179 -0
- data_engine/flow_modules/flow_module_loader.py +201 -0
- data_engine/helpers/__init__.py +25 -0
- data_engine/helpers/duckdb.py +705 -0
- data_engine/hosts/__init__.py +1 -0
- data_engine/hosts/daemon/__init__.py +23 -0
- data_engine/hosts/daemon/app.py +221 -0
- data_engine/hosts/daemon/bootstrap.py +69 -0
- data_engine/hosts/daemon/client.py +465 -0
- data_engine/hosts/daemon/commands.py +64 -0
- data_engine/hosts/daemon/composition.py +310 -0
- data_engine/hosts/daemon/constants.py +15 -0
- data_engine/hosts/daemon/entrypoints.py +97 -0
- data_engine/hosts/daemon/lifecycle.py +191 -0
- data_engine/hosts/daemon/manager.py +272 -0
- data_engine/hosts/daemon/ownership.py +126 -0
- data_engine/hosts/daemon/runtime_commands.py +188 -0
- data_engine/hosts/daemon/runtime_control.py +31 -0
- data_engine/hosts/daemon/server.py +84 -0
- data_engine/hosts/daemon/shared_state.py +147 -0
- data_engine/hosts/daemon/state_sync.py +101 -0
- data_engine/platform/__init__.py +1 -0
- data_engine/platform/identity.py +35 -0
- data_engine/platform/local_settings.py +146 -0
- data_engine/platform/theme.py +259 -0
- data_engine/platform/workspace_models.py +190 -0
- data_engine/platform/workspace_policy.py +333 -0
- data_engine/runtime/__init__.py +1 -0
- data_engine/runtime/file_watch.py +185 -0
- data_engine/runtime/ledger_models.py +116 -0
- data_engine/runtime/runtime_db.py +938 -0
- data_engine/runtime/shared_state.py +523 -0
- data_engine/services/__init__.py +49 -0
- data_engine/services/daemon.py +64 -0
- data_engine/services/daemon_state.py +40 -0
- data_engine/services/flow_catalog.py +102 -0
- data_engine/services/flow_execution.py +48 -0
- data_engine/services/ledger.py +85 -0
- data_engine/services/logs.py +65 -0
- data_engine/services/runtime_binding.py +105 -0
- data_engine/services/runtime_execution.py +126 -0
- data_engine/services/runtime_history.py +62 -0
- data_engine/services/settings.py +58 -0
- data_engine/services/shared_state.py +28 -0
- data_engine/services/theme.py +59 -0
- data_engine/services/workspace_provisioning.py +224 -0
- data_engine/services/workspaces.py +74 -0
- data_engine/ui/__init__.py +3 -0
- data_engine/ui/cli/__init__.py +19 -0
- data_engine/ui/cli/app.py +161 -0
- data_engine/ui/cli/commands_doctor.py +178 -0
- data_engine/ui/cli/commands_run.py +80 -0
- data_engine/ui/cli/commands_start.py +100 -0
- data_engine/ui/cli/commands_workspace.py +97 -0
- data_engine/ui/cli/dependencies.py +44 -0
- data_engine/ui/cli/parser.py +56 -0
- data_engine/ui/gui/__init__.py +25 -0
- data_engine/ui/gui/app.py +116 -0
- data_engine/ui/gui/bootstrap.py +487 -0
- data_engine/ui/gui/bootstrapper.py +140 -0
- data_engine/ui/gui/cache_models.py +23 -0
- data_engine/ui/gui/control_support.py +185 -0
- data_engine/ui/gui/controllers/__init__.py +6 -0
- data_engine/ui/gui/controllers/flows.py +439 -0
- data_engine/ui/gui/controllers/runtime.py +245 -0
- data_engine/ui/gui/dialogs/__init__.py +12 -0
- data_engine/ui/gui/dialogs/messages.py +88 -0
- data_engine/ui/gui/dialogs/previews.py +222 -0
- data_engine/ui/gui/helpers/__init__.py +62 -0
- data_engine/ui/gui/helpers/inspection.py +81 -0
- data_engine/ui/gui/helpers/lifecycle.py +112 -0
- data_engine/ui/gui/helpers/scroll.py +28 -0
- data_engine/ui/gui/helpers/theming.py +87 -0
- data_engine/ui/gui/icons/dark_light.svg +12 -0
- data_engine/ui/gui/icons/documentation.svg +1 -0
- data_engine/ui/gui/icons/failed.svg +3 -0
- data_engine/ui/gui/icons/group.svg +4 -0
- data_engine/ui/gui/icons/home.svg +2 -0
- data_engine/ui/gui/icons/manual.svg +2 -0
- data_engine/ui/gui/icons/poll.svg +2 -0
- data_engine/ui/gui/icons/schedule.svg +4 -0
- data_engine/ui/gui/icons/settings.svg +2 -0
- data_engine/ui/gui/icons/started.svg +3 -0
- data_engine/ui/gui/icons/success.svg +3 -0
- data_engine/ui/gui/icons/view-log.svg +3 -0
- data_engine/ui/gui/icons.py +50 -0
- data_engine/ui/gui/launcher.py +48 -0
- data_engine/ui/gui/presenters/__init__.py +72 -0
- data_engine/ui/gui/presenters/docs.py +140 -0
- data_engine/ui/gui/presenters/logs.py +58 -0
- data_engine/ui/gui/presenters/runtime_projection.py +29 -0
- data_engine/ui/gui/presenters/sidebar.py +88 -0
- data_engine/ui/gui/presenters/steps.py +148 -0
- data_engine/ui/gui/presenters/workspace.py +39 -0
- data_engine/ui/gui/presenters/workspace_binding.py +75 -0
- data_engine/ui/gui/presenters/workspace_settings.py +182 -0
- data_engine/ui/gui/preview_models.py +37 -0
- data_engine/ui/gui/render_support.py +241 -0
- data_engine/ui/gui/rendering/__init__.py +12 -0
- data_engine/ui/gui/rendering/artifacts.py +95 -0
- data_engine/ui/gui/rendering/icons.py +50 -0
- data_engine/ui/gui/runtime.py +47 -0
- data_engine/ui/gui/state_support.py +193 -0
- data_engine/ui/gui/support.py +214 -0
- data_engine/ui/gui/surface.py +209 -0
- data_engine/ui/gui/theme.py +720 -0
- data_engine/ui/gui/widgets/__init__.py +34 -0
- data_engine/ui/gui/widgets/config.py +41 -0
- data_engine/ui/gui/widgets/logs.py +62 -0
- data_engine/ui/gui/widgets/panels.py +507 -0
- data_engine/ui/gui/widgets/sidebar.py +130 -0
- data_engine/ui/gui/widgets/steps.py +84 -0
- data_engine/ui/tui/__init__.py +5 -0
- data_engine/ui/tui/app.py +222 -0
- data_engine/ui/tui/bootstrap.py +475 -0
- data_engine/ui/tui/bootstrapper.py +117 -0
- data_engine/ui/tui/controllers/__init__.py +6 -0
- data_engine/ui/tui/controllers/flows.py +349 -0
- data_engine/ui/tui/controllers/runtime.py +167 -0
- data_engine/ui/tui/runtime.py +34 -0
- data_engine/ui/tui/state_support.py +141 -0
- data_engine/ui/tui/support.py +63 -0
- data_engine/ui/tui/theme.py +204 -0
- data_engine/ui/tui/widgets.py +123 -0
- data_engine/views/__init__.py +109 -0
- data_engine/views/actions.py +80 -0
- data_engine/views/artifacts.py +58 -0
- data_engine/views/flow_display.py +69 -0
- data_engine/views/logs.py +54 -0
- data_engine/views/models.py +96 -0
- data_engine/views/presentation.py +133 -0
- data_engine/views/runs.py +62 -0
- data_engine/views/state.py +39 -0
- data_engine/views/status.py +13 -0
- data_engine/views/text.py +109 -0
- py_data_engine-0.1.0.dist-info/METADATA +330 -0
- py_data_engine-0.1.0.dist-info/RECORD +200 -0
- py_data_engine-0.1.0.dist-info/WHEEL +5 -0
- py_data_engine-0.1.0.dist-info/entry_points.txt +2 -0
- 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
|
+
]
|