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,59 @@
1
+ """Shared theme resolution services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Mapping
6
+
7
+ from data_engine.platform.theme import (
8
+ DEFAULT_THEME,
9
+ THEMES,
10
+ ThemePalette,
11
+ resolve_theme_name,
12
+ system_theme_name,
13
+ theme_button_text,
14
+ toggle_theme_name,
15
+ )
16
+
17
+
18
+ class ThemeService:
19
+ """Thin injectable wrapper around shared theme state decisions."""
20
+
21
+ def __init__(
22
+ self,
23
+ *,
24
+ themes: Mapping[str, ThemePalette] = THEMES,
25
+ default_theme_name: str = DEFAULT_THEME,
26
+ resolve_theme_name_func: Callable[[str], str] = resolve_theme_name,
27
+ system_theme_name_func: Callable[[], str] = system_theme_name,
28
+ toggle_theme_name_func: Callable[[str], str] = toggle_theme_name,
29
+ theme_button_text_func: Callable[[str], str] = theme_button_text,
30
+ ) -> None:
31
+ self._themes = themes
32
+ self.default_theme_name = default_theme_name
33
+ self._resolve_theme_name = resolve_theme_name_func
34
+ self._system_theme_name = system_theme_name_func
35
+ self._toggle_theme_name = toggle_theme_name_func
36
+ self._theme_button_text = theme_button_text_func
37
+
38
+ def resolve_name(self, theme_name: str = DEFAULT_THEME) -> str:
39
+ """Resolve one explicit or system-bound theme name."""
40
+ return self._resolve_theme_name(theme_name)
41
+
42
+ def system_name(self) -> str:
43
+ """Return the host-system theme name."""
44
+ return self._system_theme_name()
45
+
46
+ def toggle_name(self, theme_name: str) -> str:
47
+ """Return the opposite theme name."""
48
+ return self._toggle_theme_name(theme_name)
49
+
50
+ def button_text(self, theme_name: str) -> str:
51
+ """Return the user-facing theme toggle text."""
52
+ return self._theme_button_text(theme_name)
53
+
54
+ def palette(self, theme_name: str = DEFAULT_THEME) -> ThemePalette:
55
+ """Return the resolved semantic palette."""
56
+ return self._themes[self.resolve_name(theme_name)]
57
+
58
+
59
+ __all__ = ["ThemeService"]
@@ -0,0 +1,224 @@
1
+ """Workspace provisioning helpers shared by CLI and GUI surfaces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ from data_engine.platform.workspace_models import (
10
+ DATA_ENGINE_WORKSPACE_COLLECTION_ROOT_ENV_VAR,
11
+ WORKSPACE_FLOW_HELPERS_DIR_NAME,
12
+ WorkspacePaths,
13
+ validate_workspace_id,
14
+ )
15
+
16
+
17
+ def checkout_source_dir(app_root: Path) -> Path | None:
18
+ """Return the repo-local source directory when app_root points at a checkout."""
19
+ src_dir = app_root / "src"
20
+ return src_dir if src_dir.is_dir() else None
21
+
22
+
23
+ def checkout_tests_dir(app_root: Path) -> Path | None:
24
+ """Return the repo-local tests directory when app_root points at a checkout."""
25
+ tests_dir = app_root / "tests"
26
+ return tests_dir if tests_dir.is_dir() else None
27
+
28
+
29
+ def workspace_vscode_settings(
30
+ workspace_root: Path,
31
+ *,
32
+ app_root: Path,
33
+ interpreter_path: Path | None = None,
34
+ ) -> dict[str, object]:
35
+ """Return VS Code settings for one workspace root."""
36
+ del interpreter_path
37
+ workspace_id = validate_workspace_id(workspace_root.name)
38
+ terminal_env = {
39
+ "DATA_ENGINE_APP_ROOT": str(app_root),
40
+ "DATA_ENGINE_WORKSPACE_ROOT": str(workspace_root),
41
+ "DATA_ENGINE_WORKSPACE_ID": workspace_id,
42
+ }
43
+ settings: dict[str, object] = {
44
+ "python.defaultInterpreterPath": "${workspaceFolder}/.venv",
45
+ "files.exclude": {".workspace_state": True},
46
+ "search.exclude": {".workspace_state": True},
47
+ "terminal.integrated.env.linux": terminal_env,
48
+ "terminal.integrated.env.osx": terminal_env,
49
+ "terminal.integrated.env.windows": terminal_env,
50
+ }
51
+ src_dir = checkout_source_dir(app_root)
52
+ if src_dir is not None:
53
+ settings["python.analysis.extraPaths"] = [str(src_dir)]
54
+ tests_dir = checkout_tests_dir(app_root)
55
+ if tests_dir is not None:
56
+ settings["python.testing.pytestEnabled"] = True
57
+ settings["python.testing.pytestArgs"] = [str(tests_dir)]
58
+ return settings
59
+
60
+
61
+ def collection_vscode_settings(
62
+ collection_root: Path,
63
+ *,
64
+ app_root: Path,
65
+ interpreter_path: Path | None = None,
66
+ ) -> dict[str, object]:
67
+ """Return VS Code settings for one workspace collection root."""
68
+ del interpreter_path
69
+ terminal_env = {
70
+ "DATA_ENGINE_APP_ROOT": str(app_root),
71
+ DATA_ENGINE_WORKSPACE_COLLECTION_ROOT_ENV_VAR: str(collection_root),
72
+ }
73
+ settings: dict[str, object] = {
74
+ "python.defaultInterpreterPath": "${workspaceFolder}/.venv",
75
+ "files.exclude": {"**/.workspace_state": True},
76
+ "search.exclude": {"**/.workspace_state": True},
77
+ "terminal.integrated.env.linux": terminal_env,
78
+ "terminal.integrated.env.osx": terminal_env,
79
+ "terminal.integrated.env.windows": terminal_env,
80
+ }
81
+ src_dir = checkout_source_dir(app_root)
82
+ if src_dir is not None:
83
+ settings["python.analysis.extraPaths"] = [str(src_dir)]
84
+ tests_dir = checkout_tests_dir(app_root)
85
+ if tests_dir is not None:
86
+ settings["python.testing.pytestEnabled"] = True
87
+ settings["python.testing.pytestArgs"] = [str(tests_dir)]
88
+ return settings
89
+
90
+
91
+ def write_workspace_vscode_settings(
92
+ workspace_root: Path,
93
+ *,
94
+ app_root: Path,
95
+ interpreter_path: Path | None = None,
96
+ overwrite: bool = False,
97
+ ) -> Path | None:
98
+ """Write workspace-local VS Code settings unless an existing file should be preserved."""
99
+ settings_path = workspace_root / ".vscode" / "settings.json"
100
+ if settings_path.exists() and not overwrite:
101
+ return None
102
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
103
+ settings_path.write_text(
104
+ json.dumps(
105
+ workspace_vscode_settings(
106
+ workspace_root,
107
+ app_root=app_root,
108
+ interpreter_path=interpreter_path,
109
+ ),
110
+ indent=2,
111
+ )
112
+ + "\n",
113
+ encoding="utf-8",
114
+ )
115
+ return settings_path
116
+
117
+
118
+ def write_collection_vscode_settings(
119
+ collection_root: Path,
120
+ *,
121
+ app_root: Path,
122
+ interpreter_path: Path | None = None,
123
+ overwrite: bool = False,
124
+ ) -> Path | None:
125
+ """Write collection-root VS Code settings unless an existing file should be preserved."""
126
+ settings_path = collection_root / ".vscode" / "settings.json"
127
+ if settings_path.exists() and not overwrite:
128
+ return None
129
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
130
+ settings_path.write_text(
131
+ json.dumps(
132
+ collection_vscode_settings(
133
+ collection_root,
134
+ app_root=app_root,
135
+ interpreter_path=interpreter_path,
136
+ ),
137
+ indent=2,
138
+ )
139
+ + "\n",
140
+ encoding="utf-8",
141
+ )
142
+ return settings_path
143
+
144
+
145
+ @dataclass(frozen=True)
146
+ class WorkspaceProvisioningResult:
147
+ """Describe which workspace assets were created during provisioning."""
148
+
149
+ workspace_root: Path
150
+ created_paths: tuple[Path, ...]
151
+ preserved_paths: tuple[Path, ...]
152
+
153
+ @property
154
+ def created_anything(self) -> bool:
155
+ """Return whether provisioning created any new files or directories."""
156
+ return bool(self.created_paths)
157
+
158
+
159
+ class WorkspaceProvisioningService:
160
+ """Own safe workspace-folder provisioning for operator surfaces."""
161
+
162
+ def provision_workspace(
163
+ self,
164
+ workspace_paths: WorkspacePaths,
165
+ *,
166
+ interpreter_path: Path | None = None,
167
+ ) -> WorkspaceProvisioningResult:
168
+ """Provision missing authored-workspace folders without overwriting existing content."""
169
+ created_paths: list[Path] = []
170
+ preserved_paths: list[Path] = []
171
+ workspace_root = workspace_paths.workspace_root
172
+ if workspace_root.exists() and not workspace_root.is_dir():
173
+ raise ValueError(f"Workspace path is not a directory: {workspace_root}")
174
+ if not workspace_root.exists():
175
+ workspace_root.mkdir(parents=True, exist_ok=True)
176
+ created_paths.append(workspace_root)
177
+ collection_settings_path = write_collection_vscode_settings(
178
+ workspace_paths.workspace_collection_root,
179
+ app_root=workspace_paths.app_root,
180
+ interpreter_path=interpreter_path,
181
+ overwrite=False,
182
+ )
183
+ if collection_settings_path is None:
184
+ preserved_paths.append(workspace_paths.workspace_collection_root / ".vscode" / "settings.json")
185
+ else:
186
+ created_paths.append(collection_settings_path)
187
+ for directory in (
188
+ workspace_paths.flow_modules_dir,
189
+ workspace_paths.flow_modules_dir / WORKSPACE_FLOW_HELPERS_DIR_NAME,
190
+ workspace_paths.config_dir,
191
+ workspace_paths.databases_dir,
192
+ ):
193
+ if directory.exists():
194
+ preserved_paths.append(directory)
195
+ continue
196
+ directory.mkdir(parents=True, exist_ok=True)
197
+ created_paths.append(directory)
198
+ settings_path = write_workspace_vscode_settings(
199
+ workspace_root,
200
+ app_root=workspace_paths.app_root,
201
+ interpreter_path=interpreter_path,
202
+ overwrite=False,
203
+ )
204
+ if settings_path is None:
205
+ preserved_paths.append(workspace_root / ".vscode" / "settings.json")
206
+ else:
207
+ created_paths.append(settings_path)
208
+ return WorkspaceProvisioningResult(
209
+ workspace_root=workspace_root,
210
+ created_paths=tuple(created_paths),
211
+ preserved_paths=tuple(preserved_paths),
212
+ )
213
+
214
+
215
+ __all__ = [
216
+ "WorkspaceProvisioningResult",
217
+ "WorkspaceProvisioningService",
218
+ "collection_vscode_settings",
219
+ "checkout_source_dir",
220
+ "checkout_tests_dir",
221
+ "write_collection_vscode_settings",
222
+ "workspace_vscode_settings",
223
+ "write_workspace_vscode_settings",
224
+ ]
@@ -0,0 +1,74 @@
1
+ """Workspace path and discovery services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from pathlib import Path
7
+
8
+ from data_engine.platform.workspace_models import DiscoveredWorkspace, WorkspacePaths
9
+ from data_engine.platform.workspace_policy import AppStatePolicy, RuntimeLayoutPolicy, WorkspaceDiscoveryPolicy
10
+
11
+
12
+ class WorkspaceService:
13
+ """Own workspace discovery and path resolution through explicit collaborators."""
14
+
15
+ def __init__(
16
+ self,
17
+ *,
18
+ app_state_policy: AppStatePolicy | None = None,
19
+ discovery_policy: WorkspaceDiscoveryPolicy | None = None,
20
+ runtime_layout_policy: RuntimeLayoutPolicy | None = None,
21
+ discover_workspaces_func: Callable[..., tuple[DiscoveredWorkspace, ...]] | None = None,
22
+ resolve_workspace_paths_func: Callable[..., WorkspacePaths] | None = None,
23
+ ) -> None:
24
+ self._app_state_policy = app_state_policy or AppStatePolicy()
25
+ self._discovery_policy = discovery_policy or WorkspaceDiscoveryPolicy(app_state_policy=self._app_state_policy)
26
+ self._runtime_layout_policy = runtime_layout_policy or RuntimeLayoutPolicy(
27
+ app_state_policy=self._app_state_policy,
28
+ discovery_policy=self._discovery_policy,
29
+ )
30
+ self._discover_workspaces = discover_workspaces_func
31
+ self._resolve_workspace_paths = resolve_workspace_paths_func
32
+
33
+ def discover(
34
+ self,
35
+ *,
36
+ app_root: Path | None = None,
37
+ workspace_collection_root: Path | None = None,
38
+ ) -> tuple[DiscoveredWorkspace, ...]:
39
+ """Return discoverable workspaces for the current app and collection roots."""
40
+ if self._discover_workspaces is None:
41
+ return self._discovery_policy.discover(
42
+ app_root=app_root,
43
+ workspace_collection_root=workspace_collection_root,
44
+ )
45
+ return self._discover_workspaces(
46
+ app_root=app_root,
47
+ workspace_collection_root=workspace_collection_root,
48
+ )
49
+
50
+ def resolve_paths(
51
+ self,
52
+ *,
53
+ workspace_id: str | None = None,
54
+ workspace_root: Path | None = None,
55
+ data_root: Path | None = None,
56
+ workspace_collection_root: Path | None = None,
57
+ ) -> WorkspacePaths:
58
+ """Resolve one workspace path set with the current override-aware rules."""
59
+ if self._resolve_workspace_paths is None:
60
+ return self._runtime_layout_policy.resolve_paths(
61
+ workspace_id=workspace_id,
62
+ workspace_root=workspace_root,
63
+ data_root=data_root,
64
+ workspace_collection_root=workspace_collection_root,
65
+ )
66
+ return self._resolve_workspace_paths(
67
+ workspace_id=workspace_id,
68
+ workspace_root=workspace_root,
69
+ data_root=data_root,
70
+ workspace_collection_root=workspace_collection_root,
71
+ )
72
+
73
+
74
+ __all__ = ["WorkspaceService"]
@@ -0,0 +1,3 @@
1
+ """Operator surface packages for Data Engine."""
2
+
3
+ __all__ = ["gui", "tui"]
@@ -0,0 +1,19 @@
1
+ """CLI surface package."""
2
+
3
+ from data_engine.ui.cli.app import (
4
+ CliDependencies,
5
+ CliDependencyFactories,
6
+ build_default_cli_dependencies,
7
+ build_parser,
8
+ default_cli_dependency_factories,
9
+ main,
10
+ )
11
+
12
+ __all__ = [
13
+ "CliDependencies",
14
+ "CliDependencyFactories",
15
+ "build_default_cli_dependencies",
16
+ "build_parser",
17
+ "default_cli_dependency_factories",
18
+ "main",
19
+ ]
@@ -0,0 +1,161 @@
1
+ """Operator-focused CLI surface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ from pathlib import Path
8
+ import subprocess
9
+ import sys
10
+
11
+ from data_engine.authoring.model import FlowValidationError
12
+ from data_engine.platform.workspace_models import (
13
+ DATA_ENGINE_APP_ROOT_ENV_VAR,
14
+ DATA_ENGINE_WORKSPACE_ROOT_ENV_VAR,
15
+ InvalidWorkspaceIdError,
16
+ machine_id_text,
17
+ )
18
+ from data_engine.ui.cli import commands_doctor as _commands_doctor
19
+ from data_engine.ui.cli import commands_run as _commands_run
20
+ from data_engine.ui.cli.commands_start import (
21
+ launch_desktop_ui as _launch_desktop_ui,
22
+ launch_terminal_ui as _launch_terminal_ui,
23
+ preferred_gui_python_executable as _preferred_gui_python_executable,
24
+ start_gui_subprocess as _start_gui_subprocess,
25
+ start_surface as _start_surface,
26
+ )
27
+ from data_engine.ui.cli.commands_workspace import (
28
+ create_command as _create_command,
29
+ workspace_vscode_settings as _workspace_vscode_settings,
30
+ )
31
+ from data_engine.ui.cli.dependencies import (
32
+ CliDependencies,
33
+ CliDependencyFactories,
34
+ build_default_cli_dependencies,
35
+ default_cli_dependency_factories,
36
+ )
37
+ from data_engine.ui.cli.parser import _HelpFormatter, build_parser
38
+
39
+
40
+ def _default_cli_dependencies() -> CliDependencies:
41
+ return build_default_cli_dependencies()
42
+
43
+
44
+ def main(argv: list[str] | None = None, *, dependencies: CliDependencies | None = None) -> int:
45
+ parser = build_parser()
46
+ args = parser.parse_args(argv)
47
+ dependencies = dependencies or _default_cli_dependencies()
48
+ _apply_environment(args)
49
+
50
+ try:
51
+ if args.command == "start":
52
+ return _start_surface(args.start_command)
53
+ if args.command == "create":
54
+ return _create_command(args, dependencies=dependencies)
55
+ if args.command == "run":
56
+ return _run_command(args, dependencies=dependencies)
57
+ if args.command == "doctor":
58
+ return _doctor_command(args, dependencies=dependencies)
59
+ except (FlowValidationError, InvalidWorkspaceIdError) as exc:
60
+ print(str(exc), file=sys.stderr)
61
+ return 2
62
+ except KeyboardInterrupt:
63
+ print("Stopped.", file=sys.stderr)
64
+ return 130
65
+ parser.error(f"Unknown command: {args.command}")
66
+ return 2
67
+
68
+
69
+ def _apply_environment(args: argparse.Namespace) -> None:
70
+ if args.app_root is not None:
71
+ os.environ[DATA_ENGINE_APP_ROOT_ENV_VAR] = str(args.app_root.expanduser().resolve())
72
+ else:
73
+ inferred = _infer_project_root_from_cwd(Path.cwd())
74
+ if inferred is not None:
75
+ os.environ.setdefault(DATA_ENGINE_APP_ROOT_ENV_VAR, str(inferred))
76
+ if args.workspace is not None:
77
+ os.environ[DATA_ENGINE_WORKSPACE_ROOT_ENV_VAR] = str(args.workspace.expanduser().resolve())
78
+
79
+
80
+ def _infer_project_root_from_cwd(cwd: Path) -> Path | None:
81
+ candidate = cwd.expanduser().resolve()
82
+ if (candidate / "pyproject.toml").is_file() and (candidate / "src" / "data_engine").is_dir():
83
+ return candidate
84
+ return None
85
+
86
+
87
+ def _run_command(args: argparse.Namespace, *, dependencies: CliDependencies) -> int:
88
+ if args.run_command == "tests":
89
+ return _run_tests(slice_name=args.slice, list_slices=args.list_slices, dependencies=dependencies)
90
+ raise FlowValidationError(f"Unknown run command: {args.run_command}")
91
+
92
+
93
+ def _test_slice_args(slice_name: str, *, app_root: Path) -> tuple[str, ...]:
94
+ return _commands_run.test_slice_args(slice_name, app_root=app_root)
95
+
96
+
97
+ def _run_tests(*, slice_name: str, list_slices: bool, dependencies: CliDependencies) -> int:
98
+ app_root = dependencies.app_state_policy.load_settings().app_root
99
+ return _commands_run.run_tests(slice_name=slice_name, list_slices=list_slices, app_root=app_root)
100
+
101
+
102
+ def _doctor(*, dependencies: CliDependencies) -> int:
103
+ settings = dependencies.app_state_policy.load_settings()
104
+ paths = dependencies.workspace_service.resolve_paths()
105
+ return _commands_doctor.doctor(settings=settings, paths=paths)
106
+
107
+
108
+ def _doctor_command(args: argparse.Namespace, *, dependencies: CliDependencies) -> int:
109
+ if getattr(args, "doctor_command", None) == "daemons":
110
+ return _doctor_daemons(dependencies=dependencies)
111
+ return _doctor(dependencies=dependencies)
112
+
113
+
114
+ def _run_process_listing():
115
+ return _commands_doctor.run_process_listing()
116
+
117
+
118
+ def _classify_process_kind(command: str) -> str | None:
119
+ return _commands_doctor.classify_process_kind(command)
120
+
121
+ def _doctor_daemons(*, dependencies: CliDependencies) -> int:
122
+ settings = dependencies.app_state_policy.load_settings()
123
+ return _commands_doctor.doctor_daemons(
124
+ settings=settings,
125
+ workspace_service=dependencies.workspace_service,
126
+ process_listing_func=_run_process_listing,
127
+ classify_process_kind_func=_classify_process_kind,
128
+ read_lease_metadata_func=dependencies.shared_state_service.read_lease_metadata,
129
+ lease_is_stale_func=lambda paths, stale_after_seconds: dependencies.shared_state_service.lease_is_stale(
130
+ paths,
131
+ stale_after_seconds=stale_after_seconds,
132
+ ),
133
+ machine_id_text_func=machine_id_text,
134
+ )
135
+
136
+
137
+ __all__ = [
138
+ "CliDependencies",
139
+ "CliDependencyFactories",
140
+ "_HelpFormatter",
141
+ "_apply_environment",
142
+ "_classify_process_kind",
143
+ "_doctor",
144
+ "_doctor_command",
145
+ "_doctor_daemons",
146
+ "_infer_project_root_from_cwd",
147
+ "_launch_desktop_ui",
148
+ "_launch_terminal_ui",
149
+ "_preferred_gui_python_executable",
150
+ "_run_command",
151
+ "_run_process_listing",
152
+ "_run_tests",
153
+ "_start_gui_subprocess",
154
+ "_start_surface",
155
+ "_test_slice_args",
156
+ "_workspace_vscode_settings",
157
+ "build_default_cli_dependencies",
158
+ "build_parser",
159
+ "default_cli_dependency_factories",
160
+ "main",
161
+ ]