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,503 @@
1
+ """Small AST-based project mapper for the Data Engine codebase."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import ast
7
+ from collections import defaultdict
8
+ from dataclasses import asdict, dataclass
9
+ import json
10
+ from pathlib import Path
11
+
12
+
13
+ DEFAULT_PACKAGE_ROOT = Path(__file__).resolve().parents[1]
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class ImportSummary:
18
+ """One import statement discovered in a module."""
19
+
20
+ module: str | None
21
+ names: tuple[str, ...]
22
+ level: int = 0
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class FunctionSummary:
27
+ """Top-level function metadata for one module."""
28
+
29
+ name: str
30
+ lineno: int
31
+ params: tuple[str, ...]
32
+ decorators: tuple[str, ...]
33
+ returns: str | None = None
34
+ async_def: bool = False
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ClassSummary:
39
+ """Top-level class metadata for one module."""
40
+
41
+ name: str
42
+ lineno: int
43
+ bases: tuple[str, ...]
44
+ attributes: tuple[AssignmentSummary, ...]
45
+ instance_attributes: tuple[str, ...]
46
+ methods: tuple[FunctionSummary, ...]
47
+ decorators: tuple[str, ...]
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class AssignmentSummary:
52
+ """Top-level assignment metadata for one module."""
53
+
54
+ target: str
55
+ lineno: int
56
+ value_kind: str
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class ModuleSummary:
61
+ """AST-level summary for one Python module."""
62
+
63
+ module: str
64
+ path: str
65
+ docstring: str | None
66
+ imports: tuple[ImportSummary, ...]
67
+ functions: tuple[FunctionSummary, ...]
68
+ classes: tuple[ClassSummary, ...]
69
+ assignments: tuple[AssignmentSummary, ...]
70
+ flow_calls: tuple[str, ...]
71
+ line_count: int
72
+
73
+
74
+ def build_project_ast_map(package_root: Path | str | None = None) -> dict[str, object]:
75
+ """Return an AST-derived summary of the package beneath one root."""
76
+ root = Path(package_root or DEFAULT_PACKAGE_ROOT).resolve()
77
+ modules = tuple(_summarize_module(root, path) for path in sorted(root.rglob("*.py")) if "__pycache__" not in path.parts)
78
+ module_dicts = [asdict(module) for module in modules]
79
+ import_graph = _build_import_graph(modules)
80
+ package_rollups = _build_package_rollups(modules)
81
+ hotspots = _build_hotspots(modules)
82
+ return {
83
+ "package_root": _display_package_root(root),
84
+ "module_count": len(modules),
85
+ "modules": module_dicts,
86
+ "import_graph": import_graph,
87
+ "package_rollups": package_rollups,
88
+ "hotspots": hotspots,
89
+ }
90
+
91
+
92
+ def render_project_inventory_markdown(package_root: Path | str | None = None) -> str:
93
+ """Return a line-by-line Markdown inventory for one package root."""
94
+ payload = build_project_ast_map(package_root)
95
+ lines = [
96
+ "# Project Inventory",
97
+ "",
98
+ "This page is generated from the current AST map and is intentionally inventory-shaped rather than explanatory.",
99
+ "",
100
+ f"- package root: `{payload['package_root']}`",
101
+ f"- module count: `{payload['module_count']}`",
102
+ "",
103
+ ]
104
+ for module in payload["modules"]:
105
+ lines.append(f"- module `{module['module']}`")
106
+ for assignment in module["assignments"]:
107
+ lines.append(f" - attribute `{assignment['target']}`")
108
+ for function in module["functions"]:
109
+ prefix = "async function" if function["async_def"] else "function"
110
+ lines.append(f" - {prefix} `{function['name']}`")
111
+ for param in function["params"]:
112
+ lines.append(f" - param `{param}`")
113
+ for class_summary in module["classes"]:
114
+ lines.append(f" - class `{class_summary['name']}`")
115
+ for attribute in class_summary["attributes"]:
116
+ lines.append(f" - attribute `{attribute['target']}`")
117
+ for attribute_name in class_summary["instance_attributes"]:
118
+ lines.append(f" - instance attribute `{attribute_name}`")
119
+ for method in class_summary["methods"]:
120
+ prefix = "async method" if method["async_def"] else "method"
121
+ lines.append(f" - {prefix} `{method['name']}`")
122
+ for param in method["params"]:
123
+ lines.append(f" - param `{param}`")
124
+ if not module["assignments"] and not module["functions"] and not module["classes"]:
125
+ lines.append(" - no top-level symbols")
126
+ lines.append("")
127
+ return "\n".join(lines)
128
+
129
+
130
+ def _summarize_module(package_root: Path, path: Path) -> ModuleSummary:
131
+ source = path.read_text(encoding="utf-8")
132
+ tree = ast.parse(source, filename=str(path))
133
+ module_name = _module_name_for_path(package_root, path)
134
+ imports: list[ImportSummary] = []
135
+ functions: list[FunctionSummary] = []
136
+ classes: list[ClassSummary] = []
137
+ assignments: list[AssignmentSummary] = []
138
+ flow_calls: list[str] = []
139
+
140
+ for node in tree.body:
141
+ if isinstance(node, ast.Import):
142
+ imports.append(
143
+ ImportSummary(
144
+ module=None,
145
+ names=tuple(alias.name for alias in node.names),
146
+ )
147
+ )
148
+ continue
149
+ if isinstance(node, ast.ImportFrom):
150
+ imports.append(
151
+ ImportSummary(
152
+ module=node.module,
153
+ names=tuple(alias.name for alias in node.names),
154
+ level=node.level,
155
+ )
156
+ )
157
+ continue
158
+ if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
159
+ functions.append(_function_summary(node))
160
+ continue
161
+ if isinstance(node, ast.ClassDef):
162
+ classes.append(_class_summary(node))
163
+ continue
164
+ if isinstance(node, ast.Assign):
165
+ value_kind = type(node.value).__name__
166
+ targets = [target for target in node.targets if isinstance(target, ast.Name)]
167
+ for target in targets:
168
+ assignments.append(
169
+ AssignmentSummary(
170
+ target=target.id,
171
+ lineno=node.lineno,
172
+ value_kind=value_kind,
173
+ )
174
+ )
175
+ if _is_flow_call(node.value):
176
+ flow_calls.extend(target.id for target in targets)
177
+ continue
178
+ if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
179
+ assignments.append(
180
+ AssignmentSummary(
181
+ target=node.target.id,
182
+ lineno=node.lineno,
183
+ value_kind=type(node.value).__name__ if node.value is not None else "None",
184
+ )
185
+ )
186
+ if node.value is not None and _is_flow_call(node.value):
187
+ flow_calls.append(node.target.id)
188
+
189
+ return ModuleSummary(
190
+ module=module_name,
191
+ path=path.as_posix(),
192
+ docstring=ast.get_docstring(tree),
193
+ imports=tuple(imports),
194
+ functions=tuple(functions),
195
+ classes=tuple(classes),
196
+ assignments=tuple(assignments),
197
+ flow_calls=tuple(flow_calls),
198
+ line_count=len(source.splitlines()),
199
+ )
200
+
201
+
202
+ def _module_name_for_path(package_root: Path, path: Path) -> str:
203
+ relative = path.relative_to(package_root)
204
+ parts = relative.with_suffix("").parts
205
+ if parts[-1] == "__init__":
206
+ parts = parts[:-1]
207
+ return ".".join((package_root.name, *parts)) if parts else package_root.name
208
+
209
+
210
+ def _display_package_root(package_root: Path) -> str:
211
+ pyproject = next((parent / "pyproject.toml" for parent in package_root.parents if (parent / "pyproject.toml").exists()), None)
212
+ if pyproject is not None:
213
+ try:
214
+ return package_root.relative_to(pyproject.parent).as_posix()
215
+ except ValueError:
216
+ pass
217
+ return package_root.name
218
+
219
+
220
+ def _is_flow_call(node: ast.AST) -> bool:
221
+ if not isinstance(node, ast.Call):
222
+ return False
223
+ func = node.func
224
+ if isinstance(func, ast.Name):
225
+ return func.id == "Flow"
226
+ return isinstance(func, ast.Attribute) and func.attr == "Flow"
227
+
228
+
229
+ def _function_summary(node: ast.FunctionDef | ast.AsyncFunctionDef) -> FunctionSummary:
230
+ return FunctionSummary(
231
+ name=node.name,
232
+ lineno=node.lineno,
233
+ params=_parameter_names(node.args),
234
+ decorators=tuple(_expr_text(item) for item in node.decorator_list),
235
+ returns=_expr_text(node.returns) if node.returns is not None else None,
236
+ async_def=isinstance(node, ast.AsyncFunctionDef),
237
+ )
238
+
239
+
240
+ def _class_summary(node: ast.ClassDef) -> ClassSummary:
241
+ attributes: list[AssignmentSummary] = []
242
+ methods: list[FunctionSummary] = []
243
+ instance_attributes: list[str] = []
244
+
245
+ for child in node.body:
246
+ if isinstance(child, ast.FunctionDef | ast.AsyncFunctionDef):
247
+ methods.append(_function_summary(child))
248
+ instance_attributes.extend(_instance_attribute_names(child))
249
+ continue
250
+ if isinstance(child, ast.Assign):
251
+ value_kind = type(child.value).__name__
252
+ for target in child.targets:
253
+ if isinstance(target, ast.Name):
254
+ attributes.append(
255
+ AssignmentSummary(
256
+ target=target.id,
257
+ lineno=child.lineno,
258
+ value_kind=value_kind,
259
+ )
260
+ )
261
+ continue
262
+ if isinstance(child, ast.AnnAssign) and isinstance(child.target, ast.Name):
263
+ attributes.append(
264
+ AssignmentSummary(
265
+ target=child.target.id,
266
+ lineno=child.lineno,
267
+ value_kind=type(child.value).__name__ if child.value is not None else "None",
268
+ )
269
+ )
270
+
271
+ return ClassSummary(
272
+ name=node.name,
273
+ lineno=node.lineno,
274
+ bases=tuple(_expr_text(item) for item in node.bases),
275
+ attributes=tuple(attributes),
276
+ instance_attributes=tuple(dict.fromkeys(instance_attributes)),
277
+ methods=tuple(methods),
278
+ decorators=tuple(_expr_text(item) for item in node.decorator_list),
279
+ )
280
+
281
+
282
+ def _parameter_names(args: ast.arguments) -> tuple[str, ...]:
283
+ params: list[str] = []
284
+ posonly_count = len(args.posonlyargs)
285
+ combined = [*args.posonlyargs, *args.args]
286
+ defaults = [None] * (len(combined) - len(args.defaults)) + list(args.defaults)
287
+ for index, (arg, default) in enumerate(zip(combined, defaults, strict=False)):
288
+ rendered = arg.arg
289
+ if arg.annotation is not None:
290
+ rendered = f"{rendered}: {_expr_text(arg.annotation)}"
291
+ if default is not None:
292
+ rendered = f"{rendered}={_expr_text(default)}"
293
+ params.append(rendered)
294
+ if posonly_count and index + 1 == posonly_count:
295
+ params.append("/")
296
+ if args.vararg is not None:
297
+ rendered = f"*{args.vararg.arg}"
298
+ if args.vararg.annotation is not None:
299
+ rendered = f"{rendered}: {_expr_text(args.vararg.annotation)}"
300
+ params.append(rendered)
301
+ elif args.kwonlyargs:
302
+ params.append("*")
303
+ for arg, default in zip(args.kwonlyargs, args.kw_defaults, strict=False):
304
+ rendered = arg.arg
305
+ if arg.annotation is not None:
306
+ rendered = f"{rendered}: {_expr_text(arg.annotation)}"
307
+ if default is not None:
308
+ rendered = f"{rendered}={_expr_text(default)}"
309
+ params.append(rendered)
310
+ if args.kwarg is not None:
311
+ rendered = f"**{args.kwarg.arg}"
312
+ if args.kwarg.annotation is not None:
313
+ rendered = f"{rendered}: {_expr_text(args.kwarg.annotation)}"
314
+ params.append(rendered)
315
+ return tuple(params)
316
+
317
+
318
+ def _instance_attribute_names(node: ast.FunctionDef | ast.AsyncFunctionDef) -> tuple[str, ...]:
319
+ names: list[str] = []
320
+ for child in ast.walk(node):
321
+ if isinstance(child, ast.Assign):
322
+ for target in child.targets:
323
+ if isinstance(target, ast.Attribute) and isinstance(target.value, ast.Name) and target.value.id == "self":
324
+ names.append(target.attr)
325
+ continue
326
+ if isinstance(child, ast.AnnAssign):
327
+ target = child.target
328
+ if isinstance(target, ast.Attribute) and isinstance(target.value, ast.Name) and target.value.id == "self":
329
+ names.append(target.attr)
330
+ return tuple(names)
331
+
332
+
333
+ def _expr_text(node: ast.AST) -> str:
334
+ try:
335
+ return ast.unparse(node)
336
+ except Exception:
337
+ return type(node).__name__
338
+
339
+
340
+ def _build_import_graph(modules: tuple[ModuleSummary, ...]) -> dict[str, object]:
341
+ module_names = {module.module for module in modules}
342
+ package_name = modules[0].module.split(".")[0] if modules else ""
343
+ edges: list[dict[str, str]] = []
344
+ internal_targets_by_source: dict[str, set[str]] = defaultdict(set)
345
+ external_targets_by_source: dict[str, set[str]] = defaultdict(set)
346
+
347
+ for module in modules:
348
+ for item in module.imports:
349
+ for target in _resolved_import_targets(module.module, item, module_names):
350
+ if target in module_names:
351
+ internal_targets_by_source[module.module].add(target)
352
+ edges.append({"from": module.module, "to": target, "kind": "internal"})
353
+ else:
354
+ external_targets_by_source[module.module].add(target)
355
+
356
+ return {
357
+ "internal_edges": edges,
358
+ "internal_edge_count": len(edges),
359
+ "external_imports": [
360
+ {"module": module_name, "targets": sorted(targets)}
361
+ for module_name, targets in sorted(external_targets_by_source.items())
362
+ ],
363
+ "fan_out": [
364
+ {
365
+ "module": module.module,
366
+ "internal_targets": len(internal_targets_by_source.get(module.module, set())),
367
+ "external_targets": len(external_targets_by_source.get(module.module, set())),
368
+ }
369
+ for module in modules
370
+ ],
371
+ }
372
+
373
+
374
+ def _build_package_rollups(modules: tuple[ModuleSummary, ...]) -> list[dict[str, object]]:
375
+ buckets: dict[str, dict[str, object]] = {}
376
+ for module in modules:
377
+ package = _package_bucket(module.module)
378
+ bucket = buckets.setdefault(
379
+ package,
380
+ {
381
+ "package": package,
382
+ "module_count": 0,
383
+ "function_count": 0,
384
+ "class_count": 0,
385
+ "flow_count": 0,
386
+ "line_count": 0,
387
+ },
388
+ )
389
+ bucket["module_count"] += 1
390
+ bucket["function_count"] += len(module.functions)
391
+ bucket["class_count"] += len(module.classes)
392
+ bucket["flow_count"] += len(module.flow_calls)
393
+ bucket["line_count"] += module.line_count
394
+ package_name = modules[0].module.split(".")[0] if modules else ""
395
+ return sorted(buckets.values(), key=lambda item: (item["package"] != package_name, item["package"]))
396
+
397
+
398
+ def _build_hotspots(modules: tuple[ModuleSummary, ...]) -> dict[str, list[dict[str, object]]]:
399
+ package_name = modules[0].module.split(".")[0] if modules else ""
400
+ by_lines = sorted(
401
+ (
402
+ {
403
+ "module": module.module,
404
+ "line_count": module.line_count,
405
+ "function_count": len(module.functions),
406
+ "class_count": len(module.classes),
407
+ }
408
+ for module in modules
409
+ ),
410
+ key=lambda item: (-item["line_count"], item["module"]),
411
+ )
412
+ by_internal_fan_out = sorted(
413
+ (
414
+ {
415
+ "module": module.module,
416
+ "internal_imports": sum(
417
+ 1
418
+ for item in module.imports
419
+ for target in _resolved_import_targets(module.module, item, set())
420
+ if target.startswith(package_name)
421
+ ),
422
+ "line_count": module.line_count,
423
+ }
424
+ for module in modules
425
+ ),
426
+ key=lambda item: (-item["internal_imports"], -item["line_count"], item["module"]),
427
+ )
428
+ return {
429
+ "largest_modules": by_lines[:10],
430
+ "most_internal_imports": by_internal_fan_out[:10],
431
+ }
432
+
433
+
434
+ def _resolved_import_targets(module_name: str, item: ImportSummary, module_names: set[str]) -> tuple[str, ...]:
435
+ package_name = module_name.split(".")[0]
436
+ current_parts = module_name.split(".")
437
+ current_package_parts = current_parts[:-1]
438
+
439
+ if item.module is None and item.level == 0:
440
+ return item.names
441
+
442
+ if item.level > 0:
443
+ anchor_parts = current_package_parts[: len(current_package_parts) - item.level + 1]
444
+ base_parts = anchor_parts + (item.module.split(".") if item.module else [])
445
+ base_module = ".".join(base_parts)
446
+ return _candidate_targets(base_module, item.names, module_names)
447
+
448
+ if item.module is None:
449
+ return ()
450
+
451
+ return _candidate_targets(item.module, item.names, module_names if item.module.startswith(f"{package_name}.") or item.module == package_name else set())
452
+
453
+
454
+ def _candidate_targets(base_module: str, names: tuple[str, ...], module_names: set[str]) -> tuple[str, ...]:
455
+ if not names:
456
+ return (base_module,) if base_module else ()
457
+ targets: list[str] = []
458
+ if base_module and (not module_names or base_module in module_names):
459
+ targets.append(base_module)
460
+ for name in names:
461
+ if name == "*":
462
+ continue
463
+ candidate = ".".join(part for part in (base_module, name) if part)
464
+ if not module_names or candidate in module_names:
465
+ targets.append(candidate)
466
+ if targets:
467
+ return tuple(dict.fromkeys(targets))
468
+ return (base_module,) if base_module else tuple(name for name in names if name != "*")
469
+
470
+
471
+ def _package_bucket(module_name: str) -> str:
472
+ parts = module_name.split(".")
473
+ if len(parts) <= 2:
474
+ return module_name
475
+ return ".".join(parts[:2])
476
+
477
+
478
+ def main(argv: list[str] | None = None) -> int:
479
+ """Print a JSON AST project map for one package root."""
480
+ parser = argparse.ArgumentParser(description="Build a small AST-derived map of the Data Engine project.")
481
+ parser.add_argument(
482
+ "package_root",
483
+ nargs="?",
484
+ default=str(DEFAULT_PACKAGE_ROOT),
485
+ help="Package root to inspect. Defaults to src/data_engine.",
486
+ )
487
+ parser.add_argument(
488
+ "--format",
489
+ choices=("json", "markdown"),
490
+ default="json",
491
+ help="Output format. Defaults to json.",
492
+ )
493
+ args = parser.parse_args(argv)
494
+ if args.format == "markdown":
495
+ print(render_project_inventory_markdown(args.package_root))
496
+ return 0
497
+ payload = build_project_ast_map(args.package_root)
498
+ print(json.dumps(payload, indent=2))
499
+ return 0
500
+
501
+
502
+ if __name__ == "__main__":
503
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ """Documentation assets and authored Sphinx sources."""
@@ -0,0 +1,13 @@
1
+ body,
2
+ .wy-body-for-nav,
3
+ .wy-grid-for-nav,
4
+ .wy-nav-content-wrap,
5
+ .rst-content,
6
+ .document {
7
+ background: #ffffff;
8
+ }
9
+
10
+ .wy-nav-content {
11
+ background: #ffffff;
12
+ max-width: none;
13
+ }
@@ -0,0 +1,42 @@
1
+ API Reference
2
+ =============
3
+
4
+ The package entrypoints most users will import are:
5
+
6
+ - ``data_engine.Flow``
7
+ - ``data_engine.FlowContext``
8
+ - ``data_engine.discover_flows``
9
+ - ``data_engine.load_flow``
10
+ - ``data_engine.run``
11
+
12
+ Flow builder
13
+ ------------
14
+
15
+ .. automodule:: data_engine.authoring.builder
16
+ :members:
17
+ :undoc-members:
18
+ :show-inheritance:
19
+
20
+ Runtime models
21
+ --------------
22
+
23
+ .. automodule:: data_engine.authoring.model
24
+ :members:
25
+ :undoc-members:
26
+ :show-inheritance:
27
+
28
+ File Watching
29
+ -------------
30
+
31
+ .. automodule:: data_engine.runtime.file_watch
32
+ :members:
33
+ :undoc-members:
34
+ :show-inheritance:
35
+
36
+ Application Services
37
+ --------------------
38
+
39
+ .. automodule:: data_engine.services
40
+ :members:
41
+ :undoc-members:
42
+ :show-inheritance:
@@ -0,0 +1,37 @@
1
+ """Sphinx configuration for the Data Engine project docs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+
9
+ ROOT = Path(__file__).resolve().parents[3]
10
+ sys.path.insert(0, str(ROOT / "src"))
11
+
12
+ project = "Data Engine"
13
+ author = "Data Engine contributors"
14
+ release = "0.1.0"
15
+
16
+ extensions = [
17
+ "sphinx.ext.autodoc",
18
+ "myst_parser",
19
+ "sphinx.ext.napoleon",
20
+ "sphinx.ext.viewcode",
21
+ ]
22
+
23
+ source_suffix = {
24
+ ".rst": "restructuredtext",
25
+ ".md": "markdown",
26
+ }
27
+
28
+ templates_path = ["_templates"]
29
+ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
30
+
31
+ html_theme = "sphinx_rtd_theme"
32
+ html_static_path = ["_static"]
33
+ html_css_files = ["custom.css"]
34
+ html_title = "Data Engine documentation"
35
+ autodoc_member_order = "bysource"
36
+ autodoc_typehints = "description"
37
+ add_module_names = False