pytrilogy 0.0.2.50__py3-none-any.whl → 0.0.2.51__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.2.50.dist-info → pytrilogy-0.0.2.51.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.2.50.dist-info → pytrilogy-0.0.2.51.dist-info}/RECORD +27 -25
- trilogy/__init__.py +1 -1
- trilogy/core/internal.py +5 -1
- trilogy/core/models.py +124 -263
- trilogy/core/processing/concept_strategies_v3.py +14 -4
- trilogy/core/processing/node_generators/basic_node.py +7 -3
- trilogy/core/processing/node_generators/common.py +8 -3
- trilogy/core/processing/node_generators/filter_node.py +5 -5
- trilogy/core/processing/node_generators/group_node.py +24 -8
- trilogy/core/processing/node_generators/multiselect_node.py +4 -3
- trilogy/core/processing/node_generators/node_merge_node.py +14 -2
- trilogy/core/processing/node_generators/rowset_node.py +3 -4
- trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +203 -0
- trilogy/core/processing/node_generators/select_merge_node.py +17 -9
- trilogy/core/processing/nodes/base_node.py +2 -33
- trilogy/core/processing/nodes/group_node.py +19 -10
- trilogy/core/processing/nodes/merge_node.py +2 -2
- trilogy/hooks/graph_hook.py +3 -1
- trilogy/parsing/common.py +54 -12
- trilogy/parsing/parse_engine.py +39 -20
- trilogy/parsing/render.py +8 -1
- {pytrilogy-0.0.2.50.dist-info → pytrilogy-0.0.2.51.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.50.dist-info → pytrilogy-0.0.2.51.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.50.dist-info → pytrilogy-0.0.2.51.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.50.dist-info → pytrilogy-0.0.2.51.dist-info}/top_level.txt +0 -0
|
@@ -15,10 +15,10 @@ from trilogy.core.models import (
|
|
|
15
15
|
)
|
|
16
16
|
from trilogy.core.processing.nodes.base_node import (
|
|
17
17
|
StrategyNode,
|
|
18
|
-
concept_list_to_grain,
|
|
19
18
|
resolve_concept_map,
|
|
20
19
|
)
|
|
21
20
|
from trilogy.core.processing.utility import find_nullable_concepts, is_scalar_condition
|
|
21
|
+
from trilogy.parsing.common import concepts_to_grain_concepts
|
|
22
22
|
from trilogy.utility import unique
|
|
23
23
|
|
|
24
24
|
LOGGER_PREFIX = "[CONCEPT DETAIL - GROUP NODE]"
|
|
@@ -64,19 +64,27 @@ class GroupNode(StrategyNode):
|
|
|
64
64
|
p.resolve() for p in self.parents
|
|
65
65
|
]
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
target_grain = self.grain or Grain.from_concepts(
|
|
68
|
+
concepts_to_grain_concepts(
|
|
69
|
+
self.output_concepts, environment=self.environment
|
|
70
|
+
)
|
|
71
|
+
)
|
|
68
72
|
comp_grain = Grain()
|
|
69
73
|
for source in parent_sources:
|
|
70
74
|
comp_grain += source.grain
|
|
71
|
-
|
|
75
|
+
comp_grain = Grain.from_concepts(
|
|
76
|
+
concepts_to_grain_concepts(
|
|
77
|
+
comp_grain.components, environment=self.environment
|
|
78
|
+
)
|
|
79
|
+
)
|
|
72
80
|
# dynamically select if we need to group
|
|
73
81
|
# because sometimes, we are already at required grain
|
|
74
|
-
if comp_grain ==
|
|
82
|
+
if comp_grain == target_grain and self.force_group is not True:
|
|
75
83
|
# if there is no group by, and inputs equal outputs
|
|
76
84
|
# return the parent
|
|
77
85
|
logger.info(
|
|
78
86
|
f"{self.logging_prefix}{LOGGER_PREFIX} Grain of group by equals output"
|
|
79
|
-
f" grains {comp_grain} and {
|
|
87
|
+
f" grains {comp_grain} and {target_grain}"
|
|
80
88
|
)
|
|
81
89
|
if (
|
|
82
90
|
len(parent_sources) == 1
|
|
@@ -94,10 +102,11 @@ class GroupNode(StrategyNode):
|
|
|
94
102
|
source_type = SourceType.SELECT
|
|
95
103
|
else:
|
|
96
104
|
logger.info(
|
|
97
|
-
f"{self.logging_prefix}{LOGGER_PREFIX} Group node has different grain than parents;
|
|
98
|
-
f"
|
|
105
|
+
f"{self.logging_prefix}{LOGGER_PREFIX} Group node has different grain than parents; group is required."
|
|
106
|
+
f" Upstream grains {[str(source.grain) for source in parent_sources]}"
|
|
99
107
|
f" with final grain {comp_grain} vs"
|
|
100
|
-
f" target grain {
|
|
108
|
+
f" target grain {target_grain}"
|
|
109
|
+
f" delta: {comp_grain - target_grain}"
|
|
101
110
|
)
|
|
102
111
|
for parent in self.parents:
|
|
103
112
|
logger.info(
|
|
@@ -134,7 +143,7 @@ class GroupNode(StrategyNode):
|
|
|
134
143
|
source_type=source_type,
|
|
135
144
|
source_map=source_map,
|
|
136
145
|
joins=[],
|
|
137
|
-
grain=
|
|
146
|
+
grain=target_grain,
|
|
138
147
|
partial_concepts=self.partial_concepts,
|
|
139
148
|
nullable_concepts=nullable_concepts,
|
|
140
149
|
hidden_concepts=self.hidden_concepts,
|
|
@@ -163,7 +172,7 @@ class GroupNode(StrategyNode):
|
|
|
163
172
|
source_type=SourceType.SELECT,
|
|
164
173
|
source_map=source_map,
|
|
165
174
|
joins=[],
|
|
166
|
-
grain=
|
|
175
|
+
grain=target_grain,
|
|
167
176
|
nullable_concepts=base.nullable_concepts,
|
|
168
177
|
partial_concepts=self.partial_concepts,
|
|
169
178
|
condition=self.conditions,
|
|
@@ -58,7 +58,7 @@ def deduplicate_nodes(
|
|
|
58
58
|
og = merged[k1]
|
|
59
59
|
subset_to = merged[k2]
|
|
60
60
|
logger.info(
|
|
61
|
-
f"{logging_prefix}{LOGGER_PREFIX} extraneous parent node that is subset of another parent node {og.grain.issubset(subset_to.grain)} {og.grain.
|
|
61
|
+
f"{logging_prefix}{LOGGER_PREFIX} extraneous parent node that is subset of another parent node {og.grain.issubset(subset_to.grain)} {og.grain.components} {subset_to.grain.components}"
|
|
62
62
|
)
|
|
63
63
|
merged = {k: v for k, v in merged.items() if k != k1}
|
|
64
64
|
removed.add(k1)
|
|
@@ -197,7 +197,7 @@ class MergeNode(StrategyNode):
|
|
|
197
197
|
) -> List[BaseJoin | UnnestJoin]:
|
|
198
198
|
# only finally, join between them for unique values
|
|
199
199
|
dataset_list: List[QueryDatasource | Datasource] = sorted(
|
|
200
|
-
final_datasets, key=lambda x: -len(x.grain.
|
|
200
|
+
final_datasets, key=lambda x: -len(x.grain.components)
|
|
201
201
|
)
|
|
202
202
|
|
|
203
203
|
logger.info(
|
trilogy/hooks/graph_hook.py
CHANGED
|
@@ -30,6 +30,7 @@ class GraphHook(BaseHook):
|
|
|
30
30
|
graph: nx.DiGraph,
|
|
31
31
|
target: str | None = None,
|
|
32
32
|
highlight_nodes: list[str] | None = None,
|
|
33
|
+
remove_isolates: bool = True,
|
|
33
34
|
):
|
|
34
35
|
from matplotlib import pyplot as plt
|
|
35
36
|
|
|
@@ -38,7 +39,8 @@ class GraphHook(BaseHook):
|
|
|
38
39
|
for node in nodes:
|
|
39
40
|
if "__preql_internal" in node:
|
|
40
41
|
graph.remove_node(node)
|
|
41
|
-
|
|
42
|
+
if remove_isolates:
|
|
43
|
+
graph.remove_nodes_from(list(nx.isolates(graph)))
|
|
42
44
|
color_map = []
|
|
43
45
|
highlight_nodes = highlight_nodes or []
|
|
44
46
|
for node in graph:
|
trilogy/parsing/common.py
CHANGED
|
@@ -4,7 +4,13 @@ from typing import List, Tuple
|
|
|
4
4
|
from trilogy.constants import (
|
|
5
5
|
VIRTUAL_CONCEPT_PREFIX,
|
|
6
6
|
)
|
|
7
|
-
from trilogy.core.enums import
|
|
7
|
+
from trilogy.core.enums import (
|
|
8
|
+
FunctionType,
|
|
9
|
+
Granularity,
|
|
10
|
+
Modifier,
|
|
11
|
+
PurposeLineage,
|
|
12
|
+
WindowType,
|
|
13
|
+
)
|
|
8
14
|
from trilogy.core.functions import arg_to_datatype, function_args_to_output_purpose
|
|
9
15
|
from trilogy.core.models import (
|
|
10
16
|
AggregateWrapper,
|
|
@@ -66,7 +72,7 @@ def process_function_args(
|
|
|
66
72
|
concept = function_to_concept(
|
|
67
73
|
arg,
|
|
68
74
|
name=f"{VIRTUAL_CONCEPT_PREFIX}_{arg.operator.value}_{id_hash}",
|
|
69
|
-
|
|
75
|
+
environment=environment,
|
|
70
76
|
)
|
|
71
77
|
# to satisfy mypy, concept will always have metadata
|
|
72
78
|
if concept.metadata and meta:
|
|
@@ -80,7 +86,7 @@ def process_function_args(
|
|
|
80
86
|
concept = arbitrary_to_concept(
|
|
81
87
|
arg,
|
|
82
88
|
name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
|
|
83
|
-
|
|
89
|
+
environment=environment,
|
|
84
90
|
)
|
|
85
91
|
if concept.metadata and meta:
|
|
86
92
|
concept.metadata.line_number = meta.line
|
|
@@ -139,10 +145,39 @@ def constant_to_concept(
|
|
|
139
145
|
)
|
|
140
146
|
|
|
141
147
|
|
|
148
|
+
def concepts_to_grain_concepts(
|
|
149
|
+
concepts: List[Concept] | List[str] | set[str], environment: Environment | None
|
|
150
|
+
) -> list[Concept]:
|
|
151
|
+
environment = Environment() if environment is None else environment
|
|
152
|
+
pconcepts: list[Concept] = [
|
|
153
|
+
c if isinstance(c, Concept) else environment.concepts[c] for c in concepts
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
final: List[Concept] = []
|
|
157
|
+
for sub in pconcepts:
|
|
158
|
+
if sub.purpose in (Purpose.PROPERTY, Purpose.METRIC) and sub.keys:
|
|
159
|
+
if any([c.address in pconcepts for c in sub.keys]):
|
|
160
|
+
continue
|
|
161
|
+
if sub.purpose in (Purpose.METRIC,):
|
|
162
|
+
if all([c in pconcepts for c in sub.grain.components]):
|
|
163
|
+
continue
|
|
164
|
+
if sub.granularity == Granularity.SINGLE_ROW:
|
|
165
|
+
continue
|
|
166
|
+
final.append(sub)
|
|
167
|
+
final = unique(final, "address")
|
|
168
|
+
v2 = sorted(final, key=lambda x: x.name)
|
|
169
|
+
return v2
|
|
170
|
+
|
|
171
|
+
|
|
142
172
|
def function_to_concept(
|
|
143
|
-
parent: Function,
|
|
173
|
+
parent: Function,
|
|
174
|
+
name: str,
|
|
175
|
+
environment: Environment,
|
|
176
|
+
namespace: str | None = None,
|
|
177
|
+
metadata: Metadata | None = None,
|
|
144
178
|
) -> Concept:
|
|
145
179
|
pkeys: List[Concept] = []
|
|
180
|
+
namespace = namespace or environment.namespace
|
|
146
181
|
for x in parent.arguments:
|
|
147
182
|
pkeys += [
|
|
148
183
|
x
|
|
@@ -162,7 +197,7 @@ def function_to_concept(
|
|
|
162
197
|
key_grain += [*x.keys]
|
|
163
198
|
else:
|
|
164
199
|
key_grain.append(x)
|
|
165
|
-
keys = tuple(
|
|
200
|
+
keys = tuple(concepts_to_grain_concepts(key_grain, environment))
|
|
166
201
|
if not pkeys:
|
|
167
202
|
purpose = Purpose.CONSTANT
|
|
168
203
|
else:
|
|
@@ -256,7 +291,7 @@ def window_item_to_concept(
|
|
|
256
291
|
lineage=parent,
|
|
257
292
|
metadata=fmetadata,
|
|
258
293
|
# filters are implicitly at the grain of the base item
|
|
259
|
-
grain=Grain(
|
|
294
|
+
grain=Grain.from_concepts(grain),
|
|
260
295
|
namespace=namespace,
|
|
261
296
|
keys=keys,
|
|
262
297
|
modifiers=modifiers,
|
|
@@ -268,9 +303,8 @@ def agg_wrapper_to_concept(
|
|
|
268
303
|
namespace: str,
|
|
269
304
|
name: str,
|
|
270
305
|
metadata: Metadata | None = None,
|
|
271
|
-
purpose: Purpose | None = None,
|
|
272
306
|
) -> Concept:
|
|
273
|
-
|
|
307
|
+
_, keys = get_purpose_and_keys(
|
|
274
308
|
Purpose.METRIC, tuple(parent.by) if parent.by else None
|
|
275
309
|
)
|
|
276
310
|
# anything grouped to a grain should be a property
|
|
@@ -284,7 +318,7 @@ def agg_wrapper_to_concept(
|
|
|
284
318
|
purpose=Purpose.METRIC,
|
|
285
319
|
metadata=fmetadata,
|
|
286
320
|
lineage=parent,
|
|
287
|
-
grain=Grain(
|
|
321
|
+
grain=Grain.from_concepts(parent.by) if parent.by else Grain(),
|
|
288
322
|
namespace=namespace,
|
|
289
323
|
keys=tuple(parent.by) if parent.by else keys,
|
|
290
324
|
modifiers=modifiers,
|
|
@@ -304,15 +338,17 @@ def arbitrary_to_concept(
|
|
|
304
338
|
| float
|
|
305
339
|
| str
|
|
306
340
|
),
|
|
307
|
-
|
|
341
|
+
environment: Environment,
|
|
342
|
+
namespace: str | None = None,
|
|
308
343
|
name: str | None = None,
|
|
309
344
|
metadata: Metadata | None = None,
|
|
310
345
|
purpose: Purpose | None = None,
|
|
311
346
|
) -> Concept:
|
|
347
|
+
namespace = namespace or environment.namespace
|
|
312
348
|
if isinstance(parent, AggregateWrapper):
|
|
313
349
|
if not name:
|
|
314
350
|
name = f"{VIRTUAL_CONCEPT_PREFIX}_agg_{parent.function.operator.value}_{string_to_hash(str(parent))}"
|
|
315
|
-
return agg_wrapper_to_concept(parent, namespace, name, metadata
|
|
351
|
+
return agg_wrapper_to_concept(parent, namespace, name, metadata)
|
|
316
352
|
elif isinstance(parent, WindowItem):
|
|
317
353
|
if not name:
|
|
318
354
|
name = f"{VIRTUAL_CONCEPT_PREFIX}_window_{parent.type.value}_{string_to_hash(str(parent))}"
|
|
@@ -324,7 +360,13 @@ def arbitrary_to_concept(
|
|
|
324
360
|
elif isinstance(parent, Function):
|
|
325
361
|
if not name:
|
|
326
362
|
name = f"{VIRTUAL_CONCEPT_PREFIX}_func_{parent.operator.value}_{string_to_hash(str(parent))}"
|
|
327
|
-
return function_to_concept(
|
|
363
|
+
return function_to_concept(
|
|
364
|
+
parent,
|
|
365
|
+
name,
|
|
366
|
+
metadata=metadata,
|
|
367
|
+
environment=environment,
|
|
368
|
+
namespace=namespace,
|
|
369
|
+
)
|
|
328
370
|
elif isinstance(parent, ListWrapper):
|
|
329
371
|
if not name:
|
|
330
372
|
name = f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(parent))}"
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -463,7 +463,7 @@ class ParseToObjects(Transformer):
|
|
|
463
463
|
datatype=args[2],
|
|
464
464
|
purpose=args[0],
|
|
465
465
|
metadata=metadata,
|
|
466
|
-
grain=Grain(components=parents),
|
|
466
|
+
grain=Grain(components={x.address for x in parents}),
|
|
467
467
|
namespace=namespace,
|
|
468
468
|
keys=parents,
|
|
469
469
|
modifiers=modifiers,
|
|
@@ -530,6 +530,7 @@ class ParseToObjects(Transformer):
|
|
|
530
530
|
source_value,
|
|
531
531
|
name=name,
|
|
532
532
|
namespace=namespace,
|
|
533
|
+
environment=self.environment,
|
|
533
534
|
purpose=purpose,
|
|
534
535
|
metadata=metadata,
|
|
535
536
|
)
|
|
@@ -598,7 +599,7 @@ class ParseToObjects(Transformer):
|
|
|
598
599
|
output_purpose=Purpose.CONSTANT,
|
|
599
600
|
arguments=[constant],
|
|
600
601
|
),
|
|
601
|
-
grain=Grain(components=
|
|
602
|
+
grain=Grain(components=set()),
|
|
602
603
|
namespace=namespace,
|
|
603
604
|
)
|
|
604
605
|
if concept.metadata:
|
|
@@ -623,7 +624,9 @@ class ParseToObjects(Transformer):
|
|
|
623
624
|
return args
|
|
624
625
|
|
|
625
626
|
def grain_clause(self, args) -> Grain:
|
|
626
|
-
return Grain(
|
|
627
|
+
return Grain(
|
|
628
|
+
components=set([self.environment.concepts[a].address for a in args[0]])
|
|
629
|
+
)
|
|
627
630
|
|
|
628
631
|
def whole_grain_clause(self, args) -> WholeGrainWrapper:
|
|
629
632
|
return WholeGrainWrapper(where=args[0])
|
|
@@ -715,7 +718,11 @@ class ParseToObjects(Transformer):
|
|
|
715
718
|
)
|
|
716
719
|
elif isinstance(transformation, Function):
|
|
717
720
|
concept = function_to_concept(
|
|
718
|
-
transformation,
|
|
721
|
+
transformation,
|
|
722
|
+
namespace=namespace,
|
|
723
|
+
name=output,
|
|
724
|
+
metadata=metadata,
|
|
725
|
+
environment=self.environment,
|
|
719
726
|
)
|
|
720
727
|
else:
|
|
721
728
|
raise SyntaxError("Invalid transformation")
|
|
@@ -771,10 +778,8 @@ class ParseToObjects(Transformer):
|
|
|
771
778
|
def handle_order_item(x, namespace: str):
|
|
772
779
|
if not isinstance(x, Concept):
|
|
773
780
|
x = arbitrary_to_concept(
|
|
774
|
-
x,
|
|
775
|
-
namespace=namespace,
|
|
781
|
+
x, namespace=namespace, environment=self.environment
|
|
776
782
|
)
|
|
777
|
-
self.environment.add_concept(x)
|
|
778
783
|
return x
|
|
779
784
|
|
|
780
785
|
return [
|
|
@@ -1029,12 +1034,32 @@ class ParseToObjects(Transformer):
|
|
|
1029
1034
|
order_by=order_by,
|
|
1030
1035
|
meta=Metadata(line_number=meta.line),
|
|
1031
1036
|
)
|
|
1032
|
-
for parse_pass in [
|
|
1037
|
+
for parse_pass in [
|
|
1038
|
+
1,
|
|
1039
|
+
2,
|
|
1040
|
+
]:
|
|
1033
1041
|
# the first pass will result in all concepts being defined
|
|
1034
1042
|
# the second will get grains appropriately
|
|
1035
1043
|
# eg if someone does sum(x)->a, b+c -> z - we don't know if Z is a key to group by or an aggregate
|
|
1036
1044
|
# until after the first pass, and so don't know the grain of a
|
|
1037
|
-
|
|
1045
|
+
|
|
1046
|
+
if parse_pass == 1:
|
|
1047
|
+
grain = Grain.from_concepts(
|
|
1048
|
+
[
|
|
1049
|
+
x.content
|
|
1050
|
+
for x in output.selection
|
|
1051
|
+
if isinstance(x.content, Concept)
|
|
1052
|
+
],
|
|
1053
|
+
where_clause=output.where_clause,
|
|
1054
|
+
)
|
|
1055
|
+
if parse_pass == 2:
|
|
1056
|
+
grain = Grain.from_concepts(
|
|
1057
|
+
output.output_components, where_clause=output.where_clause
|
|
1058
|
+
)
|
|
1059
|
+
print(
|
|
1060
|
+
f"end pass {parse_pass} grain {grain} from {output.output_components}"
|
|
1061
|
+
)
|
|
1062
|
+
output.grain = grain
|
|
1038
1063
|
for item in select_items:
|
|
1039
1064
|
# we don't know the grain of an aggregate at assignment time
|
|
1040
1065
|
# so rebuild at this point in the tree
|
|
@@ -1064,7 +1089,7 @@ class ParseToObjects(Transformer):
|
|
|
1064
1089
|
environment=self.environment,
|
|
1065
1090
|
)
|
|
1066
1091
|
output.local_concepts[item.content.address] = item.content
|
|
1067
|
-
|
|
1092
|
+
|
|
1068
1093
|
if order_by:
|
|
1069
1094
|
output.order_by = order_by.with_select_context(
|
|
1070
1095
|
local_concepts=output.local_concepts,
|
|
@@ -1181,7 +1206,7 @@ class ParseToObjects(Transformer):
|
|
|
1181
1206
|
if isinstance(args[0], AggregateWrapper):
|
|
1182
1207
|
left = arbitrary_to_concept(
|
|
1183
1208
|
args[0],
|
|
1184
|
-
|
|
1209
|
+
environment=self.environment,
|
|
1185
1210
|
)
|
|
1186
1211
|
self.environment.add_concept(left)
|
|
1187
1212
|
else:
|
|
@@ -1189,7 +1214,7 @@ class ParseToObjects(Transformer):
|
|
|
1189
1214
|
if isinstance(args[2], AggregateWrapper):
|
|
1190
1215
|
right = arbitrary_to_concept(
|
|
1191
1216
|
args[2],
|
|
1192
|
-
|
|
1217
|
+
environment=self.environment,
|
|
1193
1218
|
)
|
|
1194
1219
|
self.environment.add_concept(right)
|
|
1195
1220
|
else:
|
|
@@ -1227,10 +1252,7 @@ class ParseToObjects(Transformer):
|
|
|
1227
1252
|
):
|
|
1228
1253
|
right = right.content
|
|
1229
1254
|
if isinstance(right, (Function, FilterItem, WindowItem, AggregateWrapper)):
|
|
1230
|
-
right = arbitrary_to_concept(
|
|
1231
|
-
right,
|
|
1232
|
-
namespace=self.environment.namespace,
|
|
1233
|
-
)
|
|
1255
|
+
right = arbitrary_to_concept(right, environment=self.environment)
|
|
1234
1256
|
self.environment.add_concept(right, meta=meta)
|
|
1235
1257
|
return SubselectComparison(
|
|
1236
1258
|
left=args[0],
|
|
@@ -1299,10 +1321,7 @@ class ParseToObjects(Transformer):
|
|
|
1299
1321
|
elif isinstance(item, WindowType):
|
|
1300
1322
|
type = item
|
|
1301
1323
|
else:
|
|
1302
|
-
concept = arbitrary_to_concept(
|
|
1303
|
-
item,
|
|
1304
|
-
namespace=self.environment.namespace,
|
|
1305
|
-
)
|
|
1324
|
+
concept = arbitrary_to_concept(item, environment=self.environment)
|
|
1306
1325
|
self.environment.add_concept(concept, meta=meta)
|
|
1307
1326
|
assert concept
|
|
1308
1327
|
return WindowItem(
|
trilogy/parsing/render.py
CHANGED
|
@@ -158,7 +158,14 @@ class Renderer:
|
|
|
158
158
|
|
|
159
159
|
@to_string.register
|
|
160
160
|
def _(self, arg: "Grain"):
|
|
161
|
-
|
|
161
|
+
final = []
|
|
162
|
+
for comp in arg.components:
|
|
163
|
+
if comp.startswith(DEFAULT_NAMESPACE):
|
|
164
|
+
final.append(comp.split(".", 1)[1])
|
|
165
|
+
else:
|
|
166
|
+
final.append(comp)
|
|
167
|
+
final = sorted(final)
|
|
168
|
+
components = ",".join(x for x in final)
|
|
162
169
|
return f"grain ({components})"
|
|
163
170
|
|
|
164
171
|
@to_string.register
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|