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,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,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
|