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,120 @@
1
+ from trilogy.core.graph_models import (
2
+ ReferenceGraph,
3
+ concept_to_node,
4
+ datasource_to_node,
5
+ )
6
+ from trilogy.core.models.build import BuildConcept, BuildDatasource
7
+ from trilogy.core.models.build_environment import BuildEnvironment
8
+
9
+
10
+ def add_concept(
11
+ concept: BuildConcept,
12
+ g: ReferenceGraph,
13
+ concept_mapping: dict[str, BuildConcept],
14
+ default_concept_graph: dict[str, BuildConcept],
15
+ seen: set[str],
16
+ ):
17
+
18
+ # if we have sources, recursively add them
19
+ node_name = concept_to_node(concept)
20
+ if node_name in seen:
21
+ return
22
+ seen.add(node_name)
23
+ g.concepts[node_name] = concept
24
+ g.add_node(node_name)
25
+ if concept.concept_arguments:
26
+ for source in concept.concept_arguments:
27
+ if not isinstance(source, BuildConcept):
28
+ raise ValueError(
29
+ f"Invalid non-build concept {source} passed into graph generation from {concept}"
30
+ )
31
+ generic = get_default_grain_concept(source, default_concept_graph)
32
+ generic_node = concept_to_node(generic)
33
+ add_concept(generic, g, concept_mapping, default_concept_graph, seen)
34
+
35
+ g.add_edge(generic_node, node_name, fast=True)
36
+ for ps_address in concept.pseudonyms:
37
+ if ps_address not in concept_mapping:
38
+ raise SyntaxError(f"Concept {concept} has invalid pseudonym {ps_address}")
39
+ pseudonym = concept_mapping[ps_address]
40
+ pseudonym = get_default_grain_concept(pseudonym, default_concept_graph)
41
+ pseudonym_node = concept_to_node(pseudonym)
42
+ if (pseudonym_node, node_name) in g.edges and (
43
+ node_name,
44
+ pseudonym_node,
45
+ ) in g.edges:
46
+ continue
47
+ if pseudonym_node.split("@")[0] == node_name.split("@")[0]:
48
+ continue
49
+ g.add_edge(pseudonym_node, node_name, fast=True)
50
+ g.add_edge(node_name, pseudonym_node, fast=True)
51
+ g.pseudonyms.add((pseudonym_node, node_name))
52
+ g.pseudonyms.add((node_name, pseudonym_node))
53
+ add_concept(pseudonym, g, concept_mapping, default_concept_graph, seen)
54
+
55
+
56
+ def get_default_grain_concept(
57
+ concept: BuildConcept, default_concept_graph: dict[str, BuildConcept]
58
+ ) -> BuildConcept:
59
+ """Get the default grain concept from the graph."""
60
+ if concept.address in default_concept_graph:
61
+ return default_concept_graph[concept.address]
62
+ default = concept.with_default_grain()
63
+ default_concept_graph[concept.address] = default
64
+ return default
65
+
66
+
67
+ def generate_adhoc_graph(
68
+ concepts: list[BuildConcept],
69
+ datasources: list[BuildDatasource],
70
+ default_concept_graph: dict[str, BuildConcept],
71
+ restrict_to_listed: bool = False,
72
+ ) -> ReferenceGraph:
73
+ g = ReferenceGraph()
74
+ concept_mapping = {x.address: x for x in concepts}
75
+ seen: set[str] = set()
76
+ for concept in concepts:
77
+ if not isinstance(concept, BuildConcept):
78
+ raise ValueError(f"Invalid non-build concept {concept}")
79
+
80
+ # add all parsed concepts
81
+ for concept in concepts:
82
+
83
+ add_concept(concept, g, concept_mapping, default_concept_graph, seen)
84
+
85
+ for dataset in datasources:
86
+ node = datasource_to_node(dataset)
87
+ g.add_datasource_node(node, dataset)
88
+ for concept in dataset.concepts:
89
+ cnode = concept_to_node(concept)
90
+ g.concepts[cnode] = concept
91
+ g.add_node(cnode)
92
+ if restrict_to_listed:
93
+ if cnode not in g.nodes:
94
+ continue
95
+ g.add_edge(node, cnode, fast=True)
96
+ g.add_edge(cnode, node, fast=True)
97
+ # if there is a key on a table at a different grain
98
+ # add an FK edge to the canonical source, if it exists
99
+ # for example, order ID on order product table
100
+ default = get_default_grain_concept(concept, default_concept_graph)
101
+
102
+ if concept != default:
103
+ dcnode = concept_to_node(default)
104
+ g.concepts[dcnode] = default
105
+ g.add_node(dcnode)
106
+ g.add_edge(cnode, dcnode, fast=True)
107
+ g.add_edge(dcnode, cnode, fast=True)
108
+ return g
109
+
110
+
111
+ def generate_graph(
112
+ environment: BuildEnvironment,
113
+ ) -> ReferenceGraph:
114
+ default_concept_graph: dict[str, BuildConcept] = {}
115
+ return generate_adhoc_graph(
116
+ list(environment.concepts.values())
117
+ + list(environment.alias_origin_lookup.values()),
118
+ list(environment.datasources.values()),
119
+ default_concept_graph=default_concept_graph,
120
+ )
@@ -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