pytrilogy 0.0.1.102__py3-none-any.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.

Potentially problematic release.


This version of pytrilogy might be problematic. Click here for more details.

Files changed (77) hide show
  1. pytrilogy-0.0.1.102.dist-info/LICENSE.md +19 -0
  2. pytrilogy-0.0.1.102.dist-info/METADATA +277 -0
  3. pytrilogy-0.0.1.102.dist-info/RECORD +77 -0
  4. pytrilogy-0.0.1.102.dist-info/WHEEL +5 -0
  5. pytrilogy-0.0.1.102.dist-info/entry_points.txt +2 -0
  6. pytrilogy-0.0.1.102.dist-info/top_level.txt +1 -0
  7. trilogy/__init__.py +8 -0
  8. trilogy/compiler.py +0 -0
  9. trilogy/constants.py +30 -0
  10. trilogy/core/__init__.py +0 -0
  11. trilogy/core/constants.py +3 -0
  12. trilogy/core/enums.py +270 -0
  13. trilogy/core/env_processor.py +33 -0
  14. trilogy/core/environment_helpers.py +156 -0
  15. trilogy/core/ergonomics.py +187 -0
  16. trilogy/core/exceptions.py +23 -0
  17. trilogy/core/functions.py +320 -0
  18. trilogy/core/graph_models.py +55 -0
  19. trilogy/core/internal.py +37 -0
  20. trilogy/core/models.py +3145 -0
  21. trilogy/core/processing/__init__.py +0 -0
  22. trilogy/core/processing/concept_strategies_v3.py +603 -0
  23. trilogy/core/processing/graph_utils.py +44 -0
  24. trilogy/core/processing/node_generators/__init__.py +25 -0
  25. trilogy/core/processing/node_generators/basic_node.py +71 -0
  26. trilogy/core/processing/node_generators/common.py +239 -0
  27. trilogy/core/processing/node_generators/concept_merge.py +152 -0
  28. trilogy/core/processing/node_generators/filter_node.py +83 -0
  29. trilogy/core/processing/node_generators/group_node.py +92 -0
  30. trilogy/core/processing/node_generators/group_to_node.py +99 -0
  31. trilogy/core/processing/node_generators/merge_node.py +148 -0
  32. trilogy/core/processing/node_generators/multiselect_node.py +189 -0
  33. trilogy/core/processing/node_generators/rowset_node.py +130 -0
  34. trilogy/core/processing/node_generators/select_node.py +328 -0
  35. trilogy/core/processing/node_generators/unnest_node.py +37 -0
  36. trilogy/core/processing/node_generators/window_node.py +85 -0
  37. trilogy/core/processing/nodes/__init__.py +76 -0
  38. trilogy/core/processing/nodes/base_node.py +251 -0
  39. trilogy/core/processing/nodes/filter_node.py +49 -0
  40. trilogy/core/processing/nodes/group_node.py +110 -0
  41. trilogy/core/processing/nodes/merge_node.py +326 -0
  42. trilogy/core/processing/nodes/select_node_v2.py +198 -0
  43. trilogy/core/processing/nodes/unnest_node.py +54 -0
  44. trilogy/core/processing/nodes/window_node.py +34 -0
  45. trilogy/core/processing/utility.py +278 -0
  46. trilogy/core/query_processor.py +331 -0
  47. trilogy/dialect/__init__.py +0 -0
  48. trilogy/dialect/base.py +679 -0
  49. trilogy/dialect/bigquery.py +80 -0
  50. trilogy/dialect/common.py +43 -0
  51. trilogy/dialect/config.py +55 -0
  52. trilogy/dialect/duckdb.py +83 -0
  53. trilogy/dialect/enums.py +95 -0
  54. trilogy/dialect/postgres.py +86 -0
  55. trilogy/dialect/presto.py +82 -0
  56. trilogy/dialect/snowflake.py +82 -0
  57. trilogy/dialect/sql_server.py +89 -0
  58. trilogy/docs/__init__.py +0 -0
  59. trilogy/engine.py +48 -0
  60. trilogy/executor.py +242 -0
  61. trilogy/hooks/__init__.py +0 -0
  62. trilogy/hooks/base_hook.py +37 -0
  63. trilogy/hooks/graph_hook.py +24 -0
  64. trilogy/hooks/query_debugger.py +133 -0
  65. trilogy/metadata/__init__.py +0 -0
  66. trilogy/parser.py +10 -0
  67. trilogy/parsing/__init__.py +0 -0
  68. trilogy/parsing/common.py +176 -0
  69. trilogy/parsing/config.py +5 -0
  70. trilogy/parsing/exceptions.py +2 -0
  71. trilogy/parsing/helpers.py +1 -0
  72. trilogy/parsing/parse_engine.py +1951 -0
  73. trilogy/parsing/render.py +483 -0
  74. trilogy/py.typed +0 -0
  75. trilogy/scripts/__init__.py +0 -0
  76. trilogy/scripts/trilogy.py +127 -0
  77. trilogy/utility.py +31 -0
