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
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