pytrilogy 0.3.148__cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.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 (206) hide show
  1. LICENSE.md +19 -0
  2. _preql_import_resolver/__init__.py +5 -0
  3. _preql_import_resolver/_preql_import_resolver.cpython-312-aarch64-linux-gnu.so +0 -0
  4. pytrilogy-0.3.148.dist-info/METADATA +555 -0
  5. pytrilogy-0.3.148.dist-info/RECORD +206 -0
  6. pytrilogy-0.3.148.dist-info/WHEEL +5 -0
  7. pytrilogy-0.3.148.dist-info/entry_points.txt +2 -0
  8. pytrilogy-0.3.148.dist-info/licenses/LICENSE.md +19 -0
  9. trilogy/__init__.py +27 -0
  10. trilogy/ai/README.md +10 -0
  11. trilogy/ai/__init__.py +19 -0
  12. trilogy/ai/constants.py +92 -0
  13. trilogy/ai/conversation.py +107 -0
  14. trilogy/ai/enums.py +7 -0
  15. trilogy/ai/execute.py +50 -0
  16. trilogy/ai/models.py +34 -0
  17. trilogy/ai/prompts.py +100 -0
  18. trilogy/ai/providers/__init__.py +0 -0
  19. trilogy/ai/providers/anthropic.py +106 -0
  20. trilogy/ai/providers/base.py +24 -0
  21. trilogy/ai/providers/google.py +146 -0
  22. trilogy/ai/providers/openai.py +89 -0
  23. trilogy/ai/providers/utils.py +68 -0
  24. trilogy/authoring/README.md +3 -0
  25. trilogy/authoring/__init__.py +148 -0
  26. trilogy/constants.py +119 -0
  27. trilogy/core/README.md +52 -0
  28. trilogy/core/__init__.py +0 -0
  29. trilogy/core/constants.py +6 -0
  30. trilogy/core/enums.py +454 -0
  31. trilogy/core/env_processor.py +239 -0
  32. trilogy/core/environment_helpers.py +320 -0
  33. trilogy/core/ergonomics.py +193 -0
  34. trilogy/core/exceptions.py +123 -0
  35. trilogy/core/functions.py +1240 -0
  36. trilogy/core/graph_models.py +142 -0
  37. trilogy/core/internal.py +85 -0
  38. trilogy/core/models/__init__.py +0 -0
  39. trilogy/core/models/author.py +2662 -0
  40. trilogy/core/models/build.py +2603 -0
  41. trilogy/core/models/build_environment.py +165 -0
  42. trilogy/core/models/core.py +506 -0
  43. trilogy/core/models/datasource.py +434 -0
  44. trilogy/core/models/environment.py +756 -0
  45. trilogy/core/models/execute.py +1213 -0
  46. trilogy/core/optimization.py +251 -0
  47. trilogy/core/optimizations/__init__.py +12 -0
  48. trilogy/core/optimizations/base_optimization.py +17 -0
  49. trilogy/core/optimizations/hide_unused_concept.py +47 -0
  50. trilogy/core/optimizations/inline_datasource.py +102 -0
  51. trilogy/core/optimizations/predicate_pushdown.py +245 -0
  52. trilogy/core/processing/README.md +94 -0
  53. trilogy/core/processing/READMEv2.md +121 -0
  54. trilogy/core/processing/VIRTUAL_UNNEST.md +30 -0
  55. trilogy/core/processing/__init__.py +0 -0
  56. trilogy/core/processing/concept_strategies_v3.py +508 -0
  57. trilogy/core/processing/constants.py +15 -0
  58. trilogy/core/processing/discovery_node_factory.py +451 -0
  59. trilogy/core/processing/discovery_utility.py +548 -0
  60. trilogy/core/processing/discovery_validation.py +167 -0
  61. trilogy/core/processing/graph_utils.py +43 -0
  62. trilogy/core/processing/node_generators/README.md +9 -0
  63. trilogy/core/processing/node_generators/__init__.py +31 -0
  64. trilogy/core/processing/node_generators/basic_node.py +160 -0
  65. trilogy/core/processing/node_generators/common.py +270 -0
  66. trilogy/core/processing/node_generators/constant_node.py +38 -0
  67. trilogy/core/processing/node_generators/filter_node.py +315 -0
  68. trilogy/core/processing/node_generators/group_node.py +213 -0
  69. trilogy/core/processing/node_generators/group_to_node.py +117 -0
  70. trilogy/core/processing/node_generators/multiselect_node.py +207 -0
  71. trilogy/core/processing/node_generators/node_merge_node.py +695 -0
  72. trilogy/core/processing/node_generators/recursive_node.py +88 -0
  73. trilogy/core/processing/node_generators/rowset_node.py +165 -0
  74. trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  75. trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +261 -0
  76. trilogy/core/processing/node_generators/select_merge_node.py +786 -0
  77. trilogy/core/processing/node_generators/select_node.py +95 -0
  78. trilogy/core/processing/node_generators/synonym_node.py +98 -0
  79. trilogy/core/processing/node_generators/union_node.py +91 -0
  80. trilogy/core/processing/node_generators/unnest_node.py +182 -0
  81. trilogy/core/processing/node_generators/window_node.py +201 -0
  82. trilogy/core/processing/nodes/README.md +28 -0
  83. trilogy/core/processing/nodes/__init__.py +179 -0
  84. trilogy/core/processing/nodes/base_node.py +522 -0
  85. trilogy/core/processing/nodes/filter_node.py +75 -0
  86. trilogy/core/processing/nodes/group_node.py +194 -0
  87. trilogy/core/processing/nodes/merge_node.py +420 -0
  88. trilogy/core/processing/nodes/recursive_node.py +46 -0
  89. trilogy/core/processing/nodes/select_node_v2.py +242 -0
  90. trilogy/core/processing/nodes/union_node.py +53 -0
  91. trilogy/core/processing/nodes/unnest_node.py +62 -0
  92. trilogy/core/processing/nodes/window_node.py +56 -0
  93. trilogy/core/processing/utility.py +823 -0
  94. trilogy/core/query_processor.py +604 -0
  95. trilogy/core/statements/README.md +35 -0
  96. trilogy/core/statements/__init__.py +0 -0
  97. trilogy/core/statements/author.py +536 -0
  98. trilogy/core/statements/build.py +0 -0
  99. trilogy/core/statements/common.py +20 -0
  100. trilogy/core/statements/execute.py +155 -0
  101. trilogy/core/table_processor.py +66 -0
  102. trilogy/core/utility.py +8 -0
  103. trilogy/core/validation/README.md +46 -0
  104. trilogy/core/validation/__init__.py +0 -0
  105. trilogy/core/validation/common.py +161 -0
  106. trilogy/core/validation/concept.py +146 -0
  107. trilogy/core/validation/datasource.py +227 -0
  108. trilogy/core/validation/environment.py +73 -0
  109. trilogy/core/validation/fix.py +256 -0
  110. trilogy/dialect/__init__.py +32 -0
  111. trilogy/dialect/base.py +1431 -0
  112. trilogy/dialect/bigquery.py +314 -0
  113. trilogy/dialect/common.py +147 -0
  114. trilogy/dialect/config.py +159 -0
  115. trilogy/dialect/dataframe.py +50 -0
  116. trilogy/dialect/duckdb.py +376 -0
  117. trilogy/dialect/enums.py +149 -0
  118. trilogy/dialect/metadata.py +173 -0
  119. trilogy/dialect/mock.py +190 -0
  120. trilogy/dialect/postgres.py +117 -0
  121. trilogy/dialect/presto.py +110 -0
  122. trilogy/dialect/results.py +89 -0
  123. trilogy/dialect/snowflake.py +129 -0
  124. trilogy/dialect/sql_server.py +137 -0
  125. trilogy/engine.py +48 -0
  126. trilogy/execution/__init__.py +17 -0
  127. trilogy/execution/config.py +119 -0
  128. trilogy/execution/state/__init__.py +0 -0
  129. trilogy/execution/state/file_state_store.py +0 -0
  130. trilogy/execution/state/sqllite_state_store.py +0 -0
  131. trilogy/execution/state/state_store.py +301 -0
  132. trilogy/executor.py +656 -0
  133. trilogy/hooks/__init__.py +4 -0
  134. trilogy/hooks/base_hook.py +40 -0
  135. trilogy/hooks/graph_hook.py +135 -0
  136. trilogy/hooks/query_debugger.py +166 -0
  137. trilogy/metadata/__init__.py +0 -0
  138. trilogy/parser.py +10 -0
  139. trilogy/parsing/README.md +21 -0
  140. trilogy/parsing/__init__.py +0 -0
  141. trilogy/parsing/common.py +1069 -0
  142. trilogy/parsing/config.py +5 -0
  143. trilogy/parsing/exceptions.py +8 -0
  144. trilogy/parsing/helpers.py +1 -0
  145. trilogy/parsing/parse_engine.py +2863 -0
  146. trilogy/parsing/render.py +773 -0
  147. trilogy/parsing/trilogy.lark +544 -0
  148. trilogy/py.typed +0 -0
  149. trilogy/render.py +45 -0
  150. trilogy/scripts/README.md +9 -0
  151. trilogy/scripts/__init__.py +0 -0
  152. trilogy/scripts/agent.py +41 -0
  153. trilogy/scripts/agent_info.py +306 -0
  154. trilogy/scripts/common.py +430 -0
  155. trilogy/scripts/dependency/Cargo.lock +617 -0
  156. trilogy/scripts/dependency/Cargo.toml +39 -0
  157. trilogy/scripts/dependency/README.md +131 -0
  158. trilogy/scripts/dependency/build.sh +25 -0
  159. trilogy/scripts/dependency/src/directory_resolver.rs +387 -0
  160. trilogy/scripts/dependency/src/lib.rs +16 -0
  161. trilogy/scripts/dependency/src/main.rs +770 -0
  162. trilogy/scripts/dependency/src/parser.rs +435 -0
  163. trilogy/scripts/dependency/src/preql.pest +208 -0
  164. trilogy/scripts/dependency/src/python_bindings.rs +311 -0
  165. trilogy/scripts/dependency/src/resolver.rs +716 -0
  166. trilogy/scripts/dependency/tests/base.preql +3 -0
  167. trilogy/scripts/dependency/tests/cli_integration.rs +377 -0
  168. trilogy/scripts/dependency/tests/customer.preql +6 -0
  169. trilogy/scripts/dependency/tests/main.preql +9 -0
  170. trilogy/scripts/dependency/tests/orders.preql +7 -0
  171. trilogy/scripts/dependency/tests/test_data/base.preql +9 -0
  172. trilogy/scripts/dependency/tests/test_data/consumer.preql +1 -0
  173. trilogy/scripts/dependency.py +323 -0
  174. trilogy/scripts/display.py +555 -0
  175. trilogy/scripts/environment.py +59 -0
  176. trilogy/scripts/fmt.py +32 -0
  177. trilogy/scripts/ingest.py +472 -0
  178. trilogy/scripts/ingest_helpers/__init__.py +1 -0
  179. trilogy/scripts/ingest_helpers/foreign_keys.py +123 -0
  180. trilogy/scripts/ingest_helpers/formatting.py +93 -0
  181. trilogy/scripts/ingest_helpers/typing.py +161 -0
  182. trilogy/scripts/init.py +105 -0
  183. trilogy/scripts/parallel_execution.py +748 -0
  184. trilogy/scripts/plan.py +189 -0
  185. trilogy/scripts/refresh.py +106 -0
  186. trilogy/scripts/run.py +79 -0
  187. trilogy/scripts/serve.py +202 -0
  188. trilogy/scripts/serve_helpers/__init__.py +41 -0
  189. trilogy/scripts/serve_helpers/file_discovery.py +142 -0
  190. trilogy/scripts/serve_helpers/index_generation.py +206 -0
  191. trilogy/scripts/serve_helpers/models.py +38 -0
  192. trilogy/scripts/single_execution.py +131 -0
  193. trilogy/scripts/testing.py +129 -0
  194. trilogy/scripts/trilogy.py +75 -0
  195. trilogy/std/__init__.py +0 -0
  196. trilogy/std/color.preql +3 -0
  197. trilogy/std/date.preql +13 -0
  198. trilogy/std/display.preql +18 -0
  199. trilogy/std/geography.preql +22 -0
  200. trilogy/std/metric.preql +15 -0
  201. trilogy/std/money.preql +67 -0
  202. trilogy/std/net.preql +14 -0
  203. trilogy/std/ranking.preql +7 -0
  204. trilogy/std/report.preql +5 -0
  205. trilogy/std/semantic.preql +6 -0
  206. 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