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.
- LICENSE.md +19 -0
- _preql_import_resolver/__init__.py +5 -0
- _preql_import_resolver/_preql_import_resolver.cpython-311-x86_64-linux-gnu.so +0 -0
- pytrilogy-0.3.138.dist-info/METADATA +525 -0
- pytrilogy-0.3.138.dist-info/RECORD +182 -0
- pytrilogy-0.3.138.dist-info/WHEEL +5 -0
- pytrilogy-0.3.138.dist-info/entry_points.txt +2 -0
- pytrilogy-0.3.138.dist-info/licenses/LICENSE.md +19 -0
- trilogy/__init__.py +9 -0
- trilogy/ai/README.md +10 -0
- trilogy/ai/__init__.py +19 -0
- trilogy/ai/constants.py +92 -0
- trilogy/ai/conversation.py +107 -0
- trilogy/ai/enums.py +7 -0
- trilogy/ai/execute.py +50 -0
- trilogy/ai/models.py +34 -0
- trilogy/ai/prompts.py +87 -0
- trilogy/ai/providers/__init__.py +0 -0
- trilogy/ai/providers/anthropic.py +106 -0
- trilogy/ai/providers/base.py +24 -0
- trilogy/ai/providers/google.py +146 -0
- trilogy/ai/providers/openai.py +89 -0
- trilogy/ai/providers/utils.py +68 -0
- trilogy/authoring/README.md +3 -0
- trilogy/authoring/__init__.py +143 -0
- trilogy/constants.py +113 -0
- trilogy/core/README.md +52 -0
- trilogy/core/__init__.py +0 -0
- trilogy/core/constants.py +6 -0
- trilogy/core/enums.py +443 -0
- trilogy/core/env_processor.py +120 -0
- trilogy/core/environment_helpers.py +320 -0
- trilogy/core/ergonomics.py +193 -0
- trilogy/core/exceptions.py +123 -0
- trilogy/core/functions.py +1227 -0
- trilogy/core/graph_models.py +139 -0
- trilogy/core/internal.py +85 -0
- trilogy/core/models/__init__.py +0 -0
- trilogy/core/models/author.py +2672 -0
- trilogy/core/models/build.py +2521 -0
- trilogy/core/models/build_environment.py +180 -0
- trilogy/core/models/core.py +494 -0
- trilogy/core/models/datasource.py +322 -0
- trilogy/core/models/environment.py +748 -0
- trilogy/core/models/execute.py +1177 -0
- trilogy/core/optimization.py +251 -0
- trilogy/core/optimizations/__init__.py +12 -0
- trilogy/core/optimizations/base_optimization.py +17 -0
- trilogy/core/optimizations/hide_unused_concept.py +47 -0
- trilogy/core/optimizations/inline_datasource.py +102 -0
- trilogy/core/optimizations/predicate_pushdown.py +245 -0
- trilogy/core/processing/README.md +94 -0
- trilogy/core/processing/READMEv2.md +121 -0
- trilogy/core/processing/VIRTUAL_UNNEST.md +30 -0
- trilogy/core/processing/__init__.py +0 -0
- trilogy/core/processing/concept_strategies_v3.py +508 -0
- trilogy/core/processing/constants.py +15 -0
- trilogy/core/processing/discovery_node_factory.py +451 -0
- trilogy/core/processing/discovery_utility.py +517 -0
- trilogy/core/processing/discovery_validation.py +167 -0
- trilogy/core/processing/graph_utils.py +43 -0
- trilogy/core/processing/node_generators/README.md +9 -0
- trilogy/core/processing/node_generators/__init__.py +31 -0
- trilogy/core/processing/node_generators/basic_node.py +160 -0
- trilogy/core/processing/node_generators/common.py +268 -0
- trilogy/core/processing/node_generators/constant_node.py +38 -0
- trilogy/core/processing/node_generators/filter_node.py +315 -0
- trilogy/core/processing/node_generators/group_node.py +213 -0
- trilogy/core/processing/node_generators/group_to_node.py +117 -0
- trilogy/core/processing/node_generators/multiselect_node.py +205 -0
- trilogy/core/processing/node_generators/node_merge_node.py +653 -0
- trilogy/core/processing/node_generators/recursive_node.py +88 -0
- trilogy/core/processing/node_generators/rowset_node.py +165 -0
- trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +261 -0
- trilogy/core/processing/node_generators/select_merge_node.py +748 -0
- trilogy/core/processing/node_generators/select_node.py +95 -0
- trilogy/core/processing/node_generators/synonym_node.py +98 -0
- trilogy/core/processing/node_generators/union_node.py +91 -0
- trilogy/core/processing/node_generators/unnest_node.py +182 -0
- trilogy/core/processing/node_generators/window_node.py +201 -0
- trilogy/core/processing/nodes/README.md +28 -0
- trilogy/core/processing/nodes/__init__.py +179 -0
- trilogy/core/processing/nodes/base_node.py +519 -0
- trilogy/core/processing/nodes/filter_node.py +75 -0
- trilogy/core/processing/nodes/group_node.py +194 -0
- trilogy/core/processing/nodes/merge_node.py +420 -0
- trilogy/core/processing/nodes/recursive_node.py +46 -0
- trilogy/core/processing/nodes/select_node_v2.py +242 -0
- trilogy/core/processing/nodes/union_node.py +53 -0
- trilogy/core/processing/nodes/unnest_node.py +62 -0
- trilogy/core/processing/nodes/window_node.py +56 -0
- trilogy/core/processing/utility.py +823 -0
- trilogy/core/query_processor.py +596 -0
- trilogy/core/statements/README.md +35 -0
- trilogy/core/statements/__init__.py +0 -0
- trilogy/core/statements/author.py +536 -0
- trilogy/core/statements/build.py +0 -0
- trilogy/core/statements/common.py +20 -0
- trilogy/core/statements/execute.py +155 -0
- trilogy/core/table_processor.py +66 -0
- trilogy/core/utility.py +8 -0
- trilogy/core/validation/README.md +46 -0
- trilogy/core/validation/__init__.py +0 -0
- trilogy/core/validation/common.py +161 -0
- trilogy/core/validation/concept.py +146 -0
- trilogy/core/validation/datasource.py +227 -0
- trilogy/core/validation/environment.py +73 -0
- trilogy/core/validation/fix.py +106 -0
- trilogy/dialect/__init__.py +32 -0
- trilogy/dialect/base.py +1359 -0
- trilogy/dialect/bigquery.py +256 -0
- trilogy/dialect/common.py +147 -0
- trilogy/dialect/config.py +144 -0
- trilogy/dialect/dataframe.py +50 -0
- trilogy/dialect/duckdb.py +177 -0
- trilogy/dialect/enums.py +147 -0
- trilogy/dialect/metadata.py +173 -0
- trilogy/dialect/mock.py +190 -0
- trilogy/dialect/postgres.py +91 -0
- trilogy/dialect/presto.py +104 -0
- trilogy/dialect/results.py +89 -0
- trilogy/dialect/snowflake.py +90 -0
- trilogy/dialect/sql_server.py +92 -0
- trilogy/engine.py +48 -0
- trilogy/execution/config.py +75 -0
- trilogy/executor.py +568 -0
- trilogy/hooks/__init__.py +4 -0
- trilogy/hooks/base_hook.py +40 -0
- trilogy/hooks/graph_hook.py +139 -0
- trilogy/hooks/query_debugger.py +166 -0
- trilogy/metadata/__init__.py +0 -0
- trilogy/parser.py +10 -0
- trilogy/parsing/README.md +21 -0
- trilogy/parsing/__init__.py +0 -0
- trilogy/parsing/common.py +1069 -0
- trilogy/parsing/config.py +5 -0
- trilogy/parsing/exceptions.py +8 -0
- trilogy/parsing/helpers.py +1 -0
- trilogy/parsing/parse_engine.py +2813 -0
- trilogy/parsing/render.py +750 -0
- trilogy/parsing/trilogy.lark +540 -0
- trilogy/py.typed +0 -0
- trilogy/render.py +42 -0
- trilogy/scripts/README.md +7 -0
- trilogy/scripts/__init__.py +0 -0
- trilogy/scripts/dependency/Cargo.lock +617 -0
- trilogy/scripts/dependency/Cargo.toml +39 -0
- trilogy/scripts/dependency/README.md +131 -0
- trilogy/scripts/dependency/build.sh +25 -0
- trilogy/scripts/dependency/src/directory_resolver.rs +162 -0
- trilogy/scripts/dependency/src/lib.rs +16 -0
- trilogy/scripts/dependency/src/main.rs +770 -0
- trilogy/scripts/dependency/src/parser.rs +435 -0
- trilogy/scripts/dependency/src/preql.pest +208 -0
- trilogy/scripts/dependency/src/python_bindings.rs +289 -0
- trilogy/scripts/dependency/src/resolver.rs +716 -0
- trilogy/scripts/dependency/tests/base.preql +3 -0
- trilogy/scripts/dependency/tests/cli_integration.rs +377 -0
- trilogy/scripts/dependency/tests/customer.preql +6 -0
- trilogy/scripts/dependency/tests/main.preql +9 -0
- trilogy/scripts/dependency/tests/orders.preql +7 -0
- trilogy/scripts/dependency/tests/test_data/base.preql +9 -0
- trilogy/scripts/dependency/tests/test_data/consumer.preql +1 -0
- trilogy/scripts/dependency.py +323 -0
- trilogy/scripts/display.py +460 -0
- trilogy/scripts/environment.py +46 -0
- trilogy/scripts/parallel_execution.py +483 -0
- trilogy/scripts/single_execution.py +131 -0
- trilogy/scripts/trilogy.py +772 -0
- trilogy/std/__init__.py +0 -0
- trilogy/std/color.preql +3 -0
- trilogy/std/date.preql +13 -0
- trilogy/std/display.preql +18 -0
- trilogy/std/geography.preql +22 -0
- trilogy/std/metric.preql +15 -0
- trilogy/std/money.preql +67 -0
- trilogy/std/net.preql +14 -0
- trilogy/std/ranking.preql +7 -0
- trilogy/std/report.preql +5 -0
- trilogy/std/semantic.preql +6 -0
- trilogy/utility.py +34 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
from trilogy.constants import DEFAULT_NAMESPACE
|
|
2
|
+
from trilogy.core.enums import ConceptSource, DatePart, FunctionType, Purpose
|
|
3
|
+
from trilogy.core.functions import AttrAccess
|
|
4
|
+
from trilogy.core.models.author import Concept, Function, Grain, Metadata, TraitDataType
|
|
5
|
+
from trilogy.core.models.core import DataType, StructType, arg_to_datatype
|
|
6
|
+
from trilogy.core.models.environment import Environment
|
|
7
|
+
from trilogy.parsing.common import Meta
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate_date_concepts(concept: Concept, environment: Environment):
|
|
11
|
+
if concept.metadata and concept.metadata.line_number:
|
|
12
|
+
base_line_number = concept.metadata.line_number
|
|
13
|
+
else:
|
|
14
|
+
base_line_number = None
|
|
15
|
+
arg_tuples: list[tuple[FunctionType, TraitDataType]] = [
|
|
16
|
+
(FunctionType.MONTH, TraitDataType(type=DataType.INTEGER, traits=["month"])),
|
|
17
|
+
(FunctionType.YEAR, TraitDataType(type=DataType.INTEGER, traits=["year"])),
|
|
18
|
+
(
|
|
19
|
+
FunctionType.QUARTER,
|
|
20
|
+
TraitDataType(type=DataType.INTEGER, traits=["quarter"]),
|
|
21
|
+
),
|
|
22
|
+
(FunctionType.DAY, TraitDataType(type=DataType.INTEGER, traits=["day"])),
|
|
23
|
+
(
|
|
24
|
+
FunctionType.DAY_OF_WEEK,
|
|
25
|
+
TraitDataType(type=DataType.INTEGER, traits=["day_of_week"]),
|
|
26
|
+
),
|
|
27
|
+
]
|
|
28
|
+
for ftype, dtype in arg_tuples:
|
|
29
|
+
fname = ftype.name.lower()
|
|
30
|
+
address = concept.address + f".{fname}"
|
|
31
|
+
if address in environment.concepts:
|
|
32
|
+
continue
|
|
33
|
+
default_type = (
|
|
34
|
+
Purpose.CONSTANT
|
|
35
|
+
if concept.purpose == Purpose.CONSTANT
|
|
36
|
+
else Purpose.PROPERTY
|
|
37
|
+
)
|
|
38
|
+
function = Function.model_construct(
|
|
39
|
+
operator=ftype,
|
|
40
|
+
arguments=[concept.reference],
|
|
41
|
+
output_datatype=dtype,
|
|
42
|
+
output_purpose=default_type,
|
|
43
|
+
)
|
|
44
|
+
new_concept = Concept.model_construct(
|
|
45
|
+
name=f"{concept.name}.{fname}",
|
|
46
|
+
datatype=function.output_datatype,
|
|
47
|
+
purpose=default_type,
|
|
48
|
+
lineage=function,
|
|
49
|
+
grain=concept.grain,
|
|
50
|
+
namespace=concept.namespace,
|
|
51
|
+
keys=set(
|
|
52
|
+
[concept.address],
|
|
53
|
+
),
|
|
54
|
+
metadata=Metadata(
|
|
55
|
+
line_number=base_line_number,
|
|
56
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
60
|
+
for grain in [DatePart.MONTH, DatePart.YEAR]:
|
|
61
|
+
address = concept.address + f".{grain.value}_start"
|
|
62
|
+
if address in environment.concepts:
|
|
63
|
+
continue
|
|
64
|
+
function = Function.model_construct(
|
|
65
|
+
operator=FunctionType.DATE_TRUNCATE,
|
|
66
|
+
arguments=[concept.reference, grain],
|
|
67
|
+
output_datatype=DataType.DATE,
|
|
68
|
+
output_purpose=default_type,
|
|
69
|
+
arg_count=2,
|
|
70
|
+
)
|
|
71
|
+
new_concept = Concept.model_construct(
|
|
72
|
+
name=f"{concept.name}.{grain.value}_start",
|
|
73
|
+
datatype=DataType.DATE,
|
|
74
|
+
purpose=Purpose.PROPERTY,
|
|
75
|
+
lineage=function,
|
|
76
|
+
grain=concept.grain.model_copy(),
|
|
77
|
+
namespace=concept.namespace,
|
|
78
|
+
keys=set(
|
|
79
|
+
[concept.address],
|
|
80
|
+
),
|
|
81
|
+
metadata=Metadata(
|
|
82
|
+
# description=f"Auto-derived from {base_description}. The date truncated to the {grain.value}.",
|
|
83
|
+
line_number=base_line_number,
|
|
84
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
92
|
+
if concept.metadata and concept.metadata.line_number:
|
|
93
|
+
base_line_number = concept.metadata.line_number
|
|
94
|
+
else:
|
|
95
|
+
base_line_number = None
|
|
96
|
+
setup_tuples: list[tuple[FunctionType, DataType | TraitDataType]] = [
|
|
97
|
+
(FunctionType.DATE, DataType.DATE),
|
|
98
|
+
(FunctionType.HOUR, TraitDataType(type=DataType.INTEGER, traits=["hour"])),
|
|
99
|
+
(FunctionType.MINUTE, TraitDataType(type=DataType.INTEGER, traits=["minute"])),
|
|
100
|
+
(FunctionType.SECOND, TraitDataType(type=DataType.INTEGER, traits=["second"])),
|
|
101
|
+
]
|
|
102
|
+
for ftype, datatype in setup_tuples:
|
|
103
|
+
fname = ftype.name.lower()
|
|
104
|
+
address = concept.address + f".{fname}"
|
|
105
|
+
if address in environment.concepts:
|
|
106
|
+
continue
|
|
107
|
+
default_type = (
|
|
108
|
+
Purpose.CONSTANT
|
|
109
|
+
if concept.purpose == Purpose.CONSTANT
|
|
110
|
+
else Purpose.PROPERTY
|
|
111
|
+
)
|
|
112
|
+
const_function = Function.model_construct(
|
|
113
|
+
operator=ftype,
|
|
114
|
+
arguments=[concept.reference],
|
|
115
|
+
output_datatype=datatype,
|
|
116
|
+
output_purpose=default_type,
|
|
117
|
+
)
|
|
118
|
+
new_concept = Concept.model_construct(
|
|
119
|
+
name=f"{concept.name}.{fname}",
|
|
120
|
+
datatype=datatype,
|
|
121
|
+
purpose=default_type,
|
|
122
|
+
lineage=const_function,
|
|
123
|
+
grain=concept.grain.model_copy(),
|
|
124
|
+
namespace=concept.namespace,
|
|
125
|
+
keys=set(
|
|
126
|
+
[concept.address],
|
|
127
|
+
),
|
|
128
|
+
metadata=Metadata(
|
|
129
|
+
line_number=base_line_number,
|
|
130
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
if new_concept.name in environment.concepts:
|
|
134
|
+
continue
|
|
135
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def generate_key_concepts(concept: Concept, environment: Environment):
|
|
139
|
+
if concept.metadata and concept.metadata.line_number:
|
|
140
|
+
base_line_number = concept.metadata.line_number
|
|
141
|
+
else:
|
|
142
|
+
base_line_number = None
|
|
143
|
+
for ftype in [FunctionType.COUNT]:
|
|
144
|
+
address = concept.address + f".{ftype.name.lower()}"
|
|
145
|
+
if address in environment.concepts:
|
|
146
|
+
continue
|
|
147
|
+
fname = ftype.name.lower()
|
|
148
|
+
default_type = Purpose.METRIC
|
|
149
|
+
const_function: Function = Function.model_construct(
|
|
150
|
+
operator=ftype,
|
|
151
|
+
output_datatype=DataType.INTEGER,
|
|
152
|
+
output_purpose=default_type,
|
|
153
|
+
arguments=[concept.reference],
|
|
154
|
+
)
|
|
155
|
+
new_concept = Concept.model_construct(
|
|
156
|
+
name=f"{concept.name}.{fname}",
|
|
157
|
+
datatype=DataType.INTEGER,
|
|
158
|
+
purpose=default_type,
|
|
159
|
+
lineage=const_function,
|
|
160
|
+
grain=Grain(),
|
|
161
|
+
namespace=concept.namespace,
|
|
162
|
+
keys=set(),
|
|
163
|
+
metadata=Metadata(
|
|
164
|
+
# description=f"Auto-derived integer. The {ftype.value} of {concept.address}, {base_description}",
|
|
165
|
+
line_number=base_line_number,
|
|
166
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def remove_date_concepts(concept: Concept, environment: Environment):
|
|
173
|
+
"""Remove auto-generated date-related concepts for the given concept"""
|
|
174
|
+
date_suffixes = ["month", "year", "quarter", "day", "day_of_week"]
|
|
175
|
+
grain_suffixes = ["month_start", "year_start"]
|
|
176
|
+
|
|
177
|
+
for suffix in date_suffixes + grain_suffixes:
|
|
178
|
+
address = concept.address + f".{suffix}"
|
|
179
|
+
if address in environment.concepts:
|
|
180
|
+
derived_concept = environment.concepts[address]
|
|
181
|
+
# Only remove if it was auto-derived from this concept
|
|
182
|
+
if (
|
|
183
|
+
derived_concept.metadata
|
|
184
|
+
and derived_concept.metadata.concept_source
|
|
185
|
+
== ConceptSource.AUTO_DERIVED
|
|
186
|
+
and derived_concept.keys
|
|
187
|
+
and concept.address in derived_concept.keys
|
|
188
|
+
):
|
|
189
|
+
environment.remove_concept(address)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def remove_datetime_concepts(concept: Concept, environment: Environment):
|
|
193
|
+
"""Remove auto-generated datetime-related concepts for the given concept"""
|
|
194
|
+
datetime_suffixes = ["date", "hour", "minute", "second"]
|
|
195
|
+
|
|
196
|
+
for suffix in datetime_suffixes:
|
|
197
|
+
address = concept.address + f".{suffix}"
|
|
198
|
+
if address in environment.concepts:
|
|
199
|
+
derived_concept = environment.concepts[address]
|
|
200
|
+
# Only remove if it was auto-derived from this concept
|
|
201
|
+
if (
|
|
202
|
+
derived_concept.metadata
|
|
203
|
+
and derived_concept.metadata.concept_source
|
|
204
|
+
== ConceptSource.AUTO_DERIVED
|
|
205
|
+
and derived_concept.keys
|
|
206
|
+
and concept.address in derived_concept.keys
|
|
207
|
+
):
|
|
208
|
+
environment.remove_concept(address)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def remove_key_concepts(concept: Concept, environment: Environment):
|
|
212
|
+
"""Remove auto-generated key-related concepts for the given concept"""
|
|
213
|
+
key_suffixes = ["count"]
|
|
214
|
+
|
|
215
|
+
for suffix in key_suffixes:
|
|
216
|
+
address = concept.address + f".{suffix}"
|
|
217
|
+
if address in environment.concepts:
|
|
218
|
+
derived_concept = environment.concepts[address]
|
|
219
|
+
if (
|
|
220
|
+
derived_concept.metadata
|
|
221
|
+
and derived_concept.metadata.concept_source
|
|
222
|
+
== ConceptSource.AUTO_DERIVED
|
|
223
|
+
):
|
|
224
|
+
environment.remove_concept(address)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def remove_struct_concepts(concept: Concept, environment: Environment):
|
|
228
|
+
"""Remove auto-generated struct field concepts for the given concept"""
|
|
229
|
+
if not isinstance(concept.datatype, StructType):
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
target_namespace = (
|
|
233
|
+
environment.namespace + "." + concept.name
|
|
234
|
+
if environment.namespace and environment.namespace != DEFAULT_NAMESPACE
|
|
235
|
+
else concept.name
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Get all concepts in the target namespace that were auto-derived
|
|
239
|
+
concepts_to_remove = []
|
|
240
|
+
for address, derived_concept in environment.concepts.items():
|
|
241
|
+
if (
|
|
242
|
+
derived_concept.namespace == target_namespace
|
|
243
|
+
and derived_concept.metadata
|
|
244
|
+
and derived_concept.metadata.concept_source == ConceptSource.AUTO_DERIVED
|
|
245
|
+
and isinstance(derived_concept.lineage, Function)
|
|
246
|
+
and derived_concept.lineage.operator == FunctionType.ATTR_ACCESS
|
|
247
|
+
and len(derived_concept.lineage.arguments) >= 1
|
|
248
|
+
and derived_concept.lineage.arguments[0] == concept.reference
|
|
249
|
+
):
|
|
250
|
+
concepts_to_remove.append(address)
|
|
251
|
+
|
|
252
|
+
for address in concepts_to_remove:
|
|
253
|
+
environment.remove_concept(address)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def remove_related_concepts(concept: Concept, environment: Environment):
|
|
257
|
+
"""Remove all auto-generated concepts that were derived from the given concept"""
|
|
258
|
+
|
|
259
|
+
# Remove key-related concepts
|
|
260
|
+
if concept.purpose == Purpose.KEY:
|
|
261
|
+
remove_key_concepts(concept, environment)
|
|
262
|
+
|
|
263
|
+
# Remove datatype-specific concepts
|
|
264
|
+
if concept.datatype == DataType.DATE:
|
|
265
|
+
remove_date_concepts(concept, environment)
|
|
266
|
+
elif concept.datatype == DataType.DATETIME:
|
|
267
|
+
remove_date_concepts(concept, environment)
|
|
268
|
+
remove_datetime_concepts(concept, environment)
|
|
269
|
+
elif concept.datatype == DataType.TIMESTAMP:
|
|
270
|
+
remove_date_concepts(concept, environment)
|
|
271
|
+
remove_datetime_concepts(concept, environment)
|
|
272
|
+
|
|
273
|
+
# Remove struct field concepts
|
|
274
|
+
if isinstance(concept.datatype, StructType):
|
|
275
|
+
remove_struct_concepts(concept, environment)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def generate_related_concepts(
|
|
279
|
+
concept: Concept,
|
|
280
|
+
environment: Environment,
|
|
281
|
+
meta: Meta | None = None,
|
|
282
|
+
add_derived: bool = False,
|
|
283
|
+
):
|
|
284
|
+
"""Auto populate common derived concepts on types"""
|
|
285
|
+
if concept.purpose == Purpose.KEY and add_derived:
|
|
286
|
+
generate_key_concepts(concept, environment)
|
|
287
|
+
|
|
288
|
+
# datatype types
|
|
289
|
+
if concept.datatype == DataType.DATE and add_derived:
|
|
290
|
+
generate_date_concepts(concept, environment)
|
|
291
|
+
elif concept.datatype == DataType.DATETIME and add_derived:
|
|
292
|
+
|
|
293
|
+
generate_date_concepts(concept, environment)
|
|
294
|
+
generate_datetime_concepts(concept, environment)
|
|
295
|
+
elif concept.datatype == DataType.TIMESTAMP and add_derived:
|
|
296
|
+
generate_date_concepts(concept, environment)
|
|
297
|
+
generate_datetime_concepts(concept, environment)
|
|
298
|
+
|
|
299
|
+
if isinstance(concept.datatype, StructType):
|
|
300
|
+
for key, value in concept.datatype.fields_map.items():
|
|
301
|
+
auto = Concept.model_construct(
|
|
302
|
+
name=key,
|
|
303
|
+
datatype=arg_to_datatype(value),
|
|
304
|
+
purpose=Purpose.PROPERTY,
|
|
305
|
+
namespace=(
|
|
306
|
+
environment.namespace + "." + concept.name
|
|
307
|
+
if environment.namespace
|
|
308
|
+
and environment.namespace != DEFAULT_NAMESPACE
|
|
309
|
+
else concept.name
|
|
310
|
+
),
|
|
311
|
+
lineage=AttrAccess([concept.reference, key], environment=environment),
|
|
312
|
+
grain=concept.grain,
|
|
313
|
+
metadata=Metadata(
|
|
314
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
315
|
+
),
|
|
316
|
+
keys=concept.keys,
|
|
317
|
+
)
|
|
318
|
+
environment.add_concept(auto, meta=meta)
|
|
319
|
+
if isinstance(value, Concept):
|
|
320
|
+
environment.merge_concept(auto, value, modifiers=[])
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from trilogy.constants import CONFIG
|
|
2
|
+
|
|
3
|
+
# source: https://github.com/aaronbassett/Pass-phrase
|
|
4
|
+
CTE_NAMES = """quizzical
|
|
5
|
+
highfalutin
|
|
6
|
+
wakeful
|
|
7
|
+
cheerful
|
|
8
|
+
thoughtful
|
|
9
|
+
cooperative
|
|
10
|
+
questionable
|
|
11
|
+
abundant
|
|
12
|
+
uneven
|
|
13
|
+
yummy
|
|
14
|
+
juicy
|
|
15
|
+
vacuous
|
|
16
|
+
concerned
|
|
17
|
+
young
|
|
18
|
+
sparkling
|
|
19
|
+
abhorrent
|
|
20
|
+
sweltering
|
|
21
|
+
late
|
|
22
|
+
macho
|
|
23
|
+
scrawny
|
|
24
|
+
friendly
|
|
25
|
+
kaput
|
|
26
|
+
divergent
|
|
27
|
+
busy
|
|
28
|
+
charming
|
|
29
|
+
protective
|
|
30
|
+
premium
|
|
31
|
+
puzzled
|
|
32
|
+
waggish
|
|
33
|
+
rambunctious
|
|
34
|
+
puffy
|
|
35
|
+
hard
|
|
36
|
+
sedate
|
|
37
|
+
yellow
|
|
38
|
+
resonant
|
|
39
|
+
dapper
|
|
40
|
+
courageous
|
|
41
|
+
vast
|
|
42
|
+
cool
|
|
43
|
+
elated
|
|
44
|
+
wary
|
|
45
|
+
bewildered
|
|
46
|
+
level
|
|
47
|
+
wooden
|
|
48
|
+
ceaseless
|
|
49
|
+
tearful
|
|
50
|
+
cloudy
|
|
51
|
+
gullible
|
|
52
|
+
flashy
|
|
53
|
+
trite
|
|
54
|
+
quick
|
|
55
|
+
nondescript
|
|
56
|
+
round
|
|
57
|
+
slow
|
|
58
|
+
spiritual
|
|
59
|
+
brave
|
|
60
|
+
tenuous
|
|
61
|
+
abstracted
|
|
62
|
+
colossal
|
|
63
|
+
sloppy
|
|
64
|
+
obsolete
|
|
65
|
+
elegant
|
|
66
|
+
fabulous
|
|
67
|
+
vivacious
|
|
68
|
+
exuberant
|
|
69
|
+
faithful
|
|
70
|
+
helpless
|
|
71
|
+
odd
|
|
72
|
+
sordid
|
|
73
|
+
blue
|
|
74
|
+
imported
|
|
75
|
+
ugly
|
|
76
|
+
ruthless
|
|
77
|
+
deeply
|
|
78
|
+
eminent
|
|
79
|
+
badger
|
|
80
|
+
barracuda
|
|
81
|
+
bear
|
|
82
|
+
boa
|
|
83
|
+
cheetah
|
|
84
|
+
chimpanzee
|
|
85
|
+
civet
|
|
86
|
+
cobra
|
|
87
|
+
cougar
|
|
88
|
+
coyote
|
|
89
|
+
crocodile
|
|
90
|
+
dingo
|
|
91
|
+
eagle
|
|
92
|
+
eel
|
|
93
|
+
fossa
|
|
94
|
+
fox
|
|
95
|
+
human
|
|
96
|
+
jackal
|
|
97
|
+
jaguar
|
|
98
|
+
komodo
|
|
99
|
+
leopard
|
|
100
|
+
lion
|
|
101
|
+
lynx
|
|
102
|
+
mamba
|
|
103
|
+
mandrill
|
|
104
|
+
marlin
|
|
105
|
+
monitor
|
|
106
|
+
ocelot
|
|
107
|
+
petrel
|
|
108
|
+
python
|
|
109
|
+
ray
|
|
110
|
+
salamander
|
|
111
|
+
serval
|
|
112
|
+
shark
|
|
113
|
+
skua
|
|
114
|
+
tiger
|
|
115
|
+
viper
|
|
116
|
+
wolf
|
|
117
|
+
wolverine
|
|
118
|
+
albatross
|
|
119
|
+
avocet
|
|
120
|
+
budgie
|
|
121
|
+
canary
|
|
122
|
+
chick
|
|
123
|
+
chickadee
|
|
124
|
+
chicken
|
|
125
|
+
cockatiel
|
|
126
|
+
cockatoo
|
|
127
|
+
coot
|
|
128
|
+
covey
|
|
129
|
+
crow
|
|
130
|
+
cuckoo
|
|
131
|
+
darter
|
|
132
|
+
dove
|
|
133
|
+
duck
|
|
134
|
+
falcon
|
|
135
|
+
finch
|
|
136
|
+
flamingo
|
|
137
|
+
fowl
|
|
138
|
+
goldfinch
|
|
139
|
+
goose
|
|
140
|
+
grouse
|
|
141
|
+
hawk
|
|
142
|
+
heron
|
|
143
|
+
jackdaw
|
|
144
|
+
jay
|
|
145
|
+
kestrel
|
|
146
|
+
lark
|
|
147
|
+
loon
|
|
148
|
+
macaw
|
|
149
|
+
magpie
|
|
150
|
+
martin
|
|
151
|
+
osprey
|
|
152
|
+
ostrich
|
|
153
|
+
owl
|
|
154
|
+
parakeet
|
|
155
|
+
parrot
|
|
156
|
+
pelican
|
|
157
|
+
penguin
|
|
158
|
+
pigeon
|
|
159
|
+
pintail
|
|
160
|
+
puffin
|
|
161
|
+
quail
|
|
162
|
+
quetzal
|
|
163
|
+
rail
|
|
164
|
+
raven
|
|
165
|
+
razorbill
|
|
166
|
+
rhea
|
|
167
|
+
rook
|
|
168
|
+
shrike
|
|
169
|
+
skylark
|
|
170
|
+
snipe
|
|
171
|
+
sparrow
|
|
172
|
+
starling
|
|
173
|
+
stork
|
|
174
|
+
swallow
|
|
175
|
+
swift
|
|
176
|
+
tanager
|
|
177
|
+
thrush
|
|
178
|
+
toucan
|
|
179
|
+
turkey
|
|
180
|
+
vulture
|
|
181
|
+
warbler""".split(
|
|
182
|
+
"\n"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def generate_cte_names():
|
|
187
|
+
if CONFIG.randomize_cte_names:
|
|
188
|
+
from random import shuffle
|
|
189
|
+
|
|
190
|
+
new = [*CTE_NAMES]
|
|
191
|
+
shuffle(new)
|
|
192
|
+
return new
|
|
193
|
+
return CTE_NAMES
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, List, Sequence
|
|
3
|
+
|
|
4
|
+
from trilogy.core.enums import Modifier
|
|
5
|
+
from trilogy.core.models.core import (
|
|
6
|
+
ArrayType,
|
|
7
|
+
DataType,
|
|
8
|
+
MapType,
|
|
9
|
+
NumericType,
|
|
10
|
+
StructType,
|
|
11
|
+
TraitDataType,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConfigurationException(Exception):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UndefinedConceptException(Exception):
|
|
20
|
+
def __init__(self, message, suggestions: List[str]):
|
|
21
|
+
super().__init__(self, message)
|
|
22
|
+
self.message = message
|
|
23
|
+
self.suggestions = suggestions
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FrozenEnvironmentException(Exception):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class InvalidSyntaxException(Exception):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MissingParameterException(InvalidSyntaxException):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class UnresolvableQueryException(Exception):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class NoDatasourceException(UnresolvableQueryException):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ModelValidationError(Exception):
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
message,
|
|
50
|
+
children: Sequence["ModelValidationError"] | None = None,
|
|
51
|
+
**kwargs,
|
|
52
|
+
):
|
|
53
|
+
super().__init__(self, message, **kwargs)
|
|
54
|
+
self.message = message
|
|
55
|
+
self.children = children
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DatasourceModelValidationError(ModelValidationError):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class DatasourceGrainValidationError(DatasourceModelValidationError):
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class DatasourceColumnBindingData:
|
|
68
|
+
address: str
|
|
69
|
+
value: Any
|
|
70
|
+
value_type: (
|
|
71
|
+
DataType | ArrayType | StructType | MapType | NumericType | TraitDataType
|
|
72
|
+
)
|
|
73
|
+
value_modifiers: List[Modifier]
|
|
74
|
+
actual_type: (
|
|
75
|
+
DataType | ArrayType | StructType | MapType | NumericType | TraitDataType
|
|
76
|
+
)
|
|
77
|
+
actual_modifiers: List[Modifier]
|
|
78
|
+
|
|
79
|
+
def format_failure(self):
|
|
80
|
+
value_mods = (
|
|
81
|
+
f"({', '.join(x.name for x in self.value_modifiers)})"
|
|
82
|
+
if self.value_modifiers
|
|
83
|
+
else ""
|
|
84
|
+
)
|
|
85
|
+
actual_mods = (
|
|
86
|
+
f"({', '.join(x.name for x in self.actual_modifiers)})"
|
|
87
|
+
if self.actual_modifiers
|
|
88
|
+
else ""
|
|
89
|
+
)
|
|
90
|
+
return f"Value '{self.value}' for concept {self.address} has inferred type {self.value_type}{value_mods} vs expected type {str(self.actual_type)}{actual_mods}"
|
|
91
|
+
|
|
92
|
+
def is_modifier_issue(self) -> bool:
|
|
93
|
+
return len(self.value_modifiers) > 0 and any(
|
|
94
|
+
[x not in self.actual_modifiers for x in self.value_modifiers]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def is_type_issue(self) -> bool:
|
|
98
|
+
return self.value_type != self.actual_type
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class DatasourceColumnBindingError(DatasourceModelValidationError):
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
address: str,
|
|
105
|
+
errors: list[DatasourceColumnBindingData],
|
|
106
|
+
message: str | None = None,
|
|
107
|
+
):
|
|
108
|
+
if not message:
|
|
109
|
+
message = f"Datasource {address} failed validation. Data type mismatch: {[failure.format_failure() for failure in errors]}"
|
|
110
|
+
super().__init__(message)
|
|
111
|
+
self.errors = errors
|
|
112
|
+
self.dataset_address = address
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ConceptModelValidationError(ModelValidationError):
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class AmbiguousRelationshipResolutionException(UnresolvableQueryException):
|
|
120
|
+
def __init__(self, message, parents: List[set[str]]):
|
|
121
|
+
super().__init__(self, message)
|
|
122
|
+
self.message = message
|
|
123
|
+
self.parents = parents
|