pytrilogy 0.3.138__cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.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.
- LICENSE.md +19 -0
- _preql_import_resolver/__init__.py +5 -0
- _preql_import_resolver/_preql_import_resolver.cpython-311-x86_64-linux-gnu.so +0 -0
- pytrilogy-0.3.138.dist-info/METADATA +525 -0
- pytrilogy-0.3.138.dist-info/RECORD +182 -0
- pytrilogy-0.3.138.dist-info/WHEEL +5 -0
- pytrilogy-0.3.138.dist-info/entry_points.txt +2 -0
- pytrilogy-0.3.138.dist-info/licenses/LICENSE.md +19 -0
- trilogy/__init__.py +9 -0
- trilogy/ai/README.md +10 -0
- trilogy/ai/__init__.py +19 -0
- trilogy/ai/constants.py +92 -0
- trilogy/ai/conversation.py +107 -0
- trilogy/ai/enums.py +7 -0
- trilogy/ai/execute.py +50 -0
- trilogy/ai/models.py +34 -0
- trilogy/ai/prompts.py +87 -0
- trilogy/ai/providers/__init__.py +0 -0
- trilogy/ai/providers/anthropic.py +106 -0
- trilogy/ai/providers/base.py +24 -0
- trilogy/ai/providers/google.py +146 -0
- trilogy/ai/providers/openai.py +89 -0
- trilogy/ai/providers/utils.py +68 -0
- trilogy/authoring/README.md +3 -0
- trilogy/authoring/__init__.py +143 -0
- trilogy/constants.py +113 -0
- trilogy/core/README.md +52 -0
- trilogy/core/__init__.py +0 -0
- trilogy/core/constants.py +6 -0
- trilogy/core/enums.py +443 -0
- trilogy/core/env_processor.py +120 -0
- trilogy/core/environment_helpers.py +320 -0
- trilogy/core/ergonomics.py +193 -0
- trilogy/core/exceptions.py +123 -0
- trilogy/core/functions.py +1227 -0
- trilogy/core/graph_models.py +139 -0
- trilogy/core/internal.py +85 -0
- trilogy/core/models/__init__.py +0 -0
- trilogy/core/models/author.py +2672 -0
- trilogy/core/models/build.py +2521 -0
- trilogy/core/models/build_environment.py +180 -0
- trilogy/core/models/core.py +494 -0
- trilogy/core/models/datasource.py +322 -0
- trilogy/core/models/environment.py +748 -0
- trilogy/core/models/execute.py +1177 -0
- trilogy/core/optimization.py +251 -0
- trilogy/core/optimizations/__init__.py +12 -0
- trilogy/core/optimizations/base_optimization.py +17 -0
- trilogy/core/optimizations/hide_unused_concept.py +47 -0
- trilogy/core/optimizations/inline_datasource.py +102 -0
- trilogy/core/optimizations/predicate_pushdown.py +245 -0
- trilogy/core/processing/README.md +94 -0
- trilogy/core/processing/READMEv2.md +121 -0
- trilogy/core/processing/VIRTUAL_UNNEST.md +30 -0
- trilogy/core/processing/__init__.py +0 -0
- trilogy/core/processing/concept_strategies_v3.py +508 -0
- trilogy/core/processing/constants.py +15 -0
- trilogy/core/processing/discovery_node_factory.py +451 -0
- trilogy/core/processing/discovery_utility.py +517 -0
- trilogy/core/processing/discovery_validation.py +167 -0
- trilogy/core/processing/graph_utils.py +43 -0
- trilogy/core/processing/node_generators/README.md +9 -0
- trilogy/core/processing/node_generators/__init__.py +31 -0
- trilogy/core/processing/node_generators/basic_node.py +160 -0
- trilogy/core/processing/node_generators/common.py +268 -0
- trilogy/core/processing/node_generators/constant_node.py +38 -0
- trilogy/core/processing/node_generators/filter_node.py +315 -0
- trilogy/core/processing/node_generators/group_node.py +213 -0
- trilogy/core/processing/node_generators/group_to_node.py +117 -0
- trilogy/core/processing/node_generators/multiselect_node.py +205 -0
- trilogy/core/processing/node_generators/node_merge_node.py +653 -0
- trilogy/core/processing/node_generators/recursive_node.py +88 -0
- trilogy/core/processing/node_generators/rowset_node.py +165 -0
- trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +261 -0
- trilogy/core/processing/node_generators/select_merge_node.py +748 -0
- trilogy/core/processing/node_generators/select_node.py +95 -0
- trilogy/core/processing/node_generators/synonym_node.py +98 -0
- trilogy/core/processing/node_generators/union_node.py +91 -0
- trilogy/core/processing/node_generators/unnest_node.py +182 -0
- trilogy/core/processing/node_generators/window_node.py +201 -0
- trilogy/core/processing/nodes/README.md +28 -0
- trilogy/core/processing/nodes/__init__.py +179 -0
- trilogy/core/processing/nodes/base_node.py +519 -0
- trilogy/core/processing/nodes/filter_node.py +75 -0
- trilogy/core/processing/nodes/group_node.py +194 -0
- trilogy/core/processing/nodes/merge_node.py +420 -0
- trilogy/core/processing/nodes/recursive_node.py +46 -0
- trilogy/core/processing/nodes/select_node_v2.py +242 -0
- trilogy/core/processing/nodes/union_node.py +53 -0
- trilogy/core/processing/nodes/unnest_node.py +62 -0
- trilogy/core/processing/nodes/window_node.py +56 -0
- trilogy/core/processing/utility.py +823 -0
- trilogy/core/query_processor.py +596 -0
- trilogy/core/statements/README.md +35 -0
- trilogy/core/statements/__init__.py +0 -0
- trilogy/core/statements/author.py +536 -0
- trilogy/core/statements/build.py +0 -0
- trilogy/core/statements/common.py +20 -0
- trilogy/core/statements/execute.py +155 -0
- trilogy/core/table_processor.py +66 -0
- trilogy/core/utility.py +8 -0
- trilogy/core/validation/README.md +46 -0
- trilogy/core/validation/__init__.py +0 -0
- trilogy/core/validation/common.py +161 -0
- trilogy/core/validation/concept.py +146 -0
- trilogy/core/validation/datasource.py +227 -0
- trilogy/core/validation/environment.py +73 -0
- trilogy/core/validation/fix.py +106 -0
- trilogy/dialect/__init__.py +32 -0
- trilogy/dialect/base.py +1359 -0
- trilogy/dialect/bigquery.py +256 -0
- trilogy/dialect/common.py +147 -0
- trilogy/dialect/config.py +144 -0
- trilogy/dialect/dataframe.py +50 -0
- trilogy/dialect/duckdb.py +177 -0
- trilogy/dialect/enums.py +147 -0
- trilogy/dialect/metadata.py +173 -0
- trilogy/dialect/mock.py +190 -0
- trilogy/dialect/postgres.py +91 -0
- trilogy/dialect/presto.py +104 -0
- trilogy/dialect/results.py +89 -0
- trilogy/dialect/snowflake.py +90 -0
- trilogy/dialect/sql_server.py +92 -0
- trilogy/engine.py +48 -0
- trilogy/execution/config.py +75 -0
- trilogy/executor.py +568 -0
- trilogy/hooks/__init__.py +4 -0
- trilogy/hooks/base_hook.py +40 -0
- trilogy/hooks/graph_hook.py +139 -0
- trilogy/hooks/query_debugger.py +166 -0
- trilogy/metadata/__init__.py +0 -0
- trilogy/parser.py +10 -0
- trilogy/parsing/README.md +21 -0
- trilogy/parsing/__init__.py +0 -0
- trilogy/parsing/common.py +1069 -0
- trilogy/parsing/config.py +5 -0
- trilogy/parsing/exceptions.py +8 -0
- trilogy/parsing/helpers.py +1 -0
- trilogy/parsing/parse_engine.py +2813 -0
- trilogy/parsing/render.py +750 -0
- trilogy/parsing/trilogy.lark +540 -0
- trilogy/py.typed +0 -0
- trilogy/render.py +42 -0
- trilogy/scripts/README.md +7 -0
- trilogy/scripts/__init__.py +0 -0
- trilogy/scripts/dependency/Cargo.lock +617 -0
- trilogy/scripts/dependency/Cargo.toml +39 -0
- trilogy/scripts/dependency/README.md +131 -0
- trilogy/scripts/dependency/build.sh +25 -0
- trilogy/scripts/dependency/src/directory_resolver.rs +162 -0
- trilogy/scripts/dependency/src/lib.rs +16 -0
- trilogy/scripts/dependency/src/main.rs +770 -0
- trilogy/scripts/dependency/src/parser.rs +435 -0
- trilogy/scripts/dependency/src/preql.pest +208 -0
- trilogy/scripts/dependency/src/python_bindings.rs +289 -0
- trilogy/scripts/dependency/src/resolver.rs +716 -0
- trilogy/scripts/dependency/tests/base.preql +3 -0
- trilogy/scripts/dependency/tests/cli_integration.rs +377 -0
- trilogy/scripts/dependency/tests/customer.preql +6 -0
- trilogy/scripts/dependency/tests/main.preql +9 -0
- trilogy/scripts/dependency/tests/orders.preql +7 -0
- trilogy/scripts/dependency/tests/test_data/base.preql +9 -0
- trilogy/scripts/dependency/tests/test_data/consumer.preql +1 -0
- trilogy/scripts/dependency.py +323 -0
- trilogy/scripts/display.py +460 -0
- trilogy/scripts/environment.py +46 -0
- trilogy/scripts/parallel_execution.py +483 -0
- trilogy/scripts/single_execution.py +131 -0
- trilogy/scripts/trilogy.py +772 -0
- trilogy/std/__init__.py +0 -0
- trilogy/std/color.preql +3 -0
- trilogy/std/date.preql +13 -0
- trilogy/std/display.preql +18 -0
- trilogy/std/geography.preql +22 -0
- trilogy/std/metric.preql +15 -0
- trilogy/std/money.preql +67 -0
- trilogy/std/net.preql +14 -0
- trilogy/std/ranking.preql +7 -0
- trilogy/std/report.preql +5 -0
- trilogy/std/semantic.preql +6 -0
- trilogy/utility.py +34 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
import networkx as nx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def normalize_path_variants(path: str) -> Path:
|
|
9
|
+
"""
|
|
10
|
+
On Windows, paths from Rust may include UNC prefixes like \\?\C:\path.
|
|
11
|
+
This function returns the path without the prefix.
|
|
12
|
+
"""
|
|
13
|
+
# Handle Windows UNC prefix (\\?\)
|
|
14
|
+
if str(path).startswith("\\\\?\\"):
|
|
15
|
+
# Strip the UNC prefix
|
|
16
|
+
normal_path = str(path)[4:]
|
|
17
|
+
return Path(normal_path)
|
|
18
|
+
return Path(path)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class ScriptNode:
|
|
23
|
+
"""Represents a script file with its path and associated data."""
|
|
24
|
+
|
|
25
|
+
path: Path
|
|
26
|
+
|
|
27
|
+
def __hash__(self):
|
|
28
|
+
return hash(self.path)
|
|
29
|
+
|
|
30
|
+
def __eq__(self, other):
|
|
31
|
+
if not isinstance(other, ScriptNode):
|
|
32
|
+
return False
|
|
33
|
+
return self.path == other.path
|
|
34
|
+
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return f"ScriptNode({self.path.name})"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DependencyStrategy(Protocol):
|
|
40
|
+
"""Protocol for dependency resolution strategies."""
|
|
41
|
+
|
|
42
|
+
def build_graph(self, nodes: list[ScriptNode]) -> nx.DiGraph:
|
|
43
|
+
"""
|
|
44
|
+
Given a list of script nodes, return a directed dependency graph.
|
|
45
|
+
|
|
46
|
+
The graph should have edges pointing from dependencies TO dependents.
|
|
47
|
+
i.e., if A depends on B, there should be an edge B -> A.
|
|
48
|
+
This means: B must run before A.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
A networkx DiGraph where nodes are ScriptNode instances and
|
|
52
|
+
edges point from dependencies to their dependents.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def build_folder_graph(self, folder: Path) -> nx.DiGraph: ...
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ETLDependencyStrategy:
|
|
59
|
+
"""
|
|
60
|
+
Dependency strategy using the Rust-based ETL logic parser.
|
|
61
|
+
|
|
62
|
+
Uses the preql-import-resolver Rust library to parse imports, datasources,
|
|
63
|
+
and persist statements, building a dependency graph that respects:
|
|
64
|
+
1. Import dependencies (imported files run before importing files)
|
|
65
|
+
2. Persist-before-declare dependencies (files that persist to a datasource run before files that declare it)
|
|
66
|
+
3. Declare-before-use dependencies (files that declare datasources run before files that import them)
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def build_folder_graph(self, folder: Path) -> nx.DiGraph:
|
|
70
|
+
"""
|
|
71
|
+
Build dependency graph for all script files in a folder.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
folder: The folder containing script files.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
A networkx DiGraph representing dependencies.
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
from _preql_import_resolver import PyImportResolver
|
|
81
|
+
except ImportError:
|
|
82
|
+
raise ImportError(
|
|
83
|
+
"The dependency resolution script could not be found. If this error occured in production, please open an issue on https://github.com/trilogy-data/pytrilogy. "
|
|
84
|
+
"If developing, please build it by running: maturin develop"
|
|
85
|
+
)
|
|
86
|
+
resolver = PyImportResolver()
|
|
87
|
+
|
|
88
|
+
result = resolver.resolve_directory(str(folder), False)
|
|
89
|
+
nodes = result.get("files", [])
|
|
90
|
+
graph = nx.DiGraph()
|
|
91
|
+
path_to_node = {}
|
|
92
|
+
edges = result.get("edges", [])
|
|
93
|
+
# Build the graph
|
|
94
|
+
for node in nodes:
|
|
95
|
+
normal_path = normalize_path_variants(node)
|
|
96
|
+
node = ScriptNode(path=normal_path)
|
|
97
|
+
path_to_node[normal_path] = node
|
|
98
|
+
graph.add_node(node)
|
|
99
|
+
|
|
100
|
+
# Build edges from the result
|
|
101
|
+
for edge in edges:
|
|
102
|
+
from_path = normalize_path_variants(edge["from"])
|
|
103
|
+
to_path = normalize_path_variants(edge["to"])
|
|
104
|
+
|
|
105
|
+
# Only add edges for files we're managing
|
|
106
|
+
if from_path in path_to_node and to_path in path_to_node:
|
|
107
|
+
graph.add_edge(path_to_node[from_path], path_to_node[to_path])
|
|
108
|
+
|
|
109
|
+
return graph
|
|
110
|
+
|
|
111
|
+
def build_graph(self, nodes: list[ScriptNode]) -> nx.DiGraph:
|
|
112
|
+
"""
|
|
113
|
+
Build dependency graph based on ETL semantics using Rust resolver.
|
|
114
|
+
|
|
115
|
+
This strategy requires all nodes to be in the same directory.
|
|
116
|
+
It uses the Rust directory resolver to analyze all files together.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
DiGraph with edges pointing from dependencies to dependents
|
|
120
|
+
(i.e., edge A -> B means A must run before B).
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
from _preql_import_resolver import PyImportResolver
|
|
124
|
+
except ImportError:
|
|
125
|
+
raise ImportError(
|
|
126
|
+
"The preql-import-resolver resolver could not be found. If this is production, please open an issue."
|
|
127
|
+
"If developing, please build it with maturin: cd trilogy/scripts/dependency && maturin develop"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
graph = nx.DiGraph()
|
|
131
|
+
|
|
132
|
+
# Add all nodes
|
|
133
|
+
for node in nodes:
|
|
134
|
+
graph.add_node(node)
|
|
135
|
+
|
|
136
|
+
# If we only have one node, return early
|
|
137
|
+
if len(nodes) <= 1:
|
|
138
|
+
return graph
|
|
139
|
+
|
|
140
|
+
# Check that all nodes are in the same directory
|
|
141
|
+
directories = {node.path.parent.resolve() for node in nodes}
|
|
142
|
+
if len(directories) > 1:
|
|
143
|
+
raise ValueError(
|
|
144
|
+
"ETLDependencyStrategy requires all script files to be in the same directory. "
|
|
145
|
+
f"Found files in {len(directories)} different directories. {directories}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Build a mapping from absolute path to node
|
|
149
|
+
# We need to handle both regular paths and UNC paths from Rust
|
|
150
|
+
path_to_node = {}
|
|
151
|
+
for node in nodes:
|
|
152
|
+
resolved_path = str(node.path.resolve())
|
|
153
|
+
# Map all path variants to the same node
|
|
154
|
+
path_to_node[normalize_path_variants(resolved_path)] = node
|
|
155
|
+
|
|
156
|
+
# Use directory resolver to get all edges at once
|
|
157
|
+
directory = nodes[0].path.parent
|
|
158
|
+
resolver = PyImportResolver()
|
|
159
|
+
|
|
160
|
+
result = resolver.resolve_directory(str(directory.resolve()), False)
|
|
161
|
+
edges = result.get("edges", [])
|
|
162
|
+
|
|
163
|
+
# Build edges from the result
|
|
164
|
+
for edge in edges:
|
|
165
|
+
from_path = normalize_path_variants(edge["from"])
|
|
166
|
+
to_path = normalize_path_variants(edge["to"])
|
|
167
|
+
|
|
168
|
+
# Only add edges for files we're managing
|
|
169
|
+
if from_path in path_to_node and to_path in path_to_node:
|
|
170
|
+
from_node = path_to_node[from_path]
|
|
171
|
+
to_node = path_to_node[to_path]
|
|
172
|
+
graph.add_edge(from_node, to_node)
|
|
173
|
+
|
|
174
|
+
return graph
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class NoDependencyStrategy:
|
|
178
|
+
"""
|
|
179
|
+
Strategy with no dependencies - all scripts can run in parallel.
|
|
180
|
+
|
|
181
|
+
Useful for testing or when scripts are known to be independent.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def build_graph(self, nodes: list[ScriptNode]) -> nx.DiGraph:
|
|
185
|
+
"""Build a graph with no edges (all nodes independent)."""
|
|
186
|
+
graph = nx.DiGraph()
|
|
187
|
+
for node in nodes:
|
|
188
|
+
graph.add_node(node)
|
|
189
|
+
return graph
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class DependencyResolver:
|
|
193
|
+
"""
|
|
194
|
+
Resolves execution order for scripts based on a pluggable dependency strategy.
|
|
195
|
+
|
|
196
|
+
Uses networkx for graph operations and provides utilities for both
|
|
197
|
+
level-based and eager BFS execution patterns.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
def __init__(self, strategy: DependencyStrategy | None = None):
|
|
201
|
+
"""
|
|
202
|
+
Initialize the resolver with a dependency strategy.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
strategy: The dependency resolution strategy to use.
|
|
206
|
+
Defaults to ETLDependencyStrategy if None.
|
|
207
|
+
"""
|
|
208
|
+
self.strategy = strategy or ETLDependencyStrategy()
|
|
209
|
+
|
|
210
|
+
def build_folder_graph(self, folder: Path) -> nx.DiGraph:
|
|
211
|
+
"""
|
|
212
|
+
Build the dependency graph for all script files in a folder.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
folder: The folder containing script files.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
A networkx DiGraph representing dependencies.
|
|
219
|
+
"""
|
|
220
|
+
graph = self.strategy.build_folder_graph(folder)
|
|
221
|
+
|
|
222
|
+
# Validate no cycles
|
|
223
|
+
if not nx.is_directed_acyclic_graph(graph):
|
|
224
|
+
cycles = list(nx.simple_cycles(graph))
|
|
225
|
+
cycle_info = "; ".join(
|
|
226
|
+
[" -> ".join(str(n.path.name) for n in cycle) for cycle in cycles[:3]]
|
|
227
|
+
)
|
|
228
|
+
raise ValueError(f"Circular dependencies detected: {cycle_info}")
|
|
229
|
+
|
|
230
|
+
return graph
|
|
231
|
+
|
|
232
|
+
def build_graph(self, nodes: list[ScriptNode]) -> nx.DiGraph:
|
|
233
|
+
"""
|
|
234
|
+
Build the dependency graph for the given nodes.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
nodes: List of script nodes.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
A networkx DiGraph representing dependencies.
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
ValueError: If the graph contains cycles.
|
|
244
|
+
"""
|
|
245
|
+
graph = self.strategy.build_graph(nodes)
|
|
246
|
+
|
|
247
|
+
# Validate no cycles
|
|
248
|
+
if not nx.is_directed_acyclic_graph(graph):
|
|
249
|
+
cycles = list(nx.simple_cycles(graph))
|
|
250
|
+
cycle_info = "; ".join(
|
|
251
|
+
[" -> ".join(str(n.path.name) for n in cycle) for cycle in cycles[:3]]
|
|
252
|
+
)
|
|
253
|
+
raise ValueError(f"Circular dependencies detected: {cycle_info}")
|
|
254
|
+
|
|
255
|
+
return graph
|
|
256
|
+
|
|
257
|
+
def get_root_nodes(self, graph: nx.DiGraph) -> list[ScriptNode]:
|
|
258
|
+
"""
|
|
259
|
+
Get nodes with no dependencies (in-degree 0).
|
|
260
|
+
|
|
261
|
+
These are the nodes that can be executed immediately.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
graph: The dependency graph.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
List of nodes with no incoming edges.
|
|
268
|
+
"""
|
|
269
|
+
return [node for node in graph.nodes() if graph.in_degree(node) == 0]
|
|
270
|
+
|
|
271
|
+
def get_dependents(self, graph: nx.DiGraph, node: ScriptNode) -> list[ScriptNode]:
|
|
272
|
+
"""
|
|
273
|
+
Get nodes that directly depend on the given node.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
graph: The dependency graph.
|
|
277
|
+
node: The node whose dependents to find.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
List of nodes that have 'node' as a dependency.
|
|
281
|
+
"""
|
|
282
|
+
return list(graph.successors(node))
|
|
283
|
+
|
|
284
|
+
def get_dependencies(self, graph: nx.DiGraph, node: ScriptNode) -> list[ScriptNode]:
|
|
285
|
+
"""
|
|
286
|
+
Get nodes that the given node depends on.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
graph: The dependency graph.
|
|
290
|
+
node: The node whose dependencies to find.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
List of nodes that must run before 'node'.
|
|
294
|
+
"""
|
|
295
|
+
return list(graph.predecessors(node))
|
|
296
|
+
|
|
297
|
+
def get_dependency_graph(
|
|
298
|
+
self, nodes: list[ScriptNode]
|
|
299
|
+
) -> dict[ScriptNode, set[ScriptNode]]:
|
|
300
|
+
"""
|
|
301
|
+
Get the raw dependency graph as a dict for inspection/debugging.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Dict mapping each node to its dependencies (predecessors).
|
|
305
|
+
"""
|
|
306
|
+
graph = self.build_graph(nodes)
|
|
307
|
+
return {node: set(graph.predecessors(node)) for node in graph.nodes()}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def create_script_nodes(files: list[Path]) -> list[ScriptNode]:
|
|
311
|
+
"""
|
|
312
|
+
Create ScriptNode instances from file paths.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
files: List of paths to script files.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
List of ScriptNode instances with file contents loaded.
|
|
319
|
+
"""
|
|
320
|
+
nodes = []
|
|
321
|
+
for file in files:
|
|
322
|
+
nodes.append(ScriptNode(path=file))
|
|
323
|
+
return nodes
|