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.
- pytrilogy-0.0.1.102.dist-info/LICENSE.md +19 -0
- pytrilogy-0.0.1.102.dist-info/METADATA +277 -0
- pytrilogy-0.0.1.102.dist-info/RECORD +77 -0
- pytrilogy-0.0.1.102.dist-info/WHEEL +5 -0
- pytrilogy-0.0.1.102.dist-info/entry_points.txt +2 -0
- pytrilogy-0.0.1.102.dist-info/top_level.txt +1 -0
- trilogy/__init__.py +8 -0
- trilogy/compiler.py +0 -0
- trilogy/constants.py +30 -0
- trilogy/core/__init__.py +0 -0
- trilogy/core/constants.py +3 -0
- trilogy/core/enums.py +270 -0
- trilogy/core/env_processor.py +33 -0
- trilogy/core/environment_helpers.py +156 -0
- trilogy/core/ergonomics.py +187 -0
- trilogy/core/exceptions.py +23 -0
- trilogy/core/functions.py +320 -0
- trilogy/core/graph_models.py +55 -0
- trilogy/core/internal.py +37 -0
- trilogy/core/models.py +3145 -0
- trilogy/core/processing/__init__.py +0 -0
- trilogy/core/processing/concept_strategies_v3.py +603 -0
- trilogy/core/processing/graph_utils.py +44 -0
- trilogy/core/processing/node_generators/__init__.py +25 -0
- trilogy/core/processing/node_generators/basic_node.py +71 -0
- trilogy/core/processing/node_generators/common.py +239 -0
- trilogy/core/processing/node_generators/concept_merge.py +152 -0
- trilogy/core/processing/node_generators/filter_node.py +83 -0
- trilogy/core/processing/node_generators/group_node.py +92 -0
- trilogy/core/processing/node_generators/group_to_node.py +99 -0
- trilogy/core/processing/node_generators/merge_node.py +148 -0
- trilogy/core/processing/node_generators/multiselect_node.py +189 -0
- trilogy/core/processing/node_generators/rowset_node.py +130 -0
- trilogy/core/processing/node_generators/select_node.py +328 -0
- trilogy/core/processing/node_generators/unnest_node.py +37 -0
- trilogy/core/processing/node_generators/window_node.py +85 -0
- trilogy/core/processing/nodes/__init__.py +76 -0
- trilogy/core/processing/nodes/base_node.py +251 -0
- trilogy/core/processing/nodes/filter_node.py +49 -0
- trilogy/core/processing/nodes/group_node.py +110 -0
- trilogy/core/processing/nodes/merge_node.py +326 -0
- trilogy/core/processing/nodes/select_node_v2.py +198 -0
- trilogy/core/processing/nodes/unnest_node.py +54 -0
- trilogy/core/processing/nodes/window_node.py +34 -0
- trilogy/core/processing/utility.py +278 -0
- trilogy/core/query_processor.py +331 -0
- trilogy/dialect/__init__.py +0 -0
- trilogy/dialect/base.py +679 -0
- trilogy/dialect/bigquery.py +80 -0
- trilogy/dialect/common.py +43 -0
- trilogy/dialect/config.py +55 -0
- trilogy/dialect/duckdb.py +83 -0
- trilogy/dialect/enums.py +95 -0
- trilogy/dialect/postgres.py +86 -0
- trilogy/dialect/presto.py +82 -0
- trilogy/dialect/snowflake.py +82 -0
- trilogy/dialect/sql_server.py +89 -0
- trilogy/docs/__init__.py +0 -0
- trilogy/engine.py +48 -0
- trilogy/executor.py +242 -0
- trilogy/hooks/__init__.py +0 -0
- trilogy/hooks/base_hook.py +37 -0
- trilogy/hooks/graph_hook.py +24 -0
- trilogy/hooks/query_debugger.py +133 -0
- trilogy/metadata/__init__.py +0 -0
- trilogy/parser.py +10 -0
- trilogy/parsing/__init__.py +0 -0
- trilogy/parsing/common.py +176 -0
- trilogy/parsing/config.py +5 -0
- trilogy/parsing/exceptions.py +2 -0
- trilogy/parsing/helpers.py +1 -0
- trilogy/parsing/parse_engine.py +1951 -0
- trilogy/parsing/render.py +483 -0
- trilogy/py.typed +0 -0
- trilogy/scripts/__init__.py +0 -0
- trilogy/scripts/trilogy.py +127 -0
- trilogy/utility.py +31 -0
trilogy/core/enums.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
InfiniteFunctionArgs = -1
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UnnestMode(Enum):
|
|
7
|
+
DIRECT = "direct"
|
|
8
|
+
CROSS_APPLY = "cross_apply"
|
|
9
|
+
CROSS_JOIN = "cross_join"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConceptSource(Enum):
|
|
13
|
+
MANUAL = "manual"
|
|
14
|
+
AUTO_DERIVED = "auto_derived"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StatementType(Enum):
|
|
18
|
+
QUERY = "query"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Purpose(Enum):
|
|
22
|
+
CONSTANT = "const"
|
|
23
|
+
KEY = "key"
|
|
24
|
+
PROPERTY = "property"
|
|
25
|
+
METRIC = "metric"
|
|
26
|
+
ROWSET = "rowset"
|
|
27
|
+
AUTO = "auto"
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def _missing_(cls, value):
|
|
31
|
+
if value == "constant":
|
|
32
|
+
return Purpose.CONSTANT
|
|
33
|
+
return super()._missing_(value)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PurposeLineage(Enum):
|
|
37
|
+
BASIC = "basic"
|
|
38
|
+
WINDOW = "window"
|
|
39
|
+
AGGREGATE = "aggregate"
|
|
40
|
+
FILTER = "filter"
|
|
41
|
+
CONSTANT = "constant"
|
|
42
|
+
UNNEST = "unnest"
|
|
43
|
+
ROOT = "root"
|
|
44
|
+
ROWSET = "rowset"
|
|
45
|
+
MULTISELECT = "multiselect"
|
|
46
|
+
MERGE = "merge"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Granularity(Enum):
|
|
50
|
+
SINGLE_ROW = "single_row"
|
|
51
|
+
MULTI_ROW = "multi_row"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Modifier(Enum):
|
|
55
|
+
PARTIAL = "Partial"
|
|
56
|
+
OPTIONAL = "Optional"
|
|
57
|
+
HIDDEN = "Hidden"
|
|
58
|
+
NULLABLE = "Nullable"
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def _missing_(cls, value):
|
|
62
|
+
strval = str(value)
|
|
63
|
+
if strval == "~":
|
|
64
|
+
return Modifier.PARTIAL
|
|
65
|
+
return super()._missing_(value=strval.capitalize())
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class JoinType(Enum):
|
|
69
|
+
INNER = "inner"
|
|
70
|
+
LEFT_OUTER = "left outer"
|
|
71
|
+
FULL = "full"
|
|
72
|
+
RIGHT_OUTER = "right outer"
|
|
73
|
+
CROSS = "cross"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Ordering(Enum):
|
|
77
|
+
ASCENDING = "asc"
|
|
78
|
+
DESCENDING = "desc"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class WindowType(Enum):
|
|
82
|
+
ROW_NUMBER = "row_number"
|
|
83
|
+
RANK = "rank"
|
|
84
|
+
LAG = "lag"
|
|
85
|
+
LEAD = "lead"
|
|
86
|
+
SUM = "sum"
|
|
87
|
+
MAX = "max"
|
|
88
|
+
MIN = "min"
|
|
89
|
+
AVG = "avg"
|
|
90
|
+
COUNT = "count"
|
|
91
|
+
COUNT_DISTINCT = "count_distinct"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class WindowOrder(Enum):
|
|
95
|
+
ASCENDING = "top"
|
|
96
|
+
DESCENDING = "bottom"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class FunctionType(Enum):
|
|
100
|
+
# custom
|
|
101
|
+
CUSTOM = "custom"
|
|
102
|
+
|
|
103
|
+
# structural
|
|
104
|
+
UNNEST = "unnest"
|
|
105
|
+
ALIAS = "alias"
|
|
106
|
+
|
|
107
|
+
# Generic
|
|
108
|
+
CASE = "case"
|
|
109
|
+
CAST = "cast"
|
|
110
|
+
CONCAT = "concat"
|
|
111
|
+
CONSTANT = "constant"
|
|
112
|
+
COALESCE = "coalesce"
|
|
113
|
+
IS_NULL = "isnull"
|
|
114
|
+
|
|
115
|
+
# COMPLEX
|
|
116
|
+
INDEX_ACCESS = "index_access"
|
|
117
|
+
ATTR_ACCESS = "attr_access"
|
|
118
|
+
|
|
119
|
+
# TEXT AND MAYBE MORE
|
|
120
|
+
SPLIT = "split"
|
|
121
|
+
|
|
122
|
+
# Math
|
|
123
|
+
DIVIDE = "divide"
|
|
124
|
+
MULTIPLY = "multiply"
|
|
125
|
+
ADD = "add"
|
|
126
|
+
SUBTRACT = "subtract"
|
|
127
|
+
MOD = "mod"
|
|
128
|
+
ROUND = "round"
|
|
129
|
+
ABS = "abs"
|
|
130
|
+
|
|
131
|
+
# Aggregates
|
|
132
|
+
## group is not a real aggregate - it just means group by this + some other set of fields
|
|
133
|
+
## but is here as syntax is identical
|
|
134
|
+
GROUP = "group"
|
|
135
|
+
|
|
136
|
+
COUNT = "count"
|
|
137
|
+
COUNT_DISTINCT = "count_distinct"
|
|
138
|
+
SUM = "sum"
|
|
139
|
+
MAX = "max"
|
|
140
|
+
MIN = "min"
|
|
141
|
+
AVG = "avg"
|
|
142
|
+
LENGTH = "len"
|
|
143
|
+
|
|
144
|
+
# String
|
|
145
|
+
LIKE = "like"
|
|
146
|
+
ILIKE = "ilike"
|
|
147
|
+
LOWER = "lower"
|
|
148
|
+
UPPER = "upper"
|
|
149
|
+
SUBSTRING = "substring"
|
|
150
|
+
STRPOS = "strpos"
|
|
151
|
+
|
|
152
|
+
# Dates
|
|
153
|
+
DATE = "date"
|
|
154
|
+
DATETIME = "datetime"
|
|
155
|
+
TIMESTAMP = "timestamp"
|
|
156
|
+
|
|
157
|
+
# time
|
|
158
|
+
SECOND = "second"
|
|
159
|
+
MINUTE = "minute"
|
|
160
|
+
HOUR = "hour"
|
|
161
|
+
DAY = "day"
|
|
162
|
+
DAY_OF_WEEK = "day_of_week"
|
|
163
|
+
WEEK = "week"
|
|
164
|
+
MONTH = "month"
|
|
165
|
+
QUARTER = "quarter"
|
|
166
|
+
YEAR = "year"
|
|
167
|
+
|
|
168
|
+
DATE_PART = "date_part"
|
|
169
|
+
DATE_TRUNCATE = "date_truncate"
|
|
170
|
+
DATE_ADD = "date_add"
|
|
171
|
+
DATE_DIFF = "date_diff"
|
|
172
|
+
|
|
173
|
+
# UNIX
|
|
174
|
+
UNIX_TO_TIMESTAMP = "unix_to_timestamp"
|
|
175
|
+
|
|
176
|
+
# CONSTANTS
|
|
177
|
+
CURRENT_DATE = "current_date"
|
|
178
|
+
CURRENT_DATETIME = "current_datetime"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class FunctionClass(Enum):
|
|
182
|
+
AGGREGATE_FUNCTIONS = [
|
|
183
|
+
FunctionType.MAX,
|
|
184
|
+
FunctionType.MIN,
|
|
185
|
+
FunctionType.SUM,
|
|
186
|
+
FunctionType.AVG,
|
|
187
|
+
FunctionType.COUNT,
|
|
188
|
+
FunctionType.COUNT_DISTINCT,
|
|
189
|
+
]
|
|
190
|
+
SINGLE_ROW = [
|
|
191
|
+
FunctionType.CONSTANT,
|
|
192
|
+
FunctionType.CURRENT_DATE,
|
|
193
|
+
FunctionType.CURRENT_DATETIME,
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class Boolean(Enum):
|
|
198
|
+
TRUE = "true"
|
|
199
|
+
FALSE = "false"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class BooleanOperator(Enum):
|
|
203
|
+
AND = "and"
|
|
204
|
+
OR = "or"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class ComparisonOperator(Enum):
|
|
208
|
+
LT = "<"
|
|
209
|
+
GT = ">"
|
|
210
|
+
EQ = "="
|
|
211
|
+
IS = "is"
|
|
212
|
+
IS_NOT = "is not"
|
|
213
|
+
GTE = ">="
|
|
214
|
+
LTE = "<="
|
|
215
|
+
NE = "!="
|
|
216
|
+
IN = "in"
|
|
217
|
+
NOT_IN = "not in"
|
|
218
|
+
# TODO: deprecate for contains?
|
|
219
|
+
LIKE = "like"
|
|
220
|
+
ILIKE = "ilike"
|
|
221
|
+
CONTAINS = "contains"
|
|
222
|
+
ELSE = "else"
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
def _missing_(cls, value):
|
|
226
|
+
if not isinstance(value, list) and " " in str(value):
|
|
227
|
+
value = str(value).split()
|
|
228
|
+
if isinstance(value, list):
|
|
229
|
+
processed = [str(v).lower() for v in value]
|
|
230
|
+
if processed == ["not", "in"]:
|
|
231
|
+
return ComparisonOperator.NOT_IN
|
|
232
|
+
if processed == ["is", "not"]:
|
|
233
|
+
return ComparisonOperator.IS_NOT
|
|
234
|
+
if value == ["in"]:
|
|
235
|
+
return ComparisonOperator.IN
|
|
236
|
+
return super()._missing_(str(value).lower())
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class DatePart(Enum):
|
|
240
|
+
MONTH = "month"
|
|
241
|
+
YEAR = "year"
|
|
242
|
+
WEEK = "week"
|
|
243
|
+
DAY = "day"
|
|
244
|
+
QUARTER = "quarter"
|
|
245
|
+
HOUR = "hour"
|
|
246
|
+
MINUTE = "minute"
|
|
247
|
+
SECOND = "second"
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def _missing_(cls, value):
|
|
251
|
+
if isinstance(value, str) and value.lower() != value:
|
|
252
|
+
return DatePart(value.lower())
|
|
253
|
+
return super()._missing_(value)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class SourceType(Enum):
|
|
257
|
+
FILTER = "filter"
|
|
258
|
+
SELECT = "select"
|
|
259
|
+
MERGE = "merge"
|
|
260
|
+
ABSTRACT = "abstract"
|
|
261
|
+
DIRECT_SELECT = "direct_select"
|
|
262
|
+
GROUP = "group"
|
|
263
|
+
WINDOW = "window"
|
|
264
|
+
UNNEST = "unnest"
|
|
265
|
+
CONSTANT = "constant"
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class ShowCategory(Enum):
|
|
269
|
+
MODELS = "models"
|
|
270
|
+
CONCEPTS = "concepts"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from trilogy.core.graph_models import ReferenceGraph, concept_to_node, datasource_to_node
|
|
2
|
+
from trilogy.core.models import Environment
|
|
3
|
+
from trilogy.core.enums import PurposeLineage
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def generate_graph(
|
|
7
|
+
environment: Environment,
|
|
8
|
+
) -> ReferenceGraph:
|
|
9
|
+
g = ReferenceGraph()
|
|
10
|
+
|
|
11
|
+
# add all parsed concepts
|
|
12
|
+
for _, concept in environment.concepts.items():
|
|
13
|
+
g.add_node(concept)
|
|
14
|
+
# if we have sources, recursively add them
|
|
15
|
+
if concept.sources:
|
|
16
|
+
node_name = concept_to_node(concept)
|
|
17
|
+
for source in concept.sources:
|
|
18
|
+
generic = source.with_default_grain()
|
|
19
|
+
g.add_edge(generic, node_name)
|
|
20
|
+
if concept.derivation == PurposeLineage.MERGE:
|
|
21
|
+
g.add_edge(node_name, generic)
|
|
22
|
+
for _, dataset in environment.datasources.items():
|
|
23
|
+
node = datasource_to_node(dataset)
|
|
24
|
+
g.add_node(dataset, type="datasource", datasource=dataset)
|
|
25
|
+
for concept in dataset.concepts:
|
|
26
|
+
g.add_edge(node, concept)
|
|
27
|
+
g.add_edge(concept, node)
|
|
28
|
+
# if there is a key on a table at a different grain
|
|
29
|
+
# add an FK edge to the canonical source, if it exists
|
|
30
|
+
# for example, order ID on order product table
|
|
31
|
+
g.add_edge(concept, concept.with_default_grain())
|
|
32
|
+
g.add_edge(concept.with_default_grain(), concept)
|
|
33
|
+
return g
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from trilogy.core.models import DataType, Concept, Environment, Function, Metadata
|
|
2
|
+
from trilogy.core.enums import Purpose, FunctionType, ConceptSource
|
|
3
|
+
from trilogy.constants import DEFAULT_NAMESPACE
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def generate_date_concepts(concept: Concept, environment: Environment):
|
|
7
|
+
if concept.metadata and concept.metadata.description:
|
|
8
|
+
base_description = concept.metadata.description
|
|
9
|
+
else:
|
|
10
|
+
base_description = f"a {concept.datatype.value}"
|
|
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
|
+
for ftype in [
|
|
16
|
+
FunctionType.MONTH,
|
|
17
|
+
FunctionType.YEAR,
|
|
18
|
+
FunctionType.QUARTER,
|
|
19
|
+
FunctionType.DAY,
|
|
20
|
+
FunctionType.DAY_OF_WEEK,
|
|
21
|
+
]:
|
|
22
|
+
fname = ftype.name.lower()
|
|
23
|
+
default_type = (
|
|
24
|
+
Purpose.CONSTANT
|
|
25
|
+
if concept.purpose == Purpose.CONSTANT
|
|
26
|
+
else Purpose.PROPERTY
|
|
27
|
+
)
|
|
28
|
+
const_function: Function = Function(
|
|
29
|
+
operator=ftype,
|
|
30
|
+
output_datatype=DataType.INTEGER,
|
|
31
|
+
output_purpose=default_type,
|
|
32
|
+
arguments=[concept],
|
|
33
|
+
)
|
|
34
|
+
namespace = (
|
|
35
|
+
None if concept.namespace == DEFAULT_NAMESPACE else concept.namespace
|
|
36
|
+
)
|
|
37
|
+
new_concept = Concept(
|
|
38
|
+
name=f"{concept.name}.{fname}",
|
|
39
|
+
datatype=DataType.INTEGER,
|
|
40
|
+
purpose=default_type,
|
|
41
|
+
lineage=const_function,
|
|
42
|
+
grain=const_function.output_grain,
|
|
43
|
+
namespace=namespace,
|
|
44
|
+
keys=(concept,),
|
|
45
|
+
metadata=Metadata(
|
|
46
|
+
description=f"Auto-derived. Integer format. The {ftype.value} derived from {concept.name}, {base_description}",
|
|
47
|
+
line_number=base_line_number,
|
|
48
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
if new_concept.name in environment.concepts:
|
|
52
|
+
continue
|
|
53
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
57
|
+
if concept.metadata and concept.metadata.description:
|
|
58
|
+
base_description = concept.metadata.description
|
|
59
|
+
else:
|
|
60
|
+
base_description = f"a {concept.datatype.value}"
|
|
61
|
+
if concept.metadata and concept.metadata.line_number:
|
|
62
|
+
base_line_number = concept.metadata.line_number
|
|
63
|
+
else:
|
|
64
|
+
base_line_number = None
|
|
65
|
+
for ftype in [
|
|
66
|
+
FunctionType.DATE,
|
|
67
|
+
FunctionType.HOUR,
|
|
68
|
+
FunctionType.MINUTE,
|
|
69
|
+
FunctionType.SECOND,
|
|
70
|
+
]:
|
|
71
|
+
fname = ftype.name.lower()
|
|
72
|
+
default_type = (
|
|
73
|
+
Purpose.CONSTANT
|
|
74
|
+
if concept.purpose == Purpose.CONSTANT
|
|
75
|
+
else Purpose.PROPERTY
|
|
76
|
+
)
|
|
77
|
+
const_function: Function = Function(
|
|
78
|
+
operator=ftype,
|
|
79
|
+
output_datatype=DataType.INTEGER,
|
|
80
|
+
output_purpose=default_type,
|
|
81
|
+
arguments=[concept],
|
|
82
|
+
)
|
|
83
|
+
namespace = (
|
|
84
|
+
None if concept.namespace == DEFAULT_NAMESPACE else concept.namespace
|
|
85
|
+
)
|
|
86
|
+
new_concept = Concept(
|
|
87
|
+
name=f"{concept.name}.{fname}",
|
|
88
|
+
datatype=DataType.INTEGER,
|
|
89
|
+
purpose=default_type,
|
|
90
|
+
lineage=const_function,
|
|
91
|
+
grain=const_function.output_grain,
|
|
92
|
+
namespace=namespace,
|
|
93
|
+
keys=(concept,),
|
|
94
|
+
metadata=Metadata(
|
|
95
|
+
description=f"Auto-derived. Integer format. The {ftype.value} derived from {concept.name}, {base_description}",
|
|
96
|
+
line_number=base_line_number,
|
|
97
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
if new_concept.name in environment.concepts:
|
|
101
|
+
continue
|
|
102
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def generate_key_concepts(concept: Concept, environment: Environment):
|
|
106
|
+
if concept.metadata and concept.metadata.description:
|
|
107
|
+
base_description = concept.metadata.description
|
|
108
|
+
else:
|
|
109
|
+
base_description = f"a {concept.datatype.value}"
|
|
110
|
+
if concept.metadata and concept.metadata.line_number:
|
|
111
|
+
base_line_number = concept.metadata.line_number
|
|
112
|
+
else:
|
|
113
|
+
base_line_number = None
|
|
114
|
+
for ftype in [FunctionType.COUNT]:
|
|
115
|
+
fname = ftype.name.lower()
|
|
116
|
+
default_type = Purpose.METRIC
|
|
117
|
+
const_function: Function = Function(
|
|
118
|
+
operator=ftype,
|
|
119
|
+
output_datatype=DataType.INTEGER,
|
|
120
|
+
output_purpose=default_type,
|
|
121
|
+
arguments=[concept],
|
|
122
|
+
)
|
|
123
|
+
namespace = (
|
|
124
|
+
None if concept.namespace == DEFAULT_NAMESPACE else concept.namespace
|
|
125
|
+
)
|
|
126
|
+
new_concept = Concept(
|
|
127
|
+
name=f"{concept.name}.{fname}",
|
|
128
|
+
datatype=DataType.INTEGER,
|
|
129
|
+
purpose=default_type,
|
|
130
|
+
lineage=const_function,
|
|
131
|
+
grain=const_function.output_grain,
|
|
132
|
+
namespace=namespace,
|
|
133
|
+
keys=(concept,),
|
|
134
|
+
metadata=Metadata(
|
|
135
|
+
description=f"Auto-derived. Integer format. The {ftype.value} derived from {concept.name}, {base_description}",
|
|
136
|
+
line_number=base_line_number,
|
|
137
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
if new_concept.name in environment.concepts:
|
|
141
|
+
continue
|
|
142
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def generate_related_concepts(concept: Concept, environment: Environment):
|
|
146
|
+
"""Auto populate common derived concepts on types"""
|
|
147
|
+
if concept.purpose == Purpose.KEY:
|
|
148
|
+
generate_key_concepts(concept, environment)
|
|
149
|
+
if concept.datatype == DataType.DATE:
|
|
150
|
+
generate_date_concepts(concept, environment)
|
|
151
|
+
elif concept.datatype == DataType.DATETIME:
|
|
152
|
+
generate_date_concepts(concept, environment)
|
|
153
|
+
generate_datetime_concepts(concept, environment)
|
|
154
|
+
elif concept.datatype == DataType.TIMESTAMP:
|
|
155
|
+
generate_date_concepts(concept, environment)
|
|
156
|
+
generate_datetime_concepts(concept, environment)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# source: https://github.com/aaronbassett/Pass-phrase
|
|
2
|
+
CTE_NAMES = """quizzical
|
|
3
|
+
highfalutin
|
|
4
|
+
dynamic
|
|
5
|
+
wakeful
|
|
6
|
+
cheerful
|
|
7
|
+
thoughtful
|
|
8
|
+
cooperative
|
|
9
|
+
questionable
|
|
10
|
+
abundant
|
|
11
|
+
uneven
|
|
12
|
+
yummy
|
|
13
|
+
juicy
|
|
14
|
+
vacuous
|
|
15
|
+
concerned
|
|
16
|
+
young
|
|
17
|
+
sparkling
|
|
18
|
+
abhorrent
|
|
19
|
+
sweltering
|
|
20
|
+
late
|
|
21
|
+
macho
|
|
22
|
+
scrawny
|
|
23
|
+
friendly
|
|
24
|
+
kaput
|
|
25
|
+
divergent
|
|
26
|
+
busy
|
|
27
|
+
charming
|
|
28
|
+
protective
|
|
29
|
+
premium
|
|
30
|
+
puzzled
|
|
31
|
+
waggish
|
|
32
|
+
rambunctious
|
|
33
|
+
puffy
|
|
34
|
+
hard
|
|
35
|
+
sedate
|
|
36
|
+
yellow
|
|
37
|
+
resonant
|
|
38
|
+
dapper
|
|
39
|
+
courageous
|
|
40
|
+
vast
|
|
41
|
+
cool
|
|
42
|
+
elated
|
|
43
|
+
wary
|
|
44
|
+
bewildered
|
|
45
|
+
level
|
|
46
|
+
wooden
|
|
47
|
+
ceaseless
|
|
48
|
+
tearful
|
|
49
|
+
cloudy
|
|
50
|
+
gullible
|
|
51
|
+
flashy
|
|
52
|
+
trite
|
|
53
|
+
quick
|
|
54
|
+
nondescript
|
|
55
|
+
round
|
|
56
|
+
slow
|
|
57
|
+
spiritual
|
|
58
|
+
brave
|
|
59
|
+
tenuous
|
|
60
|
+
abstracted
|
|
61
|
+
colossal
|
|
62
|
+
sloppy
|
|
63
|
+
obsolete
|
|
64
|
+
elegant
|
|
65
|
+
fabulous
|
|
66
|
+
vivacious
|
|
67
|
+
exuberant
|
|
68
|
+
faithful
|
|
69
|
+
helpless
|
|
70
|
+
odd
|
|
71
|
+
sordid
|
|
72
|
+
blue
|
|
73
|
+
imported
|
|
74
|
+
ugly
|
|
75
|
+
ruthless
|
|
76
|
+
deeply
|
|
77
|
+
eminent
|
|
78
|
+
badger
|
|
79
|
+
barracuda
|
|
80
|
+
bear
|
|
81
|
+
boa
|
|
82
|
+
cheetah
|
|
83
|
+
chimpanzee
|
|
84
|
+
civet
|
|
85
|
+
cobra
|
|
86
|
+
cougar
|
|
87
|
+
coyote
|
|
88
|
+
crocodile
|
|
89
|
+
dingo
|
|
90
|
+
eagle
|
|
91
|
+
eel
|
|
92
|
+
fossa
|
|
93
|
+
fox
|
|
94
|
+
human
|
|
95
|
+
jackal
|
|
96
|
+
jaguar
|
|
97
|
+
komodo
|
|
98
|
+
leopard
|
|
99
|
+
lion
|
|
100
|
+
lynx
|
|
101
|
+
mamba
|
|
102
|
+
mandrill
|
|
103
|
+
marlin
|
|
104
|
+
monitor
|
|
105
|
+
ocelot
|
|
106
|
+
osprey
|
|
107
|
+
owl
|
|
108
|
+
petrel
|
|
109
|
+
python
|
|
110
|
+
ray
|
|
111
|
+
salamander
|
|
112
|
+
serval
|
|
113
|
+
shark
|
|
114
|
+
skua
|
|
115
|
+
tiger
|
|
116
|
+
viper
|
|
117
|
+
wolf
|
|
118
|
+
wolverine
|
|
119
|
+
albatross
|
|
120
|
+
avocet
|
|
121
|
+
budgie
|
|
122
|
+
canary
|
|
123
|
+
chick
|
|
124
|
+
chickadee
|
|
125
|
+
chicken
|
|
126
|
+
cockatiel
|
|
127
|
+
cockatoo
|
|
128
|
+
coot
|
|
129
|
+
covey
|
|
130
|
+
crow
|
|
131
|
+
cuckoo
|
|
132
|
+
darter
|
|
133
|
+
dove
|
|
134
|
+
duck
|
|
135
|
+
eagle
|
|
136
|
+
falcon
|
|
137
|
+
finch
|
|
138
|
+
flamingo
|
|
139
|
+
fowl
|
|
140
|
+
goldfinch
|
|
141
|
+
goose
|
|
142
|
+
grouse
|
|
143
|
+
hawk
|
|
144
|
+
heron
|
|
145
|
+
jackdaw
|
|
146
|
+
jay
|
|
147
|
+
kestrel
|
|
148
|
+
lark
|
|
149
|
+
loon
|
|
150
|
+
macaw
|
|
151
|
+
magpie
|
|
152
|
+
martin
|
|
153
|
+
osprey
|
|
154
|
+
ostrich
|
|
155
|
+
owl
|
|
156
|
+
parakeet
|
|
157
|
+
parrot
|
|
158
|
+
pelican
|
|
159
|
+
penguin
|
|
160
|
+
pigeon
|
|
161
|
+
pintail
|
|
162
|
+
puffin
|
|
163
|
+
quail
|
|
164
|
+
quetzal
|
|
165
|
+
rail
|
|
166
|
+
raven
|
|
167
|
+
razorbill
|
|
168
|
+
rhea
|
|
169
|
+
rook
|
|
170
|
+
shrike
|
|
171
|
+
skylark
|
|
172
|
+
snipe
|
|
173
|
+
sparrow
|
|
174
|
+
starling
|
|
175
|
+
stork
|
|
176
|
+
swallow
|
|
177
|
+
swift
|
|
178
|
+
tanager
|
|
179
|
+
thrush
|
|
180
|
+
toucan
|
|
181
|
+
turkey
|
|
182
|
+
vulture
|
|
183
|
+
warbler""".split(
|
|
184
|
+
"\n"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
CTE_NAMES = list(set(CTE_NAMES))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UndefinedConceptException(Exception):
|
|
5
|
+
def __init__(self, message, suggestions: List[str]):
|
|
6
|
+
super().__init__(self, message)
|
|
7
|
+
self.message = message
|
|
8
|
+
self.suggestions = suggestions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class InvalidSyntaxException(Exception):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NoDatasourceException(Exception):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AmbiguousRelationshipResolutionException(Exception):
|
|
20
|
+
def __init__(self, message, parents: List[set[str]]):
|
|
21
|
+
super().__init__(self, message)
|
|
22
|
+
self.message = message
|
|
23
|
+
self.parents = parents
|