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.
Files changed (182) hide show
  1. LICENSE.md +19 -0
  2. _preql_import_resolver/__init__.py +5 -0
  3. _preql_import_resolver/_preql_import_resolver.cpython-311-x86_64-linux-gnu.so +0 -0
  4. pytrilogy-0.3.138.dist-info/METADATA +525 -0
  5. pytrilogy-0.3.138.dist-info/RECORD +182 -0
  6. pytrilogy-0.3.138.dist-info/WHEEL +5 -0
  7. pytrilogy-0.3.138.dist-info/entry_points.txt +2 -0
  8. pytrilogy-0.3.138.dist-info/licenses/LICENSE.md +19 -0
  9. trilogy/__init__.py +9 -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 +87 -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 +143 -0
  26. trilogy/constants.py +113 -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 +443 -0
  31. trilogy/core/env_processor.py +120 -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 +1227 -0
  36. trilogy/core/graph_models.py +139 -0
  37. trilogy/core/internal.py +85 -0
  38. trilogy/core/models/__init__.py +0 -0
  39. trilogy/core/models/author.py +2672 -0
  40. trilogy/core/models/build.py +2521 -0
  41. trilogy/core/models/build_environment.py +180 -0
  42. trilogy/core/models/core.py +494 -0
  43. trilogy/core/models/datasource.py +322 -0
  44. trilogy/core/models/environment.py +748 -0
  45. trilogy/core/models/execute.py +1177 -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 +517 -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 +268 -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 +205 -0
  71. trilogy/core/processing/node_generators/node_merge_node.py +653 -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 +748 -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 +519 -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 +596 -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 +106 -0
  110. trilogy/dialect/__init__.py +32 -0
  111. trilogy/dialect/base.py +1359 -0
  112. trilogy/dialect/bigquery.py +256 -0
  113. trilogy/dialect/common.py +147 -0
  114. trilogy/dialect/config.py +144 -0
  115. trilogy/dialect/dataframe.py +50 -0
  116. trilogy/dialect/duckdb.py +177 -0
  117. trilogy/dialect/enums.py +147 -0
  118. trilogy/dialect/metadata.py +173 -0
  119. trilogy/dialect/mock.py +190 -0
  120. trilogy/dialect/postgres.py +91 -0
  121. trilogy/dialect/presto.py +104 -0
  122. trilogy/dialect/results.py +89 -0
  123. trilogy/dialect/snowflake.py +90 -0
  124. trilogy/dialect/sql_server.py +92 -0
  125. trilogy/engine.py +48 -0
  126. trilogy/execution/config.py +75 -0
  127. trilogy/executor.py +568 -0
  128. trilogy/hooks/__init__.py +4 -0
  129. trilogy/hooks/base_hook.py +40 -0
  130. trilogy/hooks/graph_hook.py +139 -0
  131. trilogy/hooks/query_debugger.py +166 -0
  132. trilogy/metadata/__init__.py +0 -0
  133. trilogy/parser.py +10 -0
  134. trilogy/parsing/README.md +21 -0
  135. trilogy/parsing/__init__.py +0 -0
  136. trilogy/parsing/common.py +1069 -0
  137. trilogy/parsing/config.py +5 -0
  138. trilogy/parsing/exceptions.py +8 -0
  139. trilogy/parsing/helpers.py +1 -0
  140. trilogy/parsing/parse_engine.py +2813 -0
  141. trilogy/parsing/render.py +750 -0
  142. trilogy/parsing/trilogy.lark +540 -0
  143. trilogy/py.typed +0 -0
  144. trilogy/render.py +42 -0
  145. trilogy/scripts/README.md +7 -0
  146. trilogy/scripts/__init__.py +0 -0
  147. trilogy/scripts/dependency/Cargo.lock +617 -0
  148. trilogy/scripts/dependency/Cargo.toml +39 -0
  149. trilogy/scripts/dependency/README.md +131 -0
  150. trilogy/scripts/dependency/build.sh +25 -0
  151. trilogy/scripts/dependency/src/directory_resolver.rs +162 -0
  152. trilogy/scripts/dependency/src/lib.rs +16 -0
  153. trilogy/scripts/dependency/src/main.rs +770 -0
  154. trilogy/scripts/dependency/src/parser.rs +435 -0
  155. trilogy/scripts/dependency/src/preql.pest +208 -0
  156. trilogy/scripts/dependency/src/python_bindings.rs +289 -0
  157. trilogy/scripts/dependency/src/resolver.rs +716 -0
  158. trilogy/scripts/dependency/tests/base.preql +3 -0
  159. trilogy/scripts/dependency/tests/cli_integration.rs +377 -0
  160. trilogy/scripts/dependency/tests/customer.preql +6 -0
  161. trilogy/scripts/dependency/tests/main.preql +9 -0
  162. trilogy/scripts/dependency/tests/orders.preql +7 -0
  163. trilogy/scripts/dependency/tests/test_data/base.preql +9 -0
  164. trilogy/scripts/dependency/tests/test_data/consumer.preql +1 -0
  165. trilogy/scripts/dependency.py +323 -0
  166. trilogy/scripts/display.py +460 -0
  167. trilogy/scripts/environment.py +46 -0
  168. trilogy/scripts/parallel_execution.py +483 -0
  169. trilogy/scripts/single_execution.py +131 -0
  170. trilogy/scripts/trilogy.py +772 -0
  171. trilogy/std/__init__.py +0 -0
  172. trilogy/std/color.preql +3 -0
  173. trilogy/std/date.preql +13 -0
  174. trilogy/std/display.preql +18 -0
  175. trilogy/std/geography.preql +22 -0
  176. trilogy/std/metric.preql +15 -0
  177. trilogy/std/money.preql +67 -0
  178. trilogy/std/net.preql +14 -0
  179. trilogy/std/ranking.preql +7 -0
  180. trilogy/std/report.preql +5 -0
  181. trilogy/std/semantic.preql +6 -0
  182. trilogy/utility.py +34 -0
@@ -0,0 +1,289 @@
1
+ use pyo3::prelude::*;
2
+ use pyo3::types::{PyDict, PyList};
3
+ use std::path::PathBuf;
4
+
5
+ use crate::parser::parse_file;
6
+ use crate::resolver::ImportResolver;
7
+
8
+ /// Python module for PreQL import resolution
9
+ #[pymodule]
10
+ fn _preql_import_resolver(m: &Bound<'_, PyModule>) -> PyResult<()> {
11
+ m.add_class::<PyImportResolver>()?;
12
+ m.add_function(wrap_pyfunction!(parse_preql_file, m)?)?;
13
+ Ok(())
14
+ }
15
+
16
+ /// Parse a PreQL file and return imports, datasources, and persists
17
+ #[pyfunction]
18
+ fn parse_preql_file(py: Python<'_>, content: &str) -> PyResult<PyObject> {
19
+ let parsed = parse_file(content)
20
+ .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Parse error: {}", e)))?;
21
+
22
+ let result = PyDict::new_bound(py);
23
+
24
+ // Add imports
25
+ let imports = PyList::empty_bound(py);
26
+ for import in parsed.imports {
27
+ let import_dict = PyDict::new_bound(py);
28
+ import_dict.set_item("raw_path", import.raw_path)?;
29
+ import_dict.set_item("alias", import.alias)?;
30
+ import_dict.set_item("is_stdlib", import.is_stdlib)?;
31
+ import_dict.set_item("parent_dirs", import.parent_dirs)?;
32
+ imports.append(import_dict)?;
33
+ }
34
+ result.set_item("imports", imports)?;
35
+
36
+ // Add datasources
37
+ let datasources = PyList::empty_bound(py);
38
+ for ds in parsed.datasources {
39
+ let ds_dict = PyDict::new_bound(py);
40
+ ds_dict.set_item("name", ds.name)?;
41
+ datasources.append(ds_dict)?;
42
+ }
43
+ result.set_item("datasources", datasources)?;
44
+
45
+ // Add persists
46
+ let persists = PyList::empty_bound(py);
47
+ for persist in parsed.persists {
48
+ let persist_dict = PyDict::new_bound(py);
49
+ persist_dict.set_item("mode", persist.mode.to_string())?;
50
+ persist_dict.set_item("target_datasource", persist.target_datasource)?;
51
+ persists.append(persist_dict)?;
52
+ }
53
+ result.set_item("persists", persists)?;
54
+
55
+ Ok(result.into())
56
+ }
57
+
58
+ /// Python wrapper for ImportResolver
59
+ #[pyclass]
60
+ struct PyImportResolver {
61
+ resolver: ImportResolver,
62
+ }
63
+
64
+ #[pymethods]
65
+ impl PyImportResolver {
66
+ #[new]
67
+ fn new() -> Self {
68
+ PyImportResolver {
69
+ resolver: ImportResolver::new(),
70
+ }
71
+ }
72
+
73
+ /// Resolve dependencies for a file and return the dependency graph
74
+ fn resolve(&mut self, py: Python<'_>, path: &str) -> PyResult<PyObject> {
75
+ let path_buf = PathBuf::from(path);
76
+
77
+ let graph = self
78
+ .resolver
79
+ .resolve(&path_buf)
80
+ .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Resolve error: {}", e)))?;
81
+
82
+ let result = pyo3::types::PyDict::new_bound(py);
83
+
84
+ // Add root
85
+ result.set_item("root", graph.root.to_string_lossy().to_string())?;
86
+
87
+ // Add order (list of file paths in execution order)
88
+ let order = PyList::empty_bound(py);
89
+ for file_path in &graph.order {
90
+ order.append(file_path.to_string_lossy().to_string())?;
91
+ }
92
+ result.set_item("order", order)?;
93
+
94
+ // Add files (detailed info about each file)
95
+ let files = pyo3::types::PyDict::new_bound(py);
96
+ for (path, node) in &graph.files {
97
+ let node_dict = pyo3::types::PyDict::new_bound(py);
98
+
99
+ node_dict.set_item("path", node.path.to_string_lossy().to_string())?;
100
+ node_dict.set_item("relative_path", node.relative_path.to_string_lossy().to_string())?;
101
+
102
+ // imports
103
+ let imports = PyList::empty_bound(py);
104
+ for import in &node.imports {
105
+ let import_dict = pyo3::types::PyDict::new_bound(py);
106
+ import_dict.set_item("raw_path", &import.raw_path)?;
107
+ import_dict.set_item("alias", &import.alias)?;
108
+ import_dict.set_item("is_stdlib", import.is_stdlib)?;
109
+ if let Some(resolved_path) = &import.resolved_path {
110
+ import_dict.set_item("resolved_path", resolved_path.to_string_lossy().to_string())?;
111
+ }
112
+ imports.append(import_dict)?;
113
+ }
114
+ node_dict.set_item("imports", imports)?;
115
+
116
+ // datasources
117
+ let datasources = PyList::empty_bound(py);
118
+ for ds in &node.datasources {
119
+ let ds_dict = pyo3::types::PyDict::new_bound(py);
120
+ ds_dict.set_item("name", &ds.name)?;
121
+ datasources.append(ds_dict)?;
122
+ }
123
+ node_dict.set_item("datasources", datasources)?;
124
+
125
+ // persists
126
+ let persists = PyList::empty_bound(py);
127
+ for persist in &node.persists {
128
+ let persist_dict = pyo3::types::PyDict::new_bound(py);
129
+ persist_dict.set_item("mode", &persist.mode)?;
130
+ persist_dict.set_item("target_datasource", &persist.target_datasource)?;
131
+ persists.append(persist_dict)?;
132
+ }
133
+ node_dict.set_item("persists", persists)?;
134
+
135
+ // dependency lists
136
+ let import_dependencies = PyList::empty_bound(py);
137
+ for dep in &node.import_dependencies {
138
+ import_dependencies.append(dep.to_string_lossy().to_string())?;
139
+ }
140
+ node_dict.set_item("import_dependencies", import_dependencies)?;
141
+
142
+ let updates_datasources = PyList::empty_bound(py);
143
+ for ds in &node.updates_datasources {
144
+ updates_datasources.append(ds)?;
145
+ }
146
+ node_dict.set_item("updates_datasources", updates_datasources)?;
147
+
148
+ let declares_datasources = PyList::empty_bound(py);
149
+ for ds in &node.declares_datasources {
150
+ declares_datasources.append(ds)?;
151
+ }
152
+ node_dict.set_item("declares_datasources", declares_datasources)?;
153
+
154
+ let depends_on_datasources = PyList::empty_bound(py);
155
+ for ds in &node.depends_on_datasources {
156
+ depends_on_datasources.append(ds)?;
157
+ }
158
+ node_dict.set_item("depends_on_datasources", depends_on_datasources)?;
159
+
160
+ files.set_item(path.to_string_lossy().to_string(), node_dict)?;
161
+ }
162
+ result.set_item("files", files)?;
163
+
164
+ // Add datasource_declarations
165
+ let declarations = pyo3::types::PyDict::new_bound(py);
166
+ for (ds_name, declaring_path) in &graph.datasource_declarations {
167
+ declarations.set_item(ds_name, declaring_path.to_string_lossy().to_string())?;
168
+ }
169
+ result.set_item("datasource_declarations", declarations)?;
170
+
171
+ // Add datasource_updaters
172
+ let updaters = pyo3::types::PyDict::new_bound(py);
173
+ for (ds_name, updater_paths) in &graph.datasource_updaters {
174
+ let paths_list = PyList::empty_bound(py);
175
+ for updater_path in updater_paths {
176
+ paths_list.append(updater_path.to_string_lossy().to_string())?;
177
+ }
178
+ updaters.set_item(ds_name, paths_list)?;
179
+ }
180
+ result.set_item("datasource_updaters", updaters)?;
181
+
182
+ // Add warnings
183
+ let warnings = PyList::empty_bound(py);
184
+ for warning in &graph.warnings {
185
+ warnings.append(warning)?;
186
+ }
187
+ result.set_item("warnings", warnings)?;
188
+
189
+ Ok(result.into())
190
+ }
191
+
192
+ /// Get just the dependency order for a file
193
+ fn resolve_order(&mut self, path: &str) -> PyResult<Vec<String>> {
194
+ let path_buf = PathBuf::from(path);
195
+
196
+ let graph = self
197
+ .resolver
198
+ .resolve(&path_buf)
199
+ .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Resolve error: {}", e)))?;
200
+
201
+ Ok(graph
202
+ .order
203
+ .iter()
204
+ .map(|p| p.to_string_lossy().to_string())
205
+ .collect())
206
+ }
207
+
208
+ /// Resolve dependencies for all files in a directory
209
+ fn resolve_directory(&mut self, py: Python<'_>, dir_path: &str, _recursive: bool) -> PyResult<PyObject> {
210
+ use crate::directory_resolver::{process_directory_with_imports, build_edges, EdgeReason};
211
+ use std::fs;
212
+
213
+ let dir = PathBuf::from(dir_path);
214
+ if !dir.is_dir() {
215
+ return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
216
+ format!("{} is not a directory", dir_path)
217
+ ));
218
+ }
219
+
220
+ // Collect all .preql files in the top-level directory
221
+ let mut initial_files = Vec::new();
222
+ let read_dir = fs::read_dir(&dir)
223
+ .map_err(|e| PyErr::new::<pyo3::exceptions::PyIOError, _>(format!("Failed to read directory: {}", e)))?;
224
+
225
+ for entry in read_dir {
226
+ let entry = entry.map_err(|e| PyErr::new::<pyo3::exceptions::PyIOError, _>(format!("Failed to read entry: {}", e)))?;
227
+ let path = entry.path();
228
+ if path.is_file() && path.extension().map_or(false, |ext| ext == "preql") {
229
+ initial_files.push(path);
230
+ }
231
+ }
232
+
233
+ if initial_files.is_empty() {
234
+ return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
235
+ format!("No .preql files found in {}", dir_path)
236
+ ));
237
+ }
238
+
239
+ // Process directory with transitive import discovery
240
+ let graph = process_directory_with_imports(initial_files)
241
+ .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))?;
242
+
243
+ // Build edges
244
+ let edges = build_edges(&graph);
245
+
246
+ // Convert to Python objects
247
+ let result = PyDict::new_bound(py);
248
+ let edges_list = PyList::empty_bound(py);
249
+ let files_list = PyList::empty_bound(py);
250
+ let warnings_list = PyList::empty_bound(py);
251
+
252
+ // Add files
253
+ for file_info in graph.files.values() {
254
+ files_list.append(file_info.path.to_string_lossy().to_string())?;
255
+ }
256
+
257
+ // Add edges
258
+ for edge in edges {
259
+ let edge_dict = PyDict::new_bound(py);
260
+ edge_dict.set_item("from", edge.from.to_string_lossy().to_string())?;
261
+ edge_dict.set_item("to", edge.to.to_string_lossy().to_string())?;
262
+
263
+ let reason_dict = PyDict::new_bound(py);
264
+ match edge.reason {
265
+ EdgeReason::Import => {
266
+ reason_dict.set_item("type", "import")?;
267
+ }
268
+ EdgeReason::PersistBeforeDeclare { datasource } => {
269
+ reason_dict.set_item("type", "persist_before_declare")?;
270
+ reason_dict.set_item("datasource", datasource)?;
271
+ }
272
+ }
273
+ edge_dict.set_item("reason", reason_dict)?;
274
+ edges_list.append(edge_dict)?;
275
+ }
276
+
277
+ // Add warnings
278
+ for warning in graph.warnings {
279
+ warnings_list.append(warning)?;
280
+ }
281
+
282
+ result.set_item("directory", dir.to_string_lossy().to_string())?;
283
+ result.set_item("files", files_list)?;
284
+ result.set_item("edges", edges_list)?;
285
+ result.set_item("warnings", warnings_list)?;
286
+
287
+ Ok(result.into())
288
+ }
289
+ }