pytrilogy 0.3.142__cp313-cp313-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 (200) hide show
  1. LICENSE.md +19 -0
  2. _preql_import_resolver/__init__.py +5 -0
  3. _preql_import_resolver/_preql_import_resolver.cpython-313-x86_64-linux-gnu.so +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 +5 -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,322 @@
1
+ from typing import TYPE_CHECKING, ItemsView, List, Optional, Union, ValuesView
2
+
3
+ from pydantic import BaseModel, Field, ValidationInfo, field_validator
4
+
5
+ from trilogy.constants import DEFAULT_NAMESPACE, logger
6
+ from trilogy.core.enums import DatasourceState, Modifier
7
+ from trilogy.core.models.author import (
8
+ Concept,
9
+ ConceptRef,
10
+ Function,
11
+ Grain,
12
+ HasUUID,
13
+ LooseConceptList,
14
+ Namespaced,
15
+ WhereClause,
16
+ )
17
+
18
+ LOGGER_PREFIX = "[MODELS_DATASOURCE]"
19
+
20
+ if TYPE_CHECKING:
21
+ pass
22
+
23
+
24
+ class RawColumnExpr(BaseModel):
25
+ text: str
26
+
27
+
28
+ class ColumnAssignment(BaseModel):
29
+ alias: str | RawColumnExpr | Function
30
+ concept: ConceptRef
31
+ modifiers: List[Modifier] = Field(default_factory=list)
32
+
33
+ @field_validator("concept", mode="before")
34
+ def force_reference(cls, v: ConceptRef, info: ValidationInfo):
35
+ if isinstance(v, Concept):
36
+ return v.reference
37
+ return v
38
+
39
+ def __eq__(self, other):
40
+ if not isinstance(other, ColumnAssignment):
41
+ return False
42
+ return (
43
+ self.alias == other.alias
44
+ and self.concept == other.concept
45
+ and self.modifiers == other.modifiers
46
+ )
47
+
48
+ @property
49
+ def is_concrete(self) -> bool:
50
+ return isinstance(self.alias, str)
51
+
52
+ @property
53
+ def is_complete(self) -> bool:
54
+ return Modifier.PARTIAL not in self.modifiers
55
+
56
+ @property
57
+ def is_nullable(self) -> bool:
58
+ return Modifier.NULLABLE in self.modifiers
59
+
60
+ def with_namespace(self, namespace: str) -> "ColumnAssignment":
61
+ return ColumnAssignment.model_construct(
62
+ alias=(
63
+ self.alias.with_namespace(namespace)
64
+ if isinstance(self.alias, Function)
65
+ else self.alias
66
+ ),
67
+ concept=self.concept.with_namespace(namespace),
68
+ modifiers=self.modifiers,
69
+ )
70
+
71
+ def with_merge(
72
+ self, source: Concept, target: Concept, modifiers: List[Modifier]
73
+ ) -> "ColumnAssignment":
74
+ return ColumnAssignment.model_construct(
75
+ alias=self.alias,
76
+ concept=self.concept.with_merge(source, target, modifiers),
77
+ modifiers=(
78
+ modifiers if self.concept.address == source.address else self.modifiers
79
+ ),
80
+ )
81
+
82
+
83
+ class Address(BaseModel):
84
+ location: str
85
+ is_query: bool = False
86
+ quoted: bool = False
87
+
88
+
89
+ class Query(BaseModel):
90
+ text: str
91
+
92
+
93
+ class DatasourceMetadata(BaseModel):
94
+ freshness_concept: Concept | None
95
+ partition_fields: List[Concept] = Field(default_factory=list)
96
+ line_no: int | None = None
97
+
98
+
99
+ def safe_grain(v) -> Grain:
100
+ if isinstance(v, dict):
101
+ return Grain.model_validate(v)
102
+ elif isinstance(v, Grain):
103
+ return v
104
+ elif not v:
105
+ return Grain(components=set())
106
+ else:
107
+ raise ValueError(f"Invalid input type to safe_grain {type(v)}")
108
+
109
+
110
+ class Datasource(HasUUID, Namespaced, BaseModel):
111
+ name: str
112
+ columns: List[ColumnAssignment]
113
+ address: Union[Address, str]
114
+ grain: Grain = Field(
115
+ default_factory=lambda: Grain(components=set()), validate_default=True
116
+ )
117
+ namespace: Optional[str] = Field(default=DEFAULT_NAMESPACE, validate_default=True)
118
+ metadata: DatasourceMetadata = Field(
119
+ default_factory=lambda: DatasourceMetadata(freshness_concept=None)
120
+ )
121
+ where: Optional[WhereClause] = None
122
+ non_partial_for: Optional[WhereClause] = None
123
+ status: DatasourceState = Field(default=DatasourceState.PUBLISHED)
124
+ incremental_by: List[ConceptRef] = Field(default_factory=list)
125
+ partition_by: List[ConceptRef] = Field(default_factory=list)
126
+
127
+ @property
128
+ def safe_address(self) -> str:
129
+ if isinstance(self.address, Address):
130
+ return self.address.location
131
+ return self.address
132
+
133
+ def __eq__(self, other):
134
+ if not isinstance(other, Datasource):
135
+ return False
136
+ return (
137
+ self.name == other.name
138
+ and self.namespace == other.namespace
139
+ and self.grain == other.grain
140
+ and self.address == other.address
141
+ and self.where == other.where
142
+ and self.columns == other.columns
143
+ and self.non_partial_for == other.non_partial_for
144
+ )
145
+
146
+ def duplicate(self) -> "Datasource":
147
+ return self.model_copy(deep=True)
148
+
149
+ @property
150
+ def concrete_columns(self) -> dict[str, ColumnAssignment]:
151
+ return {c.alias: c for c in self.columns if c.is_concrete} # type: ignore[misc]
152
+
153
+ @property
154
+ def hidden_concepts(self) -> List[Concept]:
155
+ return []
156
+
157
+ def merge_concept(
158
+ self, source: Concept, target: Concept, modifiers: List[Modifier]
159
+ ):
160
+ original = [c for c in self.columns if c.concept.address == source.address]
161
+ early_exit_check = [
162
+ c for c in self.columns if c.concept.address == target.address
163
+ ]
164
+ if early_exit_check:
165
+ logger.info(
166
+ f"No concept merge needed on merge of {source} to {target}, have {[x.concept.address for x in self.columns]}"
167
+ )
168
+ return None
169
+ if len(original) != 1:
170
+ raise ValueError(
171
+ f"Expected exactly one column to merge, got {len(original)} for {source.address}, {[x.alias for x in original]}"
172
+ )
173
+ # map to the alias with the modifier, and the original
174
+ self.columns = [
175
+ c.with_merge(source, target, modifiers)
176
+ for c in self.columns
177
+ if c.concept.address != source.address
178
+ ] + original
179
+ self.grain = self.grain.with_merge(source, target, modifiers)
180
+ self.where = (
181
+ self.where.with_merge(source, target, modifiers) if self.where else None
182
+ )
183
+
184
+ self.add_column(target, original[0].alias, modifiers)
185
+
186
+ @property
187
+ def identifier(self) -> str:
188
+ if not self.namespace or self.namespace == DEFAULT_NAMESPACE:
189
+ return self.name
190
+ return f"{self.namespace}.{self.name}"
191
+
192
+ @property
193
+ def safe_identifier(self) -> str:
194
+ return self.identifier.replace(".", "_")
195
+
196
+ @property
197
+ def output_lcl(self) -> LooseConceptList:
198
+ return LooseConceptList(concepts=self.output_concepts)
199
+
200
+ @property
201
+ def non_partial_concept_addresses(self) -> set[str]:
202
+ return set([c.address for c in self.full_concepts])
203
+
204
+ @field_validator("namespace", mode="plain")
205
+ @classmethod
206
+ def namespace_validation(cls, v):
207
+ return v or DEFAULT_NAMESPACE
208
+
209
+ @field_validator("address")
210
+ @classmethod
211
+ def address_enforcement(cls, v):
212
+ if isinstance(v, str):
213
+ v = Address(location=v)
214
+ return v
215
+
216
+ @field_validator("grain", mode="before")
217
+ @classmethod
218
+ def grain_enforcement(cls, v: Grain, info: ValidationInfo):
219
+ grain: Grain = safe_grain(v)
220
+ return grain
221
+
222
+ def add_column(
223
+ self,
224
+ concept: Concept,
225
+ alias: str | RawColumnExpr | Function,
226
+ modifiers: List[Modifier] | None = None,
227
+ ):
228
+ self.columns.append(
229
+ ColumnAssignment(
230
+ alias=alias, concept=concept.reference, modifiers=modifiers or []
231
+ )
232
+ )
233
+
234
+ def __add__(self, other):
235
+ if not other == self:
236
+ raise ValueError(
237
+ "Attempted to add two datasources that are not identical, this is not a valid operation"
238
+ )
239
+ return self
240
+
241
+ def __repr__(self):
242
+ return f"Datasource<{self.identifier}@<{self.grain}>"
243
+
244
+ def __str__(self):
245
+ return self.__repr__()
246
+
247
+ def __hash__(self):
248
+ return self.identifier.__hash__()
249
+
250
+ def with_namespace(self, namespace: str):
251
+ new_namespace = (
252
+ namespace + "." + self.namespace
253
+ if self.namespace and self.namespace != DEFAULT_NAMESPACE
254
+ else namespace
255
+ )
256
+ new = Datasource.model_construct(
257
+ name=self.name,
258
+ namespace=new_namespace,
259
+ grain=self.grain.with_namespace(namespace),
260
+ address=self.address,
261
+ columns=[c.with_namespace(namespace) for c in self.columns],
262
+ where=self.where.with_namespace(namespace) if self.where else None,
263
+ non_partial_for=(
264
+ self.non_partial_for.with_namespace(namespace)
265
+ if self.non_partial_for
266
+ else None
267
+ ),
268
+ status=self.status,
269
+ incremental_by=[c.with_namespace(namespace) for c in self.incremental_by],
270
+ partition_by=[c.with_namespace(namespace) for c in self.partition_by],
271
+ )
272
+ return new
273
+
274
+ @property
275
+ def concepts(self) -> List[ConceptRef]:
276
+ return [c.concept for c in self.columns]
277
+
278
+ @property
279
+ def group_required(self):
280
+ return False
281
+
282
+ @property
283
+ def full_concepts(self) -> List[ConceptRef]:
284
+ return [c.concept for c in self.columns if Modifier.PARTIAL not in c.modifiers]
285
+
286
+ @property
287
+ def nullable_concepts(self) -> List[ConceptRef]:
288
+ return [c.concept for c in self.columns if Modifier.NULLABLE in c.modifiers]
289
+
290
+ @property
291
+ def output_concepts(self) -> List[ConceptRef]:
292
+ return self.concepts
293
+
294
+ @property
295
+ def partial_concepts(self) -> List[ConceptRef]:
296
+ return [c.concept for c in self.columns if Modifier.PARTIAL in c.modifiers]
297
+
298
+
299
+ class EnvironmentDatasourceDict(dict):
300
+ def __init__(self, *args, **kwargs) -> None:
301
+ super().__init__(self, *args, **kwargs)
302
+
303
+ def __getitem__(self, key: str) -> Datasource:
304
+ try:
305
+ return super(EnvironmentDatasourceDict, self).__getitem__(key)
306
+ except KeyError:
307
+ if DEFAULT_NAMESPACE + "." + key in self:
308
+ return self.__getitem__(DEFAULT_NAMESPACE + "." + key)
309
+ if "." in key and key.split(".", 1)[0] == DEFAULT_NAMESPACE:
310
+ return self.__getitem__(key.split(".", 1)[1])
311
+ raise
312
+
313
+ def values(self) -> ValuesView[Datasource]: # type: ignore
314
+ return super().values()
315
+
316
+ def items(self) -> ItemsView[str, Datasource]: # type: ignore
317
+ return super().items()
318
+
319
+ def duplicate(self) -> "EnvironmentDatasourceDict":
320
+ new = EnvironmentDatasourceDict()
321
+ new.update({k: v.duplicate() for k, v in self.items()})
322
+ return new