pytrilogy 0.0.3.95__py3-none-any.whl → 0.0.3.97__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.3.95.dist-info → pytrilogy-0.0.3.97.dist-info}/METADATA +44 -7
- {pytrilogy-0.0.3.95.dist-info → pytrilogy-0.0.3.97.dist-info}/RECORD +24 -23
- trilogy/__init__.py +1 -1
- trilogy/authoring/__init__.py +59 -45
- trilogy/constants.py +1 -0
- trilogy/core/enums.py +9 -0
- trilogy/core/exceptions.py +56 -2
- trilogy/core/graph_models.py +4 -4
- trilogy/core/statements/execute.py +2 -0
- trilogy/core/validation/common.py +55 -3
- trilogy/core/validation/concept.py +40 -25
- trilogy/core/validation/datasource.py +38 -34
- trilogy/core/validation/environment.py +4 -3
- trilogy/core/validation/fix.py +106 -0
- trilogy/dialect/base.py +10 -1
- trilogy/dialect/metadata.py +233 -0
- trilogy/executor.py +33 -163
- trilogy/parsing/parse_engine.py +8 -6
- trilogy/parsing/render.py +30 -3
- trilogy/parsing/trilogy.lark +7 -4
- trilogy/compiler.py +0 -0
- {pytrilogy-0.0.3.95.dist-info → pytrilogy-0.0.3.97.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.95.dist-info → pytrilogy-0.0.3.97.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.95.dist-info → pytrilogy-0.0.3.97.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.95.dist-info → pytrilogy-0.0.3.97.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, List, Optional
|
|
3
|
+
|
|
4
|
+
from trilogy.core.models.author import ConceptRef
|
|
5
|
+
from trilogy.core.models.datasource import Datasource
|
|
6
|
+
from trilogy.core.models.environment import Environment
|
|
7
|
+
from trilogy.core.statements.author import (
|
|
8
|
+
ConceptDeclarationStatement,
|
|
9
|
+
ImportStatement,
|
|
10
|
+
MergeStatementV2,
|
|
11
|
+
)
|
|
12
|
+
from trilogy.core.statements.execute import (
|
|
13
|
+
ProcessedShowStatement,
|
|
14
|
+
ProcessedStaticValueOutput,
|
|
15
|
+
ProcessedValidateStatement,
|
|
16
|
+
)
|
|
17
|
+
from trilogy.core.validation.common import ValidationTest
|
|
18
|
+
from trilogy.dialect.base import BaseDialect
|
|
19
|
+
from trilogy.engine import ResultProtocol
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class MockResult(ResultProtocol):
|
|
24
|
+
values: list["MockResultRow"]
|
|
25
|
+
columns: list[str]
|
|
26
|
+
|
|
27
|
+
def __init__(self, values: list[Any], columns: list[str]):
|
|
28
|
+
processed: list[MockResultRow] = []
|
|
29
|
+
for x in values:
|
|
30
|
+
if isinstance(x, dict):
|
|
31
|
+
processed.append(MockResultRow(x))
|
|
32
|
+
elif isinstance(x, MockResultRow):
|
|
33
|
+
processed.append(x)
|
|
34
|
+
else:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"Cannot process value of type {type(x)} in MockResult"
|
|
37
|
+
)
|
|
38
|
+
self.columns = columns
|
|
39
|
+
self.values = processed
|
|
40
|
+
|
|
41
|
+
def __iter__(self):
|
|
42
|
+
while self.values:
|
|
43
|
+
yield self.values.pop(0)
|
|
44
|
+
|
|
45
|
+
def fetchall(self):
|
|
46
|
+
return self.values
|
|
47
|
+
|
|
48
|
+
def fetchone(self):
|
|
49
|
+
if self.values:
|
|
50
|
+
return self.values.pop(0)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def fetchmany(self, size: int):
|
|
54
|
+
rval = self.values[:size]
|
|
55
|
+
self.values = self.values[size:]
|
|
56
|
+
return rval
|
|
57
|
+
|
|
58
|
+
def keys(self):
|
|
59
|
+
return self.columns
|
|
60
|
+
|
|
61
|
+
def as_dict(self):
|
|
62
|
+
return [x.as_dict() if isinstance(x, MockResultRow) else x for x in self.values]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class MockResultRow:
|
|
67
|
+
_values: dict[str, Any]
|
|
68
|
+
|
|
69
|
+
def as_dict(self):
|
|
70
|
+
return self._values
|
|
71
|
+
|
|
72
|
+
def __str__(self) -> str:
|
|
73
|
+
return str(self._values)
|
|
74
|
+
|
|
75
|
+
def __repr__(self) -> str:
|
|
76
|
+
return repr(self._values)
|
|
77
|
+
|
|
78
|
+
def __getattr__(self, name: str) -> Any:
|
|
79
|
+
if name in self._values:
|
|
80
|
+
return self._values[name]
|
|
81
|
+
return super().__getattribute__(name)
|
|
82
|
+
|
|
83
|
+
def __getitem__(self, key: str) -> Any:
|
|
84
|
+
return self._values[key]
|
|
85
|
+
|
|
86
|
+
def values(self):
|
|
87
|
+
return self._values.values()
|
|
88
|
+
|
|
89
|
+
def keys(self):
|
|
90
|
+
return self._values.keys()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def generate_result_set(
|
|
94
|
+
columns: List[ConceptRef], output_data: list[Any]
|
|
95
|
+
) -> MockResult:
|
|
96
|
+
"""Generate a mock result set from columns and output data."""
|
|
97
|
+
names = [x.address.replace(".", "_") for x in columns]
|
|
98
|
+
return MockResult(
|
|
99
|
+
values=[dict(zip(names, [row])) for row in output_data], columns=names
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def handle_concept_declaration(query: ConceptDeclarationStatement) -> MockResult:
|
|
104
|
+
"""Handle concept declaration statements without execution."""
|
|
105
|
+
concept = query.concept
|
|
106
|
+
return MockResult(
|
|
107
|
+
[
|
|
108
|
+
{
|
|
109
|
+
"address": concept.address,
|
|
110
|
+
"type": concept.datatype.value,
|
|
111
|
+
"purpose": concept.purpose.value,
|
|
112
|
+
"derivation": concept.derivation.value,
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
["address", "type", "purpose", "derivation"],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def handle_datasource(query: Datasource) -> MockResult:
|
|
120
|
+
"""Handle datasource queries without execution."""
|
|
121
|
+
return MockResult(
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
"name": query.name,
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
["name"],
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def handle_import_statement(query: ImportStatement) -> MockResult:
|
|
132
|
+
"""Handle import statements without execution."""
|
|
133
|
+
return MockResult(
|
|
134
|
+
[
|
|
135
|
+
{
|
|
136
|
+
"path": query.path,
|
|
137
|
+
"alias": query.alias,
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
["path", "alias"],
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def handle_merge_statement(
|
|
145
|
+
query: MergeStatementV2, environment: Environment
|
|
146
|
+
) -> MockResult:
|
|
147
|
+
"""Handle merge statements by updating environment and returning result."""
|
|
148
|
+
for concept in query.sources:
|
|
149
|
+
environment.merge_concept(
|
|
150
|
+
concept, query.targets[concept.address], modifiers=query.modifiers
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return MockResult(
|
|
154
|
+
[
|
|
155
|
+
{
|
|
156
|
+
"sources": ",".join([x.address for x in query.sources]),
|
|
157
|
+
"targets": ",".join([x.address for _, x in query.targets.items()]),
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
["source", "target"],
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def handle_processed_show_statement(
|
|
165
|
+
query: ProcessedShowStatement, compiled_statements: list[str]
|
|
166
|
+
) -> MockResult:
|
|
167
|
+
"""Handle processed show statements without execution."""
|
|
168
|
+
|
|
169
|
+
return generate_result_set(query.output_columns, compiled_statements)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def raw_validation_to_result(
|
|
173
|
+
raw: list[ValidationTest], generator: Optional[BaseDialect] = None
|
|
174
|
+
) -> Optional[MockResult]:
|
|
175
|
+
"""Convert raw validation tests to mock result."""
|
|
176
|
+
if not raw:
|
|
177
|
+
return None
|
|
178
|
+
output = []
|
|
179
|
+
for row in raw:
|
|
180
|
+
if row.raw_query and generator and not row.generated_query:
|
|
181
|
+
try:
|
|
182
|
+
row.generated_query = generator.compile_statement(row.raw_query)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
row.generated_query = f"Error generating query: {e}"
|
|
185
|
+
output.append(
|
|
186
|
+
{
|
|
187
|
+
"check_type": row.check_type.value,
|
|
188
|
+
"expected": row.expected,
|
|
189
|
+
"result": str(row.result) if row.result else None,
|
|
190
|
+
"ran": row.ran,
|
|
191
|
+
"query": row.generated_query if row.generated_query else "",
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
return MockResult(output, ["check_type", "expected", "result", "ran", "query"])
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def handle_processed_validate_statement(
|
|
198
|
+
query: ProcessedValidateStatement, dialect: BaseDialect, validate_environment_func
|
|
199
|
+
) -> Optional[MockResult]:
|
|
200
|
+
"""Handle processed validate statements."""
|
|
201
|
+
results = validate_environment_func(query.scope, query.targets)
|
|
202
|
+
return raw_validation_to_result(results, dialect)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def handle_show_statement_outputs(
|
|
206
|
+
statement: ProcessedShowStatement,
|
|
207
|
+
compiled_statements: list[str],
|
|
208
|
+
environment: Environment,
|
|
209
|
+
dialect: BaseDialect,
|
|
210
|
+
) -> list[MockResult]:
|
|
211
|
+
"""Handle show statement outputs without execution."""
|
|
212
|
+
output = []
|
|
213
|
+
for x in statement.output_values:
|
|
214
|
+
if isinstance(x, ProcessedStaticValueOutput):
|
|
215
|
+
output.append(generate_result_set(statement.output_columns, x.values))
|
|
216
|
+
elif compiled_statements:
|
|
217
|
+
|
|
218
|
+
output.append(
|
|
219
|
+
generate_result_set(
|
|
220
|
+
statement.output_columns,
|
|
221
|
+
compiled_statements,
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
elif isinstance(x, ProcessedValidateStatement):
|
|
225
|
+
from trilogy.core.validation.environment import validate_environment
|
|
226
|
+
|
|
227
|
+
raw = validate_environment(environment, x.scope, x.targets)
|
|
228
|
+
results = raw_validation_to_result(raw, dialect)
|
|
229
|
+
if results:
|
|
230
|
+
output.append(results)
|
|
231
|
+
else:
|
|
232
|
+
raise NotImplementedError(f"Cannot show type {type(x)} in show statement")
|
|
233
|
+
return output
|
trilogy/executor.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
1
|
from functools import singledispatchmethod
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
from typing import Any, Generator, List, Optional
|
|
@@ -7,7 +6,7 @@ from sqlalchemy import text
|
|
|
7
6
|
|
|
8
7
|
from trilogy.constants import MagicConstants, Rendering, logger
|
|
9
8
|
from trilogy.core.enums import FunctionType, Granularity, IOType, ValidationScope
|
|
10
|
-
from trilogy.core.models.author import Concept,
|
|
9
|
+
from trilogy.core.models.author import Concept, Function
|
|
11
10
|
from trilogy.core.models.build import BuildFunction
|
|
12
11
|
from trilogy.core.models.core import ListWrapper, MapWrapper
|
|
13
12
|
from trilogy.core.models.datasource import Datasource
|
|
@@ -31,7 +30,6 @@ from trilogy.core.statements.execute import (
|
|
|
31
30
|
ProcessedQueryPersist,
|
|
32
31
|
ProcessedRawSQLStatement,
|
|
33
32
|
ProcessedShowStatement,
|
|
34
|
-
ProcessedStaticValueOutput,
|
|
35
33
|
ProcessedValidateStatement,
|
|
36
34
|
)
|
|
37
35
|
from trilogy.core.validation.common import (
|
|
@@ -39,82 +37,22 @@ from trilogy.core.validation.common import (
|
|
|
39
37
|
)
|
|
40
38
|
from trilogy.dialect.base import BaseDialect
|
|
41
39
|
from trilogy.dialect.enums import Dialects
|
|
40
|
+
from trilogy.dialect.metadata import (
|
|
41
|
+
generate_result_set,
|
|
42
|
+
handle_concept_declaration,
|
|
43
|
+
handle_datasource,
|
|
44
|
+
handle_import_statement,
|
|
45
|
+
handle_merge_statement,
|
|
46
|
+
handle_processed_show_statement,
|
|
47
|
+
handle_processed_validate_statement,
|
|
48
|
+
handle_show_statement_outputs,
|
|
49
|
+
)
|
|
42
50
|
from trilogy.engine import ExecutionEngine, ResultProtocol
|
|
43
51
|
from trilogy.hooks.base_hook import BaseHook
|
|
44
52
|
from trilogy.parser import parse_text
|
|
45
53
|
from trilogy.render import get_dialect_generator
|
|
46
54
|
|
|
47
55
|
|
|
48
|
-
@dataclass
|
|
49
|
-
class MockResult(ResultProtocol):
|
|
50
|
-
values: list[Any]
|
|
51
|
-
columns: list[str]
|
|
52
|
-
|
|
53
|
-
def __init__(self, values: list[Any], columns: list[str]):
|
|
54
|
-
processed = []
|
|
55
|
-
for x in values:
|
|
56
|
-
if isinstance(x, dict):
|
|
57
|
-
processed.append(MockResultRow(x))
|
|
58
|
-
else:
|
|
59
|
-
processed.append(x)
|
|
60
|
-
self.columns = columns
|
|
61
|
-
self.values = processed
|
|
62
|
-
|
|
63
|
-
def __iter__(self):
|
|
64
|
-
while self.values:
|
|
65
|
-
yield self.values.pop(0)
|
|
66
|
-
|
|
67
|
-
def fetchall(self):
|
|
68
|
-
return self.values
|
|
69
|
-
|
|
70
|
-
def fetchone(self):
|
|
71
|
-
if self.values:
|
|
72
|
-
return self.values.pop(0)
|
|
73
|
-
return None
|
|
74
|
-
|
|
75
|
-
def fetchmany(self, size: int):
|
|
76
|
-
rval = self.values[:size]
|
|
77
|
-
self.values = self.values[size:]
|
|
78
|
-
return rval
|
|
79
|
-
|
|
80
|
-
def keys(self):
|
|
81
|
-
return self.columns
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@dataclass
|
|
85
|
-
class MockResultRow:
|
|
86
|
-
_values: dict[str, Any]
|
|
87
|
-
|
|
88
|
-
def __str__(self) -> str:
|
|
89
|
-
return str(self._values)
|
|
90
|
-
|
|
91
|
-
def __repr__(self) -> str:
|
|
92
|
-
return repr(self._values)
|
|
93
|
-
|
|
94
|
-
def __getattr__(self, name: str) -> Any:
|
|
95
|
-
if name in self._values:
|
|
96
|
-
return self._values[name]
|
|
97
|
-
return super().__getattribute__(name)
|
|
98
|
-
|
|
99
|
-
def __getitem__(self, key: str) -> Any:
|
|
100
|
-
return self._values[key]
|
|
101
|
-
|
|
102
|
-
def values(self):
|
|
103
|
-
return self._values.values()
|
|
104
|
-
|
|
105
|
-
def keys(self):
|
|
106
|
-
return self._values.keys()
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def generate_result_set(
|
|
110
|
-
columns: List[ConceptRef], output_data: list[Any]
|
|
111
|
-
) -> MockResult:
|
|
112
|
-
names = [x.address.replace(".", "_") for x in columns]
|
|
113
|
-
return MockResult(
|
|
114
|
-
values=[dict(zip(names, [row])) for row in output_data], columns=names
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
|
|
118
56
|
class Executor(object):
|
|
119
57
|
def __init__(
|
|
120
58
|
self,
|
|
@@ -150,29 +88,11 @@ class Executor(object):
|
|
|
150
88
|
|
|
151
89
|
@execute_query.register
|
|
152
90
|
def _(self, query: ConceptDeclarationStatement) -> ResultProtocol | None:
|
|
153
|
-
|
|
154
|
-
return MockResult(
|
|
155
|
-
[
|
|
156
|
-
{
|
|
157
|
-
"address": concept.address,
|
|
158
|
-
"type": concept.datatype.value,
|
|
159
|
-
"purpose": concept.purpose.value,
|
|
160
|
-
"derivation": concept.derivation.value,
|
|
161
|
-
}
|
|
162
|
-
],
|
|
163
|
-
["address", "type", "purpose", "derivation"],
|
|
164
|
-
)
|
|
91
|
+
return handle_concept_declaration(query)
|
|
165
92
|
|
|
166
93
|
@execute_query.register
|
|
167
94
|
def _(self, query: Datasource) -> ResultProtocol | None:
|
|
168
|
-
return
|
|
169
|
-
[
|
|
170
|
-
{
|
|
171
|
-
"name": query.name,
|
|
172
|
-
}
|
|
173
|
-
],
|
|
174
|
-
["name"],
|
|
175
|
-
)
|
|
95
|
+
return handle_datasource(query)
|
|
176
96
|
|
|
177
97
|
@execute_query.register
|
|
178
98
|
def _(self, query: str) -> ResultProtocol | None:
|
|
@@ -208,66 +128,28 @@ class Executor(object):
|
|
|
208
128
|
|
|
209
129
|
@execute_query.register
|
|
210
130
|
def _(self, query: ProcessedShowStatement) -> ResultProtocol | None:
|
|
211
|
-
return
|
|
212
|
-
query
|
|
131
|
+
return handle_processed_show_statement(
|
|
132
|
+
query,
|
|
213
133
|
[
|
|
214
134
|
self.generator.compile_statement(x)
|
|
215
135
|
for x in query.output_values
|
|
216
|
-
if isinstance(x, ProcessedQuery)
|
|
136
|
+
if isinstance(x, (ProcessedQuery, ProcessedQueryPersist))
|
|
217
137
|
],
|
|
218
138
|
)
|
|
219
139
|
|
|
220
|
-
def _raw_validation_to_result(
|
|
221
|
-
self, raw: list[ValidationTest]
|
|
222
|
-
) -> Optional[ResultProtocol]:
|
|
223
|
-
if not raw:
|
|
224
|
-
return None
|
|
225
|
-
output = []
|
|
226
|
-
for row in raw:
|
|
227
|
-
output.append(
|
|
228
|
-
{
|
|
229
|
-
"check_type": row.check_type.value,
|
|
230
|
-
"expected": row.expected,
|
|
231
|
-
"result": str(row.result) if row.result else None,
|
|
232
|
-
"ran": row.ran,
|
|
233
|
-
"query": row.query if row.query else "",
|
|
234
|
-
}
|
|
235
|
-
)
|
|
236
|
-
return MockResult(output, ["check_type", "expected", "result", "ran", "query"])
|
|
237
|
-
|
|
238
140
|
@execute_query.register
|
|
239
141
|
def _(self, query: ProcessedValidateStatement) -> ResultProtocol | None:
|
|
240
|
-
|
|
241
|
-
|
|
142
|
+
return handle_processed_validate_statement(
|
|
143
|
+
query, self.generator, self.validate_environment
|
|
144
|
+
)
|
|
242
145
|
|
|
243
146
|
@execute_query.register
|
|
244
147
|
def _(self, query: ImportStatement) -> ResultProtocol | None:
|
|
245
|
-
return
|
|
246
|
-
[
|
|
247
|
-
{
|
|
248
|
-
"path": query.path,
|
|
249
|
-
"alias": query.alias,
|
|
250
|
-
}
|
|
251
|
-
],
|
|
252
|
-
["path", "alias"],
|
|
253
|
-
)
|
|
148
|
+
return handle_import_statement(query)
|
|
254
149
|
|
|
255
150
|
@execute_query.register
|
|
256
151
|
def _(self, query: MergeStatementV2) -> ResultProtocol | None:
|
|
257
|
-
|
|
258
|
-
self.environment.merge_concept(
|
|
259
|
-
concept, query.targets[concept.address], modifiers=query.modifiers
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
return MockResult(
|
|
263
|
-
[
|
|
264
|
-
{
|
|
265
|
-
"sources": ",".join([x.address for x in query.sources]),
|
|
266
|
-
"targets": ",".join([x.address for _, x in query.targets.items()]),
|
|
267
|
-
}
|
|
268
|
-
],
|
|
269
|
-
["source", "target"],
|
|
270
|
-
)
|
|
152
|
+
return handle_merge_statement(query, self.environment)
|
|
271
153
|
|
|
272
154
|
@execute_query.register
|
|
273
155
|
def _(self, query: ProcessedRawSQLStatement) -> ResultProtocol | None:
|
|
@@ -516,29 +398,17 @@ class Executor(object):
|
|
|
516
398
|
# connection = self.engine.connect()
|
|
517
399
|
for statement in self.parse_text_generator(command):
|
|
518
400
|
if isinstance(statement, ProcessedShowStatement):
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
)
|
|
531
|
-
elif isinstance(x, ProcessedValidateStatement):
|
|
532
|
-
raw = self.validate_environment(
|
|
533
|
-
x.scope, x.targets, generate_only=True
|
|
534
|
-
)
|
|
535
|
-
results = self._raw_validation_to_result(raw)
|
|
536
|
-
if results:
|
|
537
|
-
output.append(results)
|
|
538
|
-
else:
|
|
539
|
-
raise NotImplementedError(
|
|
540
|
-
f"Cannot show type {type(x)} in show statement"
|
|
541
|
-
)
|
|
401
|
+
results = handle_show_statement_outputs(
|
|
402
|
+
statement,
|
|
403
|
+
[
|
|
404
|
+
self.generator.compile_statement(x)
|
|
405
|
+
for x in statement.output_values
|
|
406
|
+
if isinstance(x, (ProcessedQuery, ProcessedQueryPersist))
|
|
407
|
+
],
|
|
408
|
+
self.environment,
|
|
409
|
+
self.generator,
|
|
410
|
+
)
|
|
411
|
+
output.extend(results)
|
|
542
412
|
continue
|
|
543
413
|
if non_interactive:
|
|
544
414
|
if not isinstance(
|
|
@@ -567,5 +437,5 @@ class Executor(object):
|
|
|
567
437
|
from trilogy.core.validation.environment import validate_environment
|
|
568
438
|
|
|
569
439
|
return validate_environment(
|
|
570
|
-
self.environment,
|
|
440
|
+
self.environment, scope, targets, exec=None if generate_only else self
|
|
571
441
|
)
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -379,14 +379,16 @@ class ParseToObjects(Transformer):
|
|
|
379
379
|
def start(self, args):
|
|
380
380
|
return args
|
|
381
381
|
|
|
382
|
+
def LINE_SEPARATOR(self, args):
|
|
383
|
+
return MagicConstants.LINE_SEPARATOR
|
|
384
|
+
|
|
382
385
|
def block(self, args):
|
|
383
386
|
output = args[0]
|
|
384
387
|
if isinstance(output, ConceptDeclarationStatement):
|
|
385
|
-
if len(args) > 1 and
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
)
|
|
388
|
+
if len(args) > 1 and args[1] != MagicConstants.LINE_SEPARATOR:
|
|
389
|
+
comments = [x for x in args[1:] if isinstance(x, Comment)]
|
|
390
|
+
merged = "\n".join([x.text.split("#")[1].rstrip() for x in comments])
|
|
391
|
+
output.concept.metadata.description = merged
|
|
390
392
|
# this is a bad plan for now;
|
|
391
393
|
# because a comment after an import statement is very common
|
|
392
394
|
# and it's not intuitive that it modifies the import description
|
|
@@ -913,7 +915,7 @@ class ParseToObjects(Transformer):
|
|
|
913
915
|
return Comment(text=args[0].value)
|
|
914
916
|
|
|
915
917
|
def PARSE_COMMENT(self, args):
|
|
916
|
-
return Comment(text=args.value)
|
|
918
|
+
return Comment(text=args.value.rstrip())
|
|
917
919
|
|
|
918
920
|
@v_args(meta=True)
|
|
919
921
|
def select_transform(self, meta: Meta, args) -> ConceptTransform:
|
trilogy/parsing/render.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
from datetime import date, datetime
|
|
3
3
|
from functools import singledispatchmethod
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
from jinja2 import Template
|
|
6
7
|
|
|
@@ -12,6 +13,7 @@ from trilogy.core.models.author import (
|
|
|
12
13
|
AlignItem,
|
|
13
14
|
CaseElse,
|
|
14
15
|
CaseWhen,
|
|
16
|
+
Comment,
|
|
15
17
|
Comparison,
|
|
16
18
|
Concept,
|
|
17
19
|
ConceptRef,
|
|
@@ -83,6 +85,23 @@ class Renderer:
|
|
|
83
85
|
def __init__(self, environment: Environment | None = None):
|
|
84
86
|
self.environment = environment
|
|
85
87
|
|
|
88
|
+
def render_statement_string(self, list_of_statements: list[Any]) -> str:
|
|
89
|
+
new = []
|
|
90
|
+
last_statement_type = None
|
|
91
|
+
for stmt in list_of_statements:
|
|
92
|
+
stmt_type = type(stmt)
|
|
93
|
+
if last_statement_type is None:
|
|
94
|
+
pass
|
|
95
|
+
elif last_statement_type == Comment:
|
|
96
|
+
new.append("\n")
|
|
97
|
+
elif stmt_type != last_statement_type:
|
|
98
|
+
new.append("\n\n")
|
|
99
|
+
else:
|
|
100
|
+
new.append("\n")
|
|
101
|
+
new.append(Renderer().to_string(stmt))
|
|
102
|
+
last_statement_type = stmt_type
|
|
103
|
+
return "".join(new)
|
|
104
|
+
|
|
86
105
|
@singledispatchmethod
|
|
87
106
|
def to_string(self, arg):
|
|
88
107
|
raise NotImplementedError("Cannot render type {}".format(type(arg)))
|
|
@@ -269,6 +288,8 @@ class Renderer:
|
|
|
269
288
|
@to_string.register
|
|
270
289
|
def _(self, arg: "Address"):
|
|
271
290
|
if arg.is_query:
|
|
291
|
+
if arg.location.startswith("("):
|
|
292
|
+
return f"query '''{arg.location[1:-1]}'''"
|
|
272
293
|
return f"query '''{arg.location}'''"
|
|
273
294
|
return f"address {arg.location}"
|
|
274
295
|
|
|
@@ -286,7 +307,7 @@ class Renderer:
|
|
|
286
307
|
def _(self, arg: "ColumnAssignment"):
|
|
287
308
|
if arg.modifiers:
|
|
288
309
|
modifiers = "".join(
|
|
289
|
-
[self.to_string(modifier) for modifier in arg.modifiers]
|
|
310
|
+
[self.to_string(modifier) for modifier in sorted(arg.modifiers)]
|
|
290
311
|
)
|
|
291
312
|
else:
|
|
292
313
|
modifiers = ""
|
|
@@ -328,7 +349,7 @@ class Renderer:
|
|
|
328
349
|
else:
|
|
329
350
|
output = f"{concept.purpose.value} {namespace}{concept.name} <- {self.to_string(concept.lineage)};"
|
|
330
351
|
if base_description:
|
|
331
|
-
output += f" #
|
|
352
|
+
output += f" #{base_description}"
|
|
332
353
|
return output
|
|
333
354
|
|
|
334
355
|
@to_string.register
|
|
@@ -428,6 +449,10 @@ class Renderer:
|
|
|
428
449
|
def _(self, arg: "Comparison"):
|
|
429
450
|
return f"{self.to_string(arg.left)} {arg.operator.value} {self.to_string(arg.right)}"
|
|
430
451
|
|
|
452
|
+
@to_string.register
|
|
453
|
+
def _(self, arg: "Comment"):
|
|
454
|
+
return f"{arg.text}"
|
|
455
|
+
|
|
431
456
|
@to_string.register
|
|
432
457
|
def _(self, arg: "WindowItem"):
|
|
433
458
|
over = ",".join(self.to_string(c) for c in arg.over)
|
|
@@ -551,8 +576,10 @@ class Renderer:
|
|
|
551
576
|
def _(self, arg: Modifier):
|
|
552
577
|
if arg == Modifier.PARTIAL:
|
|
553
578
|
return "~"
|
|
554
|
-
|
|
579
|
+
elif arg == Modifier.HIDDEN:
|
|
555
580
|
return "--"
|
|
581
|
+
elif arg == Modifier.NULLABLE:
|
|
582
|
+
return "?"
|
|
556
583
|
return arg.value
|
|
557
584
|
|
|
558
585
|
@to_string.register
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
!start: ( block | show_statement )*
|
|
2
|
-
block: statement _TERMINATOR PARSE_COMMENT
|
|
1
|
+
!start: ( block | show_statement | PARSE_COMMENT )*
|
|
2
|
+
block: statement _TERMINATOR LINE_SEPARATOR? PARSE_COMMENT*
|
|
3
3
|
?statement: concept
|
|
4
4
|
| datasource
|
|
5
5
|
| function
|
|
@@ -14,9 +14,12 @@
|
|
|
14
14
|
| rawsql_statement
|
|
15
15
|
| validate_statement
|
|
16
16
|
|
|
17
|
-
_TERMINATOR: ";"i
|
|
17
|
+
_TERMINATOR: ";"i
|
|
18
18
|
|
|
19
|
-
PARSE_COMMENT.1: /#.*(\n|$)/ |
|
|
19
|
+
PARSE_COMMENT.1: /#.*(\n|$)/ | /\/\/.*(\n|$)/
|
|
20
|
+
|
|
21
|
+
// when whitespace matters - comment placement
|
|
22
|
+
LINE_SEPARATOR.1: /[ \t\r\f\v]*\n+/
|
|
20
23
|
|
|
21
24
|
// property display_name string
|
|
22
25
|
concept_declaration: PURPOSE IDENTIFIER data_type concept_nullable_modifier? metadata?
|
trilogy/compiler.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|