pytrilogy 0.3.142__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. LICENSE.md +19 -0
  2. _preql_import_resolver/__init__.py +5 -0
  3. _preql_import_resolver/_preql_import_resolver.cp312-win_amd64.pyd +0 -0
  4. pytrilogy-0.3.142.dist-info/METADATA +555 -0
  5. pytrilogy-0.3.142.dist-info/RECORD +200 -0
  6. pytrilogy-0.3.142.dist-info/WHEEL +4 -0
  7. pytrilogy-0.3.142.dist-info/entry_points.txt +2 -0
  8. pytrilogy-0.3.142.dist-info/licenses/LICENSE.md +19 -0
  9. trilogy/__init__.py +16 -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 +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 +2669 -0
  40. trilogy/core/models/build.py +2521 -0
  41. trilogy/core/models/build_environment.py +180 -0
  42. trilogy/core/models/core.py +501 -0
  43. trilogy/core/models/datasource.py +322 -0
  44. trilogy/core/models/environment.py +751 -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 +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 +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 +256 -0
  110. trilogy/dialect/__init__.py +32 -0
  111. trilogy/dialect/base.py +1392 -0
  112. trilogy/dialect/bigquery.py +308 -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 +231 -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 +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/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 +769 -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 +9 -0
  146. trilogy/scripts/__init__.py +0 -0
  147. trilogy/scripts/agent.py +41 -0
  148. trilogy/scripts/agent_info.py +303 -0
  149. trilogy/scripts/common.py +355 -0
  150. trilogy/scripts/dependency/Cargo.lock +617 -0
  151. trilogy/scripts/dependency/Cargo.toml +39 -0
  152. trilogy/scripts/dependency/README.md +131 -0
  153. trilogy/scripts/dependency/build.sh +25 -0
  154. trilogy/scripts/dependency/src/directory_resolver.rs +177 -0
  155. trilogy/scripts/dependency/src/lib.rs +16 -0
  156. trilogy/scripts/dependency/src/main.rs +770 -0
  157. trilogy/scripts/dependency/src/parser.rs +435 -0
  158. trilogy/scripts/dependency/src/preql.pest +208 -0
  159. trilogy/scripts/dependency/src/python_bindings.rs +303 -0
  160. trilogy/scripts/dependency/src/resolver.rs +716 -0
  161. trilogy/scripts/dependency/tests/base.preql +3 -0
  162. trilogy/scripts/dependency/tests/cli_integration.rs +377 -0
  163. trilogy/scripts/dependency/tests/customer.preql +6 -0
  164. trilogy/scripts/dependency/tests/main.preql +9 -0
  165. trilogy/scripts/dependency/tests/orders.preql +7 -0
  166. trilogy/scripts/dependency/tests/test_data/base.preql +9 -0
  167. trilogy/scripts/dependency/tests/test_data/consumer.preql +1 -0
  168. trilogy/scripts/dependency.py +323 -0
  169. trilogy/scripts/display.py +512 -0
  170. trilogy/scripts/environment.py +46 -0
  171. trilogy/scripts/fmt.py +32 -0
  172. trilogy/scripts/ingest.py +471 -0
  173. trilogy/scripts/ingest_helpers/__init__.py +1 -0
  174. trilogy/scripts/ingest_helpers/foreign_keys.py +123 -0
  175. trilogy/scripts/ingest_helpers/formatting.py +93 -0
  176. trilogy/scripts/ingest_helpers/typing.py +161 -0
  177. trilogy/scripts/init.py +105 -0
  178. trilogy/scripts/parallel_execution.py +713 -0
  179. trilogy/scripts/plan.py +189 -0
  180. trilogy/scripts/run.py +63 -0
  181. trilogy/scripts/serve.py +140 -0
  182. trilogy/scripts/serve_helpers/__init__.py +41 -0
  183. trilogy/scripts/serve_helpers/file_discovery.py +142 -0
  184. trilogy/scripts/serve_helpers/index_generation.py +206 -0
  185. trilogy/scripts/serve_helpers/models.py +38 -0
  186. trilogy/scripts/single_execution.py +131 -0
  187. trilogy/scripts/testing.py +119 -0
  188. trilogy/scripts/trilogy.py +68 -0
  189. trilogy/std/__init__.py +0 -0
  190. trilogy/std/color.preql +3 -0
  191. trilogy/std/date.preql +13 -0
  192. trilogy/std/display.preql +18 -0
  193. trilogy/std/geography.preql +22 -0
  194. trilogy/std/metric.preql +15 -0
  195. trilogy/std/money.preql +67 -0
  196. trilogy/std/net.preql +14 -0
  197. trilogy/std/ranking.preql +7 -0
  198. trilogy/std/report.preql +5 -0
  199. trilogy/std/semantic.preql +6 -0
  200. trilogy/utility.py +34 -0