@@ -0,0 +1,320 @@
1
+ from trilogy.core.models import (
2
+ Function,
3
+ Concept,
4
+ AggregateWrapper,
5
+ Parenthetical,
6
+ arg_to_datatype,
7
+ WindowItem,
8
+ DataType,
9
+ ListType,
10
+ StructType,
11
+ MapType,
12
+ )
13
+ from trilogy.core.enums import FunctionType, Purpose, Granularity, DatePart
14
+ from trilogy.core.exceptions import InvalidSyntaxException
15
+ from trilogy.constants import MagicConstants
16
+ from typing import Optional
17
+
18
+
19
+ def create_function_derived_concept(
20
+ name: str,
21
+ namespace: str,
22
+ operator: FunctionType,
23
+ arguments: list[Concept],
24
+ output_type: Optional[DataType | ListType | StructType | MapType] = None,
25
+ output_purpose: Optional[Purpose] = None,
26
+ ) -> Concept:
27
+ purpose = (
28
+ function_args_to_output_purpose(arguments)
29
+ if output_purpose is None
30
+ else output_purpose
31
+ )
32
+ output_type = arg_to_datatype(arguments[0]) if output_type is None else output_type
33
+ return Concept(
34
+ name=name,
35
+ namespace=namespace,
36
+ datatype=output_type,
37
+ purpose=purpose,
38
+ lineage=Function(
39
+ operator=operator,
40
+ arguments=arguments,
41
+ output_datatype=output_type,
42
+ output_purpose=purpose,
43
+ arg_count=len(arguments),
44
+ ),
45
+ )
46
+
47
+
48
+ def argument_to_purpose(arg) -> Purpose:
49
+ if isinstance(arg, Function):
50
+ return arg.output_purpose
51
+ elif isinstance(arg, AggregateWrapper):
52
+ return arg.function.output_purpose
53
+ elif isinstance(arg, Parenthetical):
54
+ return argument_to_purpose(arg.content)
55
+ elif isinstance(arg, WindowItem):
56
+ return Purpose.PROPERTY
57
+ elif isinstance(arg, Concept):
58
+ return arg.purpose
59
+ elif isinstance(arg, (int, float, str, bool, list)):
60
+ return Purpose.CONSTANT
61
+ elif isinstance(arg, DataType):
62
+ return Purpose.CONSTANT
63
+ elif isinstance(arg, DatePart):
64
+ return Purpose.CONSTANT
65
+ elif isinstance(arg, MagicConstants):
66
+ return Purpose.CONSTANT
67
+ else:
68
+ raise ValueError(f"Cannot parse arg purpose for {arg} of type {type(arg)}")
69
+
70
+
71
+ def function_args_to_output_purpose(args) -> Purpose:
72
+ has_metric = False
73
+ has_non_constant = False
74
+ has_non_single_row_constant = False
75
+ if not args:
76
+ return Purpose.CONSTANT
77
+ for arg in args:
78
+ purpose = argument_to_purpose(arg)
79
+ if purpose == Purpose.METRIC:
80
+ has_metric = True
81
+ if purpose != Purpose.CONSTANT:
82
+ has_non_constant = True
83
+ if isinstance(arg, Concept) and arg.granularity != Granularity.SINGLE_ROW:
84
+ has_non_single_row_constant = True
85
+ if args and not has_non_constant and not has_non_single_row_constant:
86
+ return Purpose.CONSTANT
87
+ if has_metric:
88
+ return Purpose.METRIC
89
+ return Purpose.PROPERTY
90
+
91
+
92
+ def Unnest(args: list[Concept]) -> Function:
93
+ output = arg_to_datatype(args[0])
94
+ if isinstance(output, (ListType)):
95
+ output = output.value_data_type
96
+ return Function(
97
+ operator=FunctionType.UNNEST,
98
+ arguments=args,
99
+ output_datatype=output,
100
+ output_purpose=Purpose.KEY,
101
+ arg_count=1,
102
+ valid_inputs={
103
+ DataType.ARRAY,
104
+ DataType.LIST,
105
+ ListType(type=DataType.STRING),
106
+ ListType(type=DataType.INTEGER),
107
+ },
108
+ )
109
+
110
+
111
+ def Group(args: list[Concept]) -> Function:
112
+ output = args[0]
113
+ return Function(
114
+ operator=FunctionType.GROUP,
115
+ arguments=args,
116
+ output_datatype=output.datatype,
117
+ output_purpose=Purpose.PROPERTY,
118
+ arg_count=-1,
119
+ )
120
+
121
+
122
+ def Count(args: list[Concept]) -> Function:
123
+ return Function(
124
+ operator=FunctionType.COUNT,
125
+ arguments=args,
126
+ output_datatype=DataType.INTEGER,
127
+ output_purpose=Purpose.METRIC,
128
+ arg_count=1,
129
+ )
130
+
131
+
132
+ def CountDistinct(args: list[Concept]) -> Function:
133
+ return Function(
134
+ operator=FunctionType.COUNT_DISTINCT,
135
+ arguments=args,
136
+ output_datatype=DataType.INTEGER,
137
+ output_purpose=Purpose.METRIC,
138
+ arg_count=1,
139
+ )
140
+
141
+
142
+ def Max(args: list[Concept]) -> Function:
143
+ return Function(
144
+ operator=FunctionType.MAX,
145
+ arguments=args,
146
+ output_datatype=args[0].datatype,
147
+ output_purpose=Purpose.METRIC,
148
+ valid_inputs={
149
+ DataType.INTEGER,
150
+ DataType.FLOAT,
151
+ DataType.NUMBER,
152
+ DataType.DATE,
153
+ DataType.DATETIME,
154
+ DataType.TIMESTAMP,
155
+ },
156
+ arg_count=1,
157
+ # output_grain=Grain(components=arguments),
158
+ )
159
+
160
+
161
+ def Min(args: list[Concept]) -> Function:
162
+ return Function(
163
+ operator=FunctionType.MIN,
164
+ arguments=args,
165
+ output_datatype=args[0].datatype,
166
+ output_purpose=Purpose.METRIC,
167
+ valid_inputs={
168
+ DataType.INTEGER,
169
+ DataType.FLOAT,
170
+ DataType.NUMBER,
171
+ DataType.DATE,
172
+ DataType.DATETIME,
173
+ DataType.TIMESTAMP,
174
+ },
175
+ arg_count=1,
176
+ # output_grain=Grain(components=arguments),
177
+ )
178
+
179
+
180
+ def Split(args: list[Concept]) -> Function:
181
+ # TODO: overload this for non-string types?
182
+ return Function(
183
+ operator=FunctionType.SPLIT,
184
+ arguments=args,
185
+ # first arg sets properties
186
+ output_datatype=ListType(type=DataType.STRING),
187
+ output_purpose=function_args_to_output_purpose(args),
188
+ valid_inputs={DataType.STRING},
189
+ arg_count=2,
190
+ )
191
+
192
+
193
+ def IndexAccess(args: list[Concept]):
194
+ return Function(
195
+ operator=FunctionType.INDEX_ACCESS,
196
+ arguments=args,
197
+ output_datatype=(
198
+ args[0].datatype.value_data_type
199
+ if isinstance(args[0].datatype, ListType)
200
+ else args[0].datatype
201
+ ),
202
+ output_purpose=Purpose.PROPERTY,
203
+ valid_inputs=[
204
+ {
205
+ DataType.LIST,
206
+ ListType(type=DataType.STRING),
207
+ ListType(type=DataType.INTEGER),
208
+ },
209
+ {
210
+ DataType.INTEGER,
211
+ },
212
+ ],
213
+ arg_count=2,
214
+ )
215
+
216
+
217
+ def AttrAccess(args: list[Concept]):
218
+ return Function(
219
+ operator=FunctionType.ATTR_ACCESS,
220
+ arguments=args,
221
+ output_datatype=args[0].field_map[args[1]].datatype, # type: ignore
222
+ output_purpose=Purpose.PROPERTY,
223
+ valid_inputs=[
224
+ {DataType.STRUCT},
225
+ {
226
+ DataType.STRING,
227
+ },
228
+ ],
229
+ arg_count=2,
230
+ )
231
+
232
+
233
+ def Abs(args: list[Concept]) -> Function:
234
+ return Function(
235
+ operator=FunctionType.ABS,
236
+ arguments=args,
237
+ output_datatype=args[0].datatype,
238
+ output_purpose=function_args_to_output_purpose(args),
239
+ valid_inputs={
240
+ DataType.INTEGER,
241
+ DataType.FLOAT,
242
+ DataType.NUMBER,
243
+ },
244
+ arg_count=1,
245
+ # output_grain=Grain(components=arguments),
246
+ )
247
+
248
+
249
+ def Coalesce(args: list[Concept]) -> Function:
250
+ non_null = [x for x in args if not x == MagicConstants.NULL]
251
+ if not len(set(arg_to_datatype(x) for x in non_null if x)) == 1:
252
+ raise InvalidSyntaxException(
253
+ f"All arguments to coalesce must be of the same type, have {set(arg_to_datatype(x) for x in args)}"
254
+ )
255
+ return Function(
256
+ operator=FunctionType.COALESCE,
257
+ arguments=args,
258
+ output_datatype=arg_to_datatype(non_null[0]),
259
+ output_purpose=function_args_to_output_purpose(non_null),
260
+ arg_count=-1,
261
+ # output_grain=Grain(components=arguments),
262
+ )
263
+
264
+
265
+ def CurrentDate(args: list[Concept]) -> Function:
266
+ return Function(
267
+ operator=FunctionType.CURRENT_DATE,
268
+ arguments=args,
269
+ output_datatype=DataType.DATE,
270
+ output_purpose=Purpose.CONSTANT,
271
+ arg_count=0,
272
+ # output_grain=Grain(components=arguments),
273
+ )
274
+
275
+
276
+ def CurrentDatetime(args: list[Concept]) -> Function:
277
+ return Function(
278
+ operator=FunctionType.CURRENT_DATETIME,
279
+ arguments=args,
280
+ output_datatype=DataType.DATE,
281
+ output_purpose=Purpose.CONSTANT,
282
+ arg_count=0,
283
+ # output_grain=Grain(components=arguments),
284
+ )
285
+
286
+
287
+ def IsNull(args: list[Concept]) -> Function:
288
+ return Function(
289
+ operator=FunctionType.IS_NULL,
290
+ arguments=args,
291
+ output_datatype=DataType.BOOL,
292
+ output_purpose=function_args_to_output_purpose(args),
293
+ arg_count=1,
294
+ # output_grain=Grain(components=arguments),
295
+ )
296
+
297
+
298
+ def StrPos(args: list[Concept]) -> Function:
299
+ return Function(
300
+ operator=FunctionType.STRPOS,
301
+ arguments=args,
302
+ output_datatype=DataType.INTEGER,
303
+ output_purpose=function_args_to_output_purpose(args),
304
+ arg_count=2,
305
+ valid_inputs=[
306
+ {DataType.STRING},
307
+ {DataType.STRING},
308
+ ],
309
+ )
310
+
311
+
312
+ def SubString(args: list[Concept]) -> Function:
313
+ return Function(
314
+ operator=FunctionType.SUBSTRING,
315
+ arguments=args,
316
+ output_datatype=DataType.STRING,
317
+ output_purpose=function_args_to_output_purpose(args),
318
+ arg_count=3,
319
+ valid_inputs=[{DataType.STRING}, {DataType.INTEGER}, {DataType.INTEGER}],
320
+ )
@@ -0,0 +1,55 @@
1
+ import networkx as nx
2
+
3
+ from trilogy.core.models import Concept, Datasource
4
+
5
+
6
+ def concept_to_node(input: Concept) -> str:
7
+ # if input.purpose == Purpose.METRIC:
8
+ # return f"c~{input.namespace}.{input.name}@{input.grain}"
9
+ return f"c~{input.namespace}.{input.name}@{input.grain}"
10
+
11
+
12
+ def datasource_to_node(input: Datasource) -> str:
13
+ # if isinstance(input, JoinedDataSource):
14
+ # return "ds~join~" + ",".join(
15
+ # [datasource_to_node(sub) for sub in input.datasources]
16
+ # )
17
+ return f"ds~{input.namespace}.{input.identifier}"
18
+
19
+
20
+ class ReferenceGraph(nx.DiGraph):
21
+ def __init__(self, *args, **kwargs):
22
+ super().__init__(*args, **kwargs)
23
+
24
+ def add_node(self, node_for_adding, **attr):
25
+ if isinstance(node_for_adding, Concept):
26
+ node_name = concept_to_node(node_for_adding)
27
+ attr["type"] = "concept"
28
+ attr["concept"] = node_for_adding
29
+ attr["grain"] = node_for_adding.grain
30
+ elif isinstance(node_for_adding, Datasource):
31
+ node_name = datasource_to_node(node_for_adding)
32
+ attr["type"] = "datasource"
33
+ attr["ds"] = node_for_adding
34
+ attr["grain"] = node_for_adding.grain
35
+ else:
36
+ node_name = node_for_adding
37
+ super().add_node(node_name, **attr)
38
+
39
+ def add_edge(self, u_of_edge, v_of_edge, **attr):
40
+ if isinstance(u_of_edge, Concept):
41
+ orig = u_of_edge
42
+ u_of_edge = concept_to_node(u_of_edge)
43
+ if u_of_edge not in self.nodes:
44
+ self.add_node(orig)
45
+ elif isinstance(u_of_edge, Datasource):
46
+ u_of_edge = datasource_to_node(u_of_edge)
47
+
48
+ if isinstance(v_of_edge, Concept):
49
+ orig = v_of_edge
50
+ v_of_edge = concept_to_node(v_of_edge)
51
+ if v_of_edge not in self.nodes:
52
+ self.add_node(orig)
53
+ elif isinstance(v_of_edge, Datasource):
54
+ v_of_edge = datasource_to_node(v_of_edge)
55
+ super().add_edge(u_of_edge, v_of_edge, **attr)
@@ -0,0 +1,37 @@
1
+ from trilogy.core.models import Concept, DataType, Function
2
+ from trilogy.core.enums import Purpose, FunctionType
3
+ from trilogy.core.constants import ALL_ROWS_CONCEPT, INTERNAL_NAMESPACE
4
+
5
+
6
+ DEFAULT_CONCEPTS = {
7
+ ALL_ROWS_CONCEPT: Concept(
8
+ name=ALL_ROWS_CONCEPT,
9
+ namespace=INTERNAL_NAMESPACE,
10
+ datatype=DataType.INTEGER,
11
+ purpose=Purpose.CONSTANT,
12
+ lineage=Function(
13
+ operator=FunctionType.CONSTANT,
14
+ arguments=[1],
15
+ output_datatype=DataType.INTEGER,
16
+ output_purpose=Purpose.CONSTANT,
17
+ ),
18
+ ),
19
+ "concept_name": Concept(
20
+ name="concept_name",
21
+ namespace=INTERNAL_NAMESPACE,
22
+ datatype=DataType.STRING,
23
+ purpose=Purpose.KEY,
24
+ ),
25
+ "datasource": Concept(
26
+ name="datasource",
27
+ namespace=INTERNAL_NAMESPACE,
28
+ datatype=DataType.STRING,
29
+ purpose=Purpose.KEY,
30
+ ),
31
+ "query_text": Concept(
32
+ name="query_text",
33
+ namespace=INTERNAL_NAMESPACE,
34
+ datatype=DataType.STRING,
35
+ purpose=Purpose.KEY,
36
+ ),
37
+ }