@@ -0,0 +1,536 @@
1
+ from functools import cached_property
2
+ from pathlib import Path
3
+ from typing import Annotated, List, Optional, Union
4
+
5
+ from pydantic import BaseModel, Field, computed_field, field_validator
6
+ from pydantic.functional_validators import PlainValidator
7
+
8
+ from trilogy.constants import CONFIG, DEFAULT_NAMESPACE
9
+ from trilogy.core.enums import (
10
+ ConceptSource,
11
+ CreateMode,
12
+ FunctionClass,
13
+ IOType,
14
+ Modifier,
15
+ PersistMode,
16
+ PublishAction,
17
+ ShowCategory,
18
+ ValidationScope,
19
+ )
20
+ from trilogy.core.models.author import (
21
+ AggregateWrapper,
22
+ AlignClause,
23
+ ArgBinding,
24
+ Concept,
25
+ ConceptRef,
26
+ CustomType,
27
+ DeriveClause,
28
+ Expr,
29
+ FilterItem,
30
+ Function,
31
+ FunctionCallWrapper,
32
+ Grain,
33
+ HasUUID,
34
+ HavingClause,
35
+ Metadata,
36
+ MultiSelectLineage,
37
+ OrderBy,
38
+ Parenthetical,
39
+ SelectLineage,
40
+ UndefinedConcept,
41
+ WhereClause,
42
+ WindowItem,
43
+ )
44
+ from trilogy.core.models.datasource import Address, ColumnAssignment, Datasource
45
+ from trilogy.core.models.environment import (
46
+ Environment,
47
+ EnvironmentConceptDict,
48
+ validate_concepts,
49
+ )
50
+ from trilogy.core.statements.common import SelectTypeMixin
51
+ from trilogy.utility import unique
52
+
53
+
54
+ class ConceptTransform(BaseModel):
55
+ function: (
56
+ Function
57
+ | FilterItem
58
+ | WindowItem
59
+ | AggregateWrapper
60
+ | FunctionCallWrapper
61
+ | Parenthetical
62
+ )
63
+ output: Concept # this has to be a full concept, as it may not exist in environment
64
+ modifiers: List[Modifier] = Field(default_factory=list)
65
+
66
+ def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
67
+ return ConceptTransform(
68
+ function=self.function.with_merge(source, target, modifiers),
69
+ output=self.output.with_merge(source, target, modifiers),
70
+ modifiers=self.modifiers + modifiers,
71
+ )
72
+
73
+ def with_namespace(self, namespace: str) -> "ConceptTransform":
74
+ return ConceptTransform(
75
+ function=self.function.with_namespace(namespace),
76
+ output=self.output.with_namespace(namespace),
77
+ modifiers=self.modifiers,
78
+ )
79
+
80
+
81
+ class SelectItem(BaseModel):
82
+ content: Union[ConceptTransform, ConceptRef]
83
+ modifiers: List[Modifier] = Field(default_factory=list)
84
+
85
+ @field_validator("content", mode="before")
86
+ def parse_content(cls, v):
87
+ if isinstance(v, Concept):
88
+ return v.reference
89
+ return v
90
+
91
+ @property
92
+ def concept(self) -> ConceptRef:
93
+ if isinstance(self.content, (ConceptRef)):
94
+ return self.content
95
+ elif isinstance(self.content, Concept):
96
+ return self.content.reference
97
+ return self.content.output.reference
98
+
99
+ @property
100
+ def is_undefined(self) -> bool:
101
+ return True if isinstance(self.content, UndefinedConcept) else False
102
+
103
+
104
+ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
105
+ selection: List[SelectItem]
106
+ order_by: Optional[OrderBy] = None
107
+ limit: Optional[int] = None
108
+ meta: Metadata = Field(default_factory=lambda: Metadata())
109
+ local_concepts: Annotated[
110
+ EnvironmentConceptDict, PlainValidator(validate_concepts)
111
+ ] = Field(default_factory=EnvironmentConceptDict)
112
+ grain: Grain = Field(default_factory=Grain)
113
+
114
+ def as_lineage(self, environment: Environment) -> SelectLineage:
115
+ derived = [
116
+ x.concept.address
117
+ for x in self.selection
118
+ if isinstance(x.content, ConceptTransform)
119
+ ]
120
+ return SelectLineage(
121
+ selection=[
122
+ environment.concepts[x.concept.address].reference
123
+ for x in self.selection
124
+ ],
125
+ order_by=self.order_by,
126
+ limit=self.limit,
127
+ where_clause=self.where_clause,
128
+ having_clause=self.having_clause,
129
+ local_concepts={
130
+ k: v for k, v in self.local_concepts.items() if k in derived
131
+ },
132
+ hidden_components=self.hidden_components,
133
+ grain=self.grain,
134
+ )
135
+
136
+ @classmethod
137
+ def from_inputs(
138
+ cls,
139
+ environment: Environment,
140
+ selection: List[SelectItem],
141
+ order_by: OrderBy | None = None,
142
+ limit: int | None = None,
143
+ meta: Metadata | None = None,
144
+ where_clause: WhereClause | None = None,
145
+ having_clause: HavingClause | None = None,
146
+ ) -> "SelectStatement":
147
+ output = SelectStatement(
148
+ selection=selection,
149
+ where_clause=where_clause,
150
+ having_clause=having_clause,
151
+ limit=limit,
152
+ order_by=order_by,
153
+ meta=meta or Metadata(),
154
+ )
155
+
156
+ output.grain = output.calculate_grain(environment, output.local_concepts)
157
+
158
+ for x in selection:
159
+ if x.is_undefined and environment.concepts.fail_on_missing:
160
+ environment.concepts.raise_undefined(
161
+ x.concept.address, meta.line_number if meta else None
162
+ )
163
+ elif isinstance(x.content, ConceptTransform):
164
+ if isinstance(x.content.output, UndefinedConcept):
165
+ continue
166
+ if CONFIG.parsing.select_as_definition and not environment.frozen:
167
+ if x.concept.address not in environment.concepts:
168
+ environment.add_concept(x.content.output, add_derived=False)
169
+ elif x.concept.address in environment.concepts:
170
+ version = environment.concepts[x.concept.address]
171
+ if version.metadata.concept_source == ConceptSource.SELECT:
172
+ environment.add_concept(
173
+ x.content.output, force=True, add_derived=False
174
+ )
175
+ x.content.output = x.content.output.set_select_grain(
176
+ output.grain, environment
177
+ )
178
+ # we might not need this
179
+ output.local_concepts[x.content.output.address] = x.content.output
180
+
181
+ elif isinstance(x.content, ConceptRef):
182
+ output.local_concepts[x.content.address] = environment.concepts[
183
+ x.content.address
184
+ ]
185
+ output.grain = output.calculate_grain(environment, output.local_concepts)
186
+ output.validate_syntax(environment)
187
+ return output
188
+
189
+ def calculate_grain(
190
+ self,
191
+ environment: Environment | None = None,
192
+ local_concepts: dict[str, Concept] | None = None,
193
+ ) -> Grain:
194
+ targets = []
195
+ for x in self.selection:
196
+ targets.append(x.concept)
197
+
198
+ result = Grain.from_concepts(
199
+ targets,
200
+ where_clause=self.where_clause,
201
+ environment=environment,
202
+ local_concepts=local_concepts,
203
+ )
204
+ return result
205
+
206
+ def validate_syntax(self, environment: Environment):
207
+ if self.where_clause:
208
+ for x in self.where_clause.concept_arguments:
209
+ if isinstance(x, UndefinedConcept):
210
+ validate = environment.concepts.get(x.address)
211
+ if validate and self.where_clause:
212
+ self.where_clause = (
213
+ self.where_clause.with_reference_replacement(
214
+ x.address, validate.reference
215
+ )
216
+ )
217
+ else:
218
+ environment.concepts.raise_undefined(
219
+ x.address, x.metadata.line_number if x.metadata else None
220
+ )
221
+ all_in_output = [x for x in self.output_components]
222
+ if self.where_clause:
223
+ for cref in self.where_clause.concept_arguments:
224
+ concept = environment.concepts[cref.address]
225
+ if isinstance(concept, UndefinedConcept):
226
+ continue
227
+ if (
228
+ concept.lineage
229
+ and isinstance(concept.lineage, Function)
230
+ and concept.lineage.operator
231
+ in FunctionClass.AGGREGATE_FUNCTIONS.value
232
+ ):
233
+ if concept.address in self.locally_derived:
234
+ raise SyntaxError(
235
+ f"Cannot reference an aggregate derived in the select ({concept.address}) in the same statement where clause; move to the HAVING clause instead; Line: {self.meta.line_number}"
236
+ )
237
+
238
+ if (
239
+ concept.lineage
240
+ and isinstance(concept.lineage, AggregateWrapper)
241
+ and concept.lineage.function.operator
242
+ in FunctionClass.AGGREGATE_FUNCTIONS.value
243
+ ):
244
+ if concept.address in self.locally_derived:
245
+ raise SyntaxError(
246
+ f"Cannot reference an aggregate derived in the select ({concept.address}) in the same statement where clause; move to the HAVING clause instead; Line: {self.meta.line_number}"
247
+ )
248
+ if self.having_clause:
249
+ for cref in self.having_clause.concept_arguments:
250
+ if cref.address not in [x for x in self.output_components]:
251
+ raise SyntaxError(
252
+ f"Cannot reference a column ({cref.address}) that is not in the select projection in the HAVING clause, move to WHERE; Line: {self.meta.line_number}"
253
+ )
254
+ if self.order_by:
255
+ for cref in self.order_by.concept_arguments:
256
+ if cref.address not in all_in_output:
257
+ raise SyntaxError(
258
+ f"Cannot order by column {cref.address} that is not in the output projection; line: {self.meta.line_number}"
259
+ )
260
+
261
+ def __str__(self):
262
+ from trilogy.parsing.render import render_query
263
+
264
+ return render_query(self)
265
+
266
+ @field_validator("selection", mode="before")
267
+ @classmethod
268
+ def selection_validation(cls, v):
269
+ new = []
270
+ for item in v:
271
+ if isinstance(item, (Concept, ConceptTransform)):
272
+ new.append(SelectItem(content=item))
273
+ else:
274
+ new.append(item)
275
+ return new
276
+
277
+ @property
278
+ def locally_derived(self) -> set[str]:
279
+ locally_derived: set[str] = set()
280
+ for item in self.selection:
281
+ if isinstance(item.content, ConceptTransform):
282
+ locally_derived.add(item.concept.address)
283
+ return locally_derived
284
+
285
+ @property
286
+ def output_components(self) -> List[ConceptRef]:
287
+ return [x.concept for x in self.selection]
288
+
289
+ @property
290
+ def hidden_components(self) -> set[str]:
291
+ return set(
292
+ x.concept.address for x in self.selection if Modifier.HIDDEN in x.modifiers
293
+ )
294
+
295
+ def to_datasource(
296
+ self,
297
+ namespace: str,
298
+ name: str,
299
+ address: Address,
300
+ environment: Environment,
301
+ grain: Grain | None = None,
302
+ ) -> Datasource:
303
+ if self.where_clause or self.having_clause:
304
+ modifiers = [Modifier.PARTIAL]
305
+ else:
306
+ modifiers = []
307
+ columns = [
308
+ # TODO: replace hardcoded replacement here
309
+ # if the concept is a locally derived concept, it cannot ever be partial
310
+ # but if it's a concept pulled in from upstream and we have a where clause, it should be partial
311
+ ColumnAssignment(
312
+ alias=(
313
+ c.address.replace(".", "_")
314
+ if c.namespace != DEFAULT_NAMESPACE
315
+ else c.name
316
+ ),
317
+ concept=environment.concepts[c.address].reference,
318
+ modifiers=modifiers if c.address not in self.locally_derived else [],
319
+ )
320
+ for c in self.output_components
321
+ ]
322
+
323
+ condition = None
324
+ if self.where_clause:
325
+ condition = self.where_clause.conditional
326
+ if self.having_clause:
327
+ if condition:
328
+ condition = self.having_clause.conditional + condition
329
+ else:
330
+ condition = self.having_clause.conditional
331
+
332
+ new_datasource = Datasource(
333
+ name=name,
334
+ address=address,
335
+ grain=grain or self.grain,
336
+ columns=columns,
337
+ namespace=namespace,
338
+ non_partial_for=WhereClause(conditional=condition) if condition else None,
339
+ )
340
+ return new_datasource
341
+
342
+
343
+ class RawSQLStatement(BaseModel):
344
+ text: str
345
+ meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
346
+
347
+
348
+ class CopyStatement(BaseModel):
349
+ target: str
350
+ target_type: IOType
351
+ meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
352
+ select: SelectStatement
353
+
354
+
355
+ class MultiSelectStatement(HasUUID, SelectTypeMixin, BaseModel):
356
+ selects: List[SelectStatement]
357
+ align: AlignClause
358
+ namespace: str
359
+ derived_concepts: List[Concept]
360
+ order_by: Optional[OrderBy] = None
361
+ limit: Optional[int] = None
362
+ meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
363
+ local_concepts: Annotated[
364
+ EnvironmentConceptDict, PlainValidator(validate_concepts)
365
+ ] = Field(default_factory=EnvironmentConceptDict)
366
+ derive: DeriveClause | None = None
367
+
368
+ def as_lineage(self, environment: Environment):
369
+ return MultiSelectLineage(
370
+ selects=[x.as_lineage(environment) for x in self.selects],
371
+ align=self.align,
372
+ derive=self.derive,
373
+ namespace=self.namespace,
374
+ # derived_concepts = self.derived_concepts,
375
+ limit=self.limit,
376
+ order_by=self.order_by,
377
+ where_clause=self.where_clause,
378
+ having_clause=self.having_clause,
379
+ hidden_components=self.hidden_components,
380
+ )
381
+
382
+ def __repr__(self):
383
+ return "MultiSelect<" + " MERGE ".join([str(s) for s in self.selects]) + ">"
384
+
385
+ @property
386
+ def grain(self):
387
+ base = Grain()
388
+ for select in self.selects:
389
+ base += select.grain
390
+ return base
391
+
392
+ @property
393
+ def output_components(self) -> List[ConceptRef]:
394
+ output = [x.reference for x in self.derived_concepts]
395
+ for select in self.selects:
396
+ output += [
397
+ x
398
+ for x in select.output_components
399
+ if x.address not in select.hidden_components
400
+ ]
401
+ return unique(output, "address")
402
+
403
+ @computed_field # type: ignore
404
+ @cached_property
405
+ def hidden_components(self) -> set[str]:
406
+ output: set[str] = set()
407
+ for select in self.selects:
408
+ output = output.union(select.hidden_components)
409
+ return output
410
+
411
+ @property
412
+ def locally_derived(self) -> set[str]:
413
+ locally_derived: set[str] = set([x.address for x in self.derived_concepts])
414
+ for select in self.selects:
415
+ locally_derived = locally_derived.union(select.locally_derived)
416
+ return locally_derived
417
+
418
+
419
+ class RowsetDerivationStatement(HasUUID, BaseModel):
420
+ name: str
421
+ select: SelectStatement | MultiSelectStatement
422
+ namespace: str
423
+
424
+ def __repr__(self):
425
+ return f"RowsetDerivation<{str(self.select)}>"
426
+
427
+ def __str__(self):
428
+ return self.__repr__()
429
+
430
+
431
+ class MergeStatementV2(HasUUID, BaseModel):
432
+ sources: list[Concept]
433
+ targets: dict[str, Concept]
434
+ source_wildcard: str | None = None
435
+ target_wildcard: str | None = None
436
+ modifiers: List[Modifier] = Field(default_factory=list)
437
+
438
+
439
+ class KeyMergeStatement(HasUUID, BaseModel):
440
+ keys: set[str]
441
+ target: ConceptRef
442
+
443
+
444
+ class ImportStatement(HasUUID, BaseModel):
445
+ # import abc.def as bar
446
+ # the bit after 'as', eg bar
447
+ alias: str
448
+ # the bit after import, abc.def
449
+ input_path: str
450
+ # what it actually resolves to, typically a filepath
451
+ path: Path
452
+
453
+
454
+ class PersistStatement(HasUUID, BaseModel):
455
+ datasource: Datasource
456
+ select: SelectStatement
457
+ persist_mode: PersistMode = PersistMode.OVERWRITE
458
+ partition_by: List[ConceptRef] = Field(default_factory=list)
459
+ meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
460
+
461
+ @property
462
+ def identifier(self):
463
+ return self.datasource.identifier
464
+
465
+ @property
466
+ def address(self):
467
+ return self.datasource.address
468
+
469
+
470
+ class ValidateStatement(BaseModel):
471
+ scope: ValidationScope
472
+ targets: list[str] | None = None
473
+
474
+
475
+ class MockStatement(BaseModel):
476
+ scope: ValidationScope
477
+ targets: list[str]
478
+
479
+
480
+ class PublishStatement(BaseModel):
481
+ scope: ValidationScope
482
+ targets: list[str]
483
+ action: PublishAction = PublishAction.PUBLISH
484
+
485
+
486
+ class CreateStatement(BaseModel):
487
+ scope: ValidationScope
488
+ create_mode: CreateMode = CreateMode.CREATE_OR_REPLACE
489
+ targets: list[str]
490
+
491
+
492
+ class ShowStatement(BaseModel):
493
+ content: SelectStatement | PersistStatement | ValidateStatement | ShowCategory
494
+
495
+
496
+ class Limit(BaseModel):
497
+ count: int
498
+
499
+
500
+ class ConceptDeclarationStatement(HasUUID, BaseModel):
501
+ concept: Concept
502
+
503
+
504
+ class ConceptDerivationStatement(BaseModel):
505
+ concept: Concept
506
+
507
+
508
+ class TypeDeclaration(BaseModel):
509
+ type: CustomType
510
+
511
+
512
+ class FunctionDeclaration(HasUUID, BaseModel):
513
+ name: str
514
+ args: list[ArgBinding]
515
+ expr: Expr
516
+
517
+
518
+ STATEMENT_TYPES = (
519
+ SelectStatement
520
+ | RawSQLStatement
521
+ | CopyStatement
522
+ | MultiSelectStatement
523
+ | RowsetDerivationStatement
524
+ | MergeStatementV2
525
+ | KeyMergeStatement
526
+ | ImportStatement
527
+ | PersistStatement
528
+ | ValidateStatement
529
+ | PublishStatement
530
+ | CreateStatement
531
+ | ShowStatement
532
+ | ConceptDeclarationStatement
533
+ | ConceptDerivationStatement
534
+ | TypeDeclaration
535
+ | FunctionDeclaration
536
+ )
File without changes
@@ -0,0 +1,20 @@
1
+ from typing import List, Union
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from trilogy.core.enums import IOType
6
+ from trilogy.core.models.author import ConceptRef, HavingClause, WhereClause
7
+
8
+
9
+ class CopyQueryMixin(BaseModel):
10
+ target: str
11
+ target_type: IOType
12
+
13
+
14
+ class SelectTypeMixin(BaseModel):
15
+ where_clause: Union["WhereClause", None] = Field(default=None)
16
+ having_clause: Union["HavingClause", None] = Field(default=None)
17
+
18
+ @property
19
+ def output_components(self) -> List[ConceptRef]:
20
+ raise NotImplementedError
@@ -0,0 +1,155 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import List, Optional, Union
3
+
4
+ from trilogy.core.enums import (
5
+ CreateMode,
6
+ IOType,
7
+ PersistMode,
8
+ PublishAction,
9
+ ValidationScope,
10
+ )
11
+ from trilogy.core.models.author import ConceptRef, HavingClause, WhereClause
12
+ from trilogy.core.models.build import (
13
+ BuildConcept,
14
+ BuildDatasource,
15
+ BuildOrderBy,
16
+ )
17
+ from trilogy.core.models.core import DataType
18
+ from trilogy.core.models.datasource import Address, Datasource
19
+ from trilogy.core.models.environment import EnvironmentConceptDict
20
+ from trilogy.core.models.execute import CTE, UnionCTE
21
+
22
+
23
+ @dataclass
24
+ class CopyQueryMixin:
25
+ target: str
26
+ target_type: IOType
27
+
28
+
29
+ @dataclass
30
+ class MaterializedDataset:
31
+ address: Address
32
+
33
+
34
+ @dataclass
35
+ class PersistQueryMixin:
36
+ output_to: MaterializedDataset
37
+ datasource: Datasource
38
+ persist_mode: PersistMode
39
+ partition_by: List[str]
40
+ partition_types: List[DataType]
41
+
42
+
43
+ @dataclass
44
+ class SelectTypeMixin:
45
+ where_clause: Union["WhereClause", None] = field(default=None)
46
+ having_clause: Union["HavingClause", None] = field(default=None)
47
+
48
+ @property
49
+ def output_components(self) -> List[ConceptRef]:
50
+ raise NotImplementedError
51
+
52
+
53
+ @dataclass
54
+ class ProcessedQuery:
55
+ output_columns: List[ConceptRef]
56
+ ctes: List[CTE | UnionCTE]
57
+ base: CTE | UnionCTE
58
+ hidden_columns: set[str] = field(default_factory=set)
59
+ limit: Optional[int] = None
60
+ order_by: Optional[BuildOrderBy] = None
61
+ local_concepts: EnvironmentConceptDict = field(
62
+ default_factory=EnvironmentConceptDict
63
+ )
64
+ locally_derived: set[str] = field(default_factory=set)
65
+
66
+
67
+ @dataclass
68
+ class ProcessedQueryPersist(ProcessedQuery, PersistQueryMixin):
69
+ pass
70
+
71
+
72
+ @dataclass
73
+ class ProcessedCopyStatement(ProcessedQuery, CopyQueryMixin):
74
+ pass
75
+
76
+
77
+ @dataclass
78
+ class ProcessedRawSQLStatement:
79
+ text: str
80
+
81
+
82
+ @dataclass
83
+ class ProcessedValidateStatement:
84
+ scope: ValidationScope
85
+ targets: Optional[List[str]]
86
+
87
+
88
+ @dataclass
89
+ class ProcessedMockStatement:
90
+ scope: ValidationScope
91
+ targets: list[str]
92
+
93
+
94
+ @dataclass
95
+ class ColumnInfo:
96
+ name: str
97
+ type: DataType
98
+ nullable: bool = True
99
+ primary_key: bool = False
100
+ description: Optional[str] = None
101
+
102
+
103
+ @dataclass
104
+ class CreateTableInfo:
105
+ name: str
106
+ columns: List[ColumnInfo]
107
+ partition_keys: list[str] = field(default_factory=list)
108
+
109
+
110
+ @dataclass
111
+ class ProcessedCreateStatement:
112
+ scope: ValidationScope
113
+ create_mode: CreateMode
114
+ targets: list[CreateTableInfo]
115
+
116
+
117
+ @dataclass
118
+ class ProcessedPublishStatement:
119
+ scope: ValidationScope
120
+ targets: list[str]
121
+ action: PublishAction
122
+
123
+
124
+ @dataclass
125
+ class ProcessedStaticValueOutput:
126
+ values: List[dict]
127
+
128
+
129
+ @dataclass
130
+ class ProcessedShowStatement:
131
+ output_columns: List[ConceptRef]
132
+ output_values: List[
133
+ Union[
134
+ BuildConcept,
135
+ BuildDatasource,
136
+ ProcessedQuery,
137
+ ProcessedQueryPersist,
138
+ ProcessedCopyStatement,
139
+ ProcessedValidateStatement,
140
+ ProcessedStaticValueOutput,
141
+ ]
142
+ ]
143
+
144
+
145
+ PROCESSED_STATEMENT_TYPES = (
146
+ ProcessedCopyStatement
147
+ | ProcessedQuery
148
+ | ProcessedRawSQLStatement
149
+ | ProcessedQueryPersist
150
+ | ProcessedShowStatement
151
+ | ProcessedValidateStatement
152
+ | ProcessedCreateStatement
153
+ | ProcessedPublishStatement
154
+ | ProcessedMockStatement
155
+ )