pytrilogy 0.0.2.57__py3-none-any.whl → 0.0.3.0__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.
Files changed (75) hide show
  1. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/METADATA +9 -2
  2. pytrilogy-0.0.3.0.dist-info/RECORD +99 -0
  3. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/WHEEL +1 -1
  4. trilogy/__init__.py +2 -2
  5. trilogy/core/enums.py +1 -7
  6. trilogy/core/env_processor.py +17 -5
  7. trilogy/core/environment_helpers.py +11 -25
  8. trilogy/core/exceptions.py +4 -0
  9. trilogy/core/functions.py +695 -261
  10. trilogy/core/graph_models.py +10 -10
  11. trilogy/core/internal.py +11 -2
  12. trilogy/core/models/__init__.py +0 -0
  13. trilogy/core/models/author.py +2110 -0
  14. trilogy/core/models/build.py +1845 -0
  15. trilogy/core/models/build_environment.py +151 -0
  16. trilogy/core/models/core.py +370 -0
  17. trilogy/core/models/datasource.py +297 -0
  18. trilogy/core/models/environment.py +696 -0
  19. trilogy/core/models/execute.py +931 -0
  20. trilogy/core/optimization.py +17 -22
  21. trilogy/core/optimizations/base_optimization.py +1 -1
  22. trilogy/core/optimizations/inline_constant.py +6 -6
  23. trilogy/core/optimizations/inline_datasource.py +17 -11
  24. trilogy/core/optimizations/predicate_pushdown.py +17 -16
  25. trilogy/core/processing/concept_strategies_v3.py +181 -146
  26. trilogy/core/processing/graph_utils.py +1 -1
  27. trilogy/core/processing/node_generators/basic_node.py +19 -18
  28. trilogy/core/processing/node_generators/common.py +51 -45
  29. trilogy/core/processing/node_generators/filter_node.py +26 -13
  30. trilogy/core/processing/node_generators/group_node.py +26 -21
  31. trilogy/core/processing/node_generators/group_to_node.py +13 -10
  32. trilogy/core/processing/node_generators/multiselect_node.py +60 -43
  33. trilogy/core/processing/node_generators/node_merge_node.py +76 -38
  34. trilogy/core/processing/node_generators/rowset_node.py +59 -36
  35. trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +27 -34
  36. trilogy/core/processing/node_generators/select_merge_node.py +161 -64
  37. trilogy/core/processing/node_generators/select_node.py +13 -13
  38. trilogy/core/processing/node_generators/union_node.py +12 -11
  39. trilogy/core/processing/node_generators/unnest_node.py +9 -7
  40. trilogy/core/processing/node_generators/window_node.py +19 -16
  41. trilogy/core/processing/nodes/__init__.py +21 -18
  42. trilogy/core/processing/nodes/base_node.py +92 -77
  43. trilogy/core/processing/nodes/filter_node.py +19 -13
  44. trilogy/core/processing/nodes/group_node.py +55 -40
  45. trilogy/core/processing/nodes/merge_node.py +47 -38
  46. trilogy/core/processing/nodes/select_node_v2.py +54 -40
  47. trilogy/core/processing/nodes/union_node.py +5 -7
  48. trilogy/core/processing/nodes/unnest_node.py +7 -11
  49. trilogy/core/processing/nodes/window_node.py +9 -4
  50. trilogy/core/processing/utility.py +108 -80
  51. trilogy/core/query_processor.py +67 -49
  52. trilogy/core/statements/__init__.py +0 -0
  53. trilogy/core/statements/author.py +413 -0
  54. trilogy/core/statements/build.py +0 -0
  55. trilogy/core/statements/common.py +30 -0
  56. trilogy/core/statements/execute.py +42 -0
  57. trilogy/dialect/base.py +152 -111
  58. trilogy/dialect/common.py +9 -10
  59. trilogy/dialect/duckdb.py +1 -1
  60. trilogy/dialect/enums.py +4 -2
  61. trilogy/dialect/presto.py +1 -1
  62. trilogy/dialect/sql_server.py +1 -1
  63. trilogy/executor.py +44 -32
  64. trilogy/hooks/base_hook.py +6 -4
  65. trilogy/hooks/query_debugger.py +110 -93
  66. trilogy/parser.py +1 -1
  67. trilogy/parsing/common.py +303 -64
  68. trilogy/parsing/parse_engine.py +263 -617
  69. trilogy/parsing/render.py +50 -26
  70. trilogy/scripts/trilogy.py +2 -1
  71. pytrilogy-0.0.2.57.dist-info/RECORD +0 -87
  72. trilogy/core/models.py +0 -4960
  73. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/LICENSE.md +0 -0
  74. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/entry_points.txt +0 -0
  75. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  from collections import defaultdict
2
2
  from typing import Dict, List
3
3
 
4
- from trilogy.core.models import Concept
4
+ from trilogy.core.models.author import Concept
5
5
  from trilogy.utility import unique
6
6
 
7
7
 
@@ -1,14 +1,9 @@
1
- # directly select out a basic derivation
2
1
  from typing import List
3
2
 
4
3
  from trilogy.constants import logger
5
- from trilogy.core.enums import SourceType
6
- from trilogy.core.models import (
7
- Concept,
8
- Function,
9
- FunctionClass,
10
- WhereClause,
11
- )
4
+ from trilogy.core.enums import FunctionClass, SourceType
5
+ from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
6
+ from trilogy.core.models.build_environment import BuildEnvironment
12
7
  from trilogy.core.processing.node_generators.common import (
13
8
  resolve_function_parent_concepts,
14
9
  )
@@ -19,10 +14,12 @@ LOGGER_PREFIX = "[GEN_BASIC_NODE]"
19
14
 
20
15
 
21
16
  def is_equivalent_basic_function_lineage(
22
- x: Concept,
23
- y: Concept,
17
+ x: BuildConcept,
18
+ y: BuildConcept,
24
19
  ):
25
- if not isinstance(x.lineage, Function) or not isinstance(y.lineage, Function):
20
+ if not isinstance(x.lineage, BuildFunction) or not isinstance(
21
+ y.lineage, BuildFunction
22
+ ):
26
23
  return False
27
24
  if x.lineage.operator == y.lineage.operator:
28
25
  return True
@@ -35,20 +32,20 @@ def is_equivalent_basic_function_lineage(
35
32
 
36
33
 
37
34
  def gen_basic_node(
38
- concept: Concept,
39
- local_optional: List[Concept],
40
- environment,
35
+ concept: BuildConcept,
36
+ local_optional: List[BuildConcept],
37
+ environment: BuildEnvironment,
41
38
  g,
42
39
  depth: int,
43
40
  source_concepts,
44
41
  history: History | None = None,
45
- conditions: WhereClause | None = None,
42
+ conditions: BuildWhereClause | None = None,
46
43
  ):
47
44
  depth_prefix = "\t" * depth
48
45
  parent_concepts = resolve_function_parent_concepts(concept, environment=environment)
49
46
 
50
47
  logger.info(
51
- f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} has parents {[x.address for x in parent_concepts]}"
48
+ f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} with lineage {concept.lineage} has parents {[x for x in parent_concepts]}"
52
49
  )
53
50
 
54
51
  equivalent_optional = [
@@ -57,6 +54,7 @@ def gen_basic_node(
57
54
  if is_equivalent_basic_function_lineage(concept, x)
58
55
  and x.address != concept.address
59
56
  ]
57
+
60
58
  if equivalent_optional:
61
59
  logger.info(
62
60
  f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} has equivalent optional {[x.address for x in equivalent_optional]}"
@@ -64,9 +62,12 @@ def gen_basic_node(
64
62
  for eo in equivalent_optional:
65
63
  parent_concepts += resolve_function_parent_concepts(eo, environment=environment)
66
64
  non_equivalent_optional = [
67
- x for x in local_optional if x not in equivalent_optional
65
+ x
66
+ for x in local_optional
67
+ if x not in equivalent_optional
68
+ and not any(x.address in y.pseudonyms for y in equivalent_optional)
68
69
  ]
69
- all_parents: list[Concept] = unique(
70
+ all_parents: list[BuildConcept] = unique(
70
71
  parent_concepts + non_equivalent_optional, "address"
71
72
  )
72
73
  logger.info(
@@ -1,16 +1,16 @@
1
1
  from collections import defaultdict
2
2
  from typing import Callable, List, Tuple
3
3
 
4
- from trilogy.core.enums import Purpose, PurposeLineage
5
- from trilogy.core.models import (
6
- AggregateWrapper,
7
- Concept,
8
- Environment,
9
- FilterItem,
10
- Function,
11
- LooseConceptList,
12
- WhereClause,
4
+ from trilogy.core.enums import Derivation, Purpose
5
+ from trilogy.core.models.build import (
6
+ BuildAggregateWrapper,
7
+ BuildConcept,
8
+ BuildFilterItem,
9
+ BuildFunction,
10
+ BuildWhereClause,
11
+ LooseBuildConceptList,
13
12
  )
13
+ from trilogy.core.models.build_environment import BuildEnvironment
14
14
  from trilogy.core.processing.nodes import (
15
15
  History,
16
16
  NodeJoin,
@@ -19,14 +19,19 @@ from trilogy.core.processing.nodes.base_node import StrategyNode
19
19
  from trilogy.core.processing.nodes.merge_node import MergeNode
20
20
  from trilogy.utility import unique
21
21
 
22
+ AGGREGATE_TYPES = (BuildAggregateWrapper,)
23
+ FUNCTION_TYPES = (BuildFunction,)
24
+
22
25
 
23
26
  def resolve_function_parent_concepts(
24
- concept: Concept, environment: Environment
25
- ) -> List[Concept]:
26
- if not isinstance(concept.lineage, (Function, AggregateWrapper)):
27
- raise ValueError(f"Concept {concept} lineage is not function or aggregate")
28
- if concept.derivation == PurposeLineage.AGGREGATE:
29
- base: list[Concept] = []
27
+ concept: BuildConcept, environment: BuildEnvironment
28
+ ) -> List[BuildConcept]:
29
+ if not isinstance(concept.lineage, (*FUNCTION_TYPES, *AGGREGATE_TYPES)):
30
+ raise ValueError(
31
+ f"Concept {concept} lineage is not function or aggregate, is {type(concept.lineage)}"
32
+ )
33
+ if concept.derivation == Derivation.AGGREGATE:
34
+ base: list[BuildConcept] = []
30
35
  if not concept.grain.abstract:
31
36
  base = concept.lineage.concept_arguments + [
32
37
  environment.concepts[c] for c in concept.grain.components
@@ -35,13 +40,13 @@ def resolve_function_parent_concepts(
35
40
  # keep the key as a parent
36
41
  else:
37
42
  base = concept.lineage.concept_arguments
38
- if isinstance(concept.lineage, AggregateWrapper):
43
+ if isinstance(concept.lineage, AGGREGATE_TYPES):
39
44
  # for aggregate wrapper, don't include the by
40
45
  extra_property_grain = concept.lineage.function.concept_arguments
41
46
  else:
42
47
  extra_property_grain = concept.lineage.concept_arguments
43
48
  for x in extra_property_grain:
44
- if isinstance(x, Concept) and x.purpose == Purpose.PROPERTY and x.keys:
49
+ if isinstance(x, BuildConcept) and x.purpose == Purpose.PROPERTY and x.keys:
45
50
  base += [environment.concepts[c] for c in x.keys]
46
51
  return unique(base, "address")
47
52
  # TODO: handle basic lineage chains?
@@ -49,10 +54,10 @@ def resolve_function_parent_concepts(
49
54
 
50
55
 
51
56
  def resolve_condition_parent_concepts(
52
- condition: WhereClause,
53
- ) -> Tuple[List[Concept], List[Tuple[Concept, ...]]]:
57
+ condition: BuildWhereClause,
58
+ ) -> Tuple[List[BuildConcept], List[Tuple[BuildConcept, ...]]]:
54
59
  base_existence = []
55
- base_rows = []
60
+ base_rows: list[BuildConcept] = []
56
61
  base_rows += condition.row_arguments
57
62
  for ctuple in condition.existence_arguments:
58
63
  base_existence.append(ctuple)
@@ -60,10 +65,10 @@ def resolve_condition_parent_concepts(
60
65
 
61
66
 
62
67
  def resolve_filter_parent_concepts(
63
- concept: Concept,
64
- environment: Environment,
65
- ) -> Tuple[Concept, List[Concept], List[Tuple[Concept, ...]]]:
66
- if not isinstance(concept.lineage, FilterItem):
68
+ concept: BuildConcept,
69
+ environment: BuildEnvironment,
70
+ ) -> Tuple[BuildConcept, List[BuildConcept], List[Tuple[BuildConcept, ...]]]:
71
+ if not isinstance(concept.lineage, (BuildFilterItem,)):
67
72
  raise ValueError(
68
73
  f"Concept {concept} lineage is not filter item, is {type(concept.lineage)}"
69
74
  )
@@ -75,14 +80,14 @@ def resolve_filter_parent_concepts(
75
80
  )
76
81
  base_rows += condition_rows
77
82
  base_existence += condition_existence
78
- if direct_parent.grain:
79
- base_rows += [environment.concepts[c] for c in direct_parent.grain.components]
83
+ # this is required so that
80
84
  if (
81
- isinstance(direct_parent, Concept)
82
- and direct_parent.purpose == Purpose.PROPERTY
85
+ isinstance(direct_parent, BuildConcept)
86
+ and direct_parent.purpose in (Purpose.PROPERTY, Purpose.METRIC)
83
87
  and direct_parent.keys
84
88
  ):
85
89
  base_rows += [environment.concepts[c] for c in direct_parent.keys]
90
+
86
91
  if concept.lineage.where.existence_arguments:
87
92
  return (
88
93
  concept.lineage.content,
@@ -94,14 +99,14 @@ def resolve_filter_parent_concepts(
94
99
 
95
100
  def gen_property_enrichment_node(
96
101
  base_node: StrategyNode,
97
- extra_properties: list[Concept],
98
- environment: Environment,
102
+ extra_properties: list[BuildConcept],
103
+ history: History,
104
+ environment: BuildEnvironment,
99
105
  g,
100
106
  depth: int,
101
107
  source_concepts,
102
108
  log_lambda: Callable,
103
- history: History | None = None,
104
- conditions: WhereClause | None = None,
109
+ conditions: BuildWhereClause | None = None,
105
110
  ):
106
111
  required_keys: dict[str, set[str]] = defaultdict(set)
107
112
  for x in extra_properties:
@@ -146,17 +151,17 @@ def gen_property_enrichment_node(
146
151
 
147
152
  def gen_enrichment_node(
148
153
  base_node: StrategyNode,
149
- join_keys: List[Concept],
150
- local_optional: list[Concept],
151
- environment: Environment,
154
+ join_keys: List[BuildConcept],
155
+ local_optional: list[BuildConcept],
156
+ environment: BuildEnvironment,
152
157
  g,
153
158
  depth: int,
154
159
  source_concepts,
155
160
  log_lambda,
156
- history: History | None = None,
157
- conditions: WhereClause | None = None,
161
+ history: History,
162
+ conditions: BuildWhereClause | None = None,
158
163
  ):
159
- local_opts = LooseConceptList(concepts=local_optional)
164
+ local_opts = LooseBuildConceptList(concepts=local_optional)
160
165
 
161
166
  extra_required = [
162
167
  x
@@ -165,7 +170,8 @@ def gen_enrichment_node(
165
170
  ]
166
171
 
167
172
  # property lookup optimization
168
- # this helps when evaluating a normalized star schema as you only want to lookup the missing properties based on the relevant keys
173
+ # this helps create ergonomic merge nodes when evaluating a normalized star schema
174
+ # as we only want to lookup the missing properties based on the relevant keys
169
175
  if all([x.purpose == Purpose.PROPERTY for x in extra_required]):
170
176
  if all(
171
177
  x.keys and all([key in base_node.output_lcl for key in x.keys])
@@ -177,16 +183,16 @@ def gen_enrichment_node(
177
183
  return gen_property_enrichment_node(
178
184
  base_node,
179
185
  extra_required,
180
- environment,
181
- g,
182
- depth,
183
- source_concepts,
186
+ environment=environment,
187
+ g=g,
188
+ depth=depth,
189
+ source_concepts=source_concepts,
184
190
  history=history,
185
191
  conditions=conditions,
186
192
  log_lambda=log_lambda,
187
193
  )
188
194
  log_lambda(
189
- f"{str(type(base_node).__name__)} searching for join keys {LooseConceptList(concepts=join_keys)} and extra required {local_opts}"
195
+ f"{str(type(base_node).__name__)} searching for join keys {LooseBuildConceptList(concepts=join_keys)} and extra required {local_opts}"
190
196
  )
191
197
  enrich_node: StrategyNode = source_concepts( # this fetches the parent + join keys
192
198
  # to then connect to the rest of the query
@@ -208,7 +214,7 @@ def gen_enrichment_node(
208
214
  non_hidden = [
209
215
  x
210
216
  for x in base_node.output_concepts
211
- if x.address not in [y.address for y in base_node.hidden_concepts]
217
+ if x.address not in base_node.hidden_concepts
212
218
  ]
213
219
  return MergeNode(
214
220
  input_concepts=unique(join_keys + extra_required + non_hidden, "address"),
@@ -1,7 +1,13 @@
1
1
  from typing import List
2
2
 
3
3
  from trilogy.constants import logger
4
- from trilogy.core.models import Concept, Environment, FilterItem, Grain, WhereClause
4
+ from trilogy.core.models.build import (
5
+ BuildConcept,
6
+ BuildFilterItem,
7
+ BuildGrain,
8
+ BuildWhereClause,
9
+ )
10
+ from trilogy.core.models.build_environment import BuildEnvironment
5
11
  from trilogy.core.processing.node_generators.common import (
6
12
  resolve_filter_parent_concepts,
7
13
  )
@@ -16,28 +22,30 @@ from trilogy.core.processing.utility import is_scalar_condition, padding, unique
16
22
 
17
23
  LOGGER_PREFIX = "[GEN_FILTER_NODE]"
18
24
 
25
+ FILTER_TYPES = (BuildFilterItem,)
26
+
19
27
 
20
28
  def gen_filter_node(
21
- concept: Concept,
22
- local_optional: List[Concept],
23
- environment: Environment,
29
+ concept: BuildConcept,
30
+ local_optional: List[BuildConcept],
31
+ environment: BuildEnvironment,
24
32
  g,
25
33
  depth: int,
26
34
  source_concepts,
27
35
  history: History | None = None,
28
- conditions: WhereClause | None = None,
36
+ conditions: BuildWhereClause | None = None,
29
37
  ) -> StrategyNode | None:
30
38
  immediate_parent, parent_row_concepts, parent_existence_concepts = (
31
39
  resolve_filter_parent_concepts(concept, environment)
32
40
  )
33
- if not isinstance(concept.lineage, FilterItem):
34
- raise SyntaxError('Filter node must have a lineage of type "FilterItem"')
41
+ if not isinstance(concept.lineage, FILTER_TYPES):
42
+ raise SyntaxError('Filter node must have a filter type lineage"')
35
43
  where = concept.lineage.where
36
44
 
37
- optional_included: list[Concept] = []
45
+ optional_included: list[BuildConcept] = []
38
46
 
39
47
  for x in local_optional:
40
- if isinstance(x.lineage, FilterItem):
48
+ if isinstance(x.lineage, FILTER_TYPES):
41
49
  if concept.lineage.where == where:
42
50
  logger.info(
43
51
  f"{padding(depth)}{LOGGER_PREFIX} fetching {x.lineage.content.address} as optional parent with same filter conditions "
@@ -48,7 +56,7 @@ def gen_filter_node(
48
56
  if conditions and conditions == where:
49
57
  optional_included.append(x)
50
58
  logger.info(
51
- f"{padding(depth)}{LOGGER_PREFIX} filter {concept.address} derived from {immediate_parent.address} row parents {[x.address for x in parent_row_concepts]} and {[[y.address] for x in parent_existence_concepts for y in x]} existence parents"
59
+ f"{padding(depth)}{LOGGER_PREFIX} filter `{concept}` condition `{concept.lineage.where}` derived from {immediate_parent.address} row parents {[x.address for x in parent_row_concepts]} and {[[y.address] for x in parent_existence_concepts for y in x]} existence parents"
52
60
  )
53
61
  # we'll populate this with the row parent
54
62
  # and the existence parent(s)
@@ -136,7 +144,7 @@ def gen_filter_node(
136
144
  parent.add_existence_concepts(flattened_existence, False).set_output_concepts(
137
145
  expected_output, False
138
146
  )
139
- parent.grain = Grain.from_concepts(
147
+ parent.grain = BuildGrain.from_concepts(
140
148
  (
141
149
  [environment.concepts[k] for k in immediate_parent.keys]
142
150
  if immediate_parent.keys
@@ -162,7 +170,7 @@ def gen_filter_node(
162
170
  output_concepts=[concept, immediate_parent] + parent_row_concepts,
163
171
  environment=environment,
164
172
  parents=core_parents,
165
- grain=Grain.from_concepts(
173
+ grain=BuildGrain.from_concepts(
166
174
  [immediate_parent] + parent_row_concepts,
167
175
  ),
168
176
  preexisting_conditions=conditions.conditional if conditions else None,
@@ -185,7 +193,7 @@ def gen_filter_node(
185
193
  )
186
194
  return filter_node
187
195
 
188
- enrich_node = source_concepts( # this fetches the parent + join keys
196
+ enrich_node: StrategyNode = source_concepts( # this fetches the parent + join keys
189
197
  # to then connect to the rest of the query
190
198
  mandatory_list=[immediate_parent] + parent_row_concepts + local_optional,
191
199
  environment=environment,
@@ -194,6 +202,11 @@ def gen_filter_node(
194
202
  history=history,
195
203
  conditions=conditions,
196
204
  )
205
+ if not enrich_node:
206
+ logger.error(
207
+ f"{padding(depth)}{LOGGER_PREFIX} filter node enrichment node could not be found"
208
+ )
209
+ return filter_node
197
210
  logger.info(
198
211
  f"{padding(depth)}{LOGGER_PREFIX} returning filter node and enrich node with {enrich_node.output_concepts} and {enrich_node.input_concepts}"
199
212
  )
@@ -1,15 +1,15 @@
1
1
  from typing import List
2
2
 
3
3
  from trilogy.constants import logger
4
- from trilogy.core.models import (
5
- AggregateWrapper,
6
- Concept,
7
- Environment,
8
- Function,
9
- Grain,
10
- LooseConceptList,
11
- WhereClause,
4
+ from trilogy.core.models.build import (
5
+ BuildAggregateWrapper,
6
+ BuildConcept,
7
+ BuildFunction,
8
+ BuildGrain,
9
+ BuildWhereClause,
10
+ LooseBuildConceptList,
12
11
  )
12
+ from trilogy.core.models.build_environment import BuildEnvironment
13
13
  from trilogy.core.processing.node_generators.common import (
14
14
  gen_enrichment_node,
15
15
  resolve_function_parent_concepts,
@@ -22,22 +22,22 @@ LOGGER_PREFIX = "[GEN_GROUP_NODE]"
22
22
 
23
23
 
24
24
  def gen_group_node(
25
- concept: Concept,
26
- local_optional: List[Concept],
27
- environment: Environment,
25
+ concept: BuildConcept,
26
+ local_optional: List[BuildConcept],
27
+ environment: BuildEnvironment,
28
28
  g,
29
29
  depth: int,
30
30
  source_concepts,
31
- history: History | None = None,
32
- conditions: WhereClause | None = None,
31
+ history: History,
32
+ conditions: BuildWhereClause | None = None,
33
33
  ) -> StrategyNode | None:
34
34
  # aggregates MUST always group to the proper grain
35
35
  # except when the
36
- parent_concepts: List[Concept] = unique(
36
+ parent_concepts: List[BuildConcept] = unique(
37
37
  resolve_function_parent_concepts(concept, environment=environment), "address"
38
38
  )
39
39
  logger.info(
40
- f"{padding(depth)}{LOGGER_PREFIX} parent concepts are {[x.address for x in parent_concepts]} from group grain {concept.grain}"
40
+ f"{padding(depth)}{LOGGER_PREFIX} parent concepts for {concept} are {[x.address for x in parent_concepts]} from group grain {concept.grain}"
41
41
  )
42
42
 
43
43
  # if the aggregation has a grain, we need to ensure these are the ONLY optional in the output of the select
@@ -53,16 +53,18 @@ def gen_group_node(
53
53
  output_concepts += grain_components
54
54
  for possible_agg in local_optional:
55
55
 
56
- if not isinstance(possible_agg.lineage, (AggregateWrapper, Function)):
56
+ if not isinstance(
57
+ possible_agg.lineage,
58
+ (BuildAggregateWrapper, BuildFunction),
59
+ ):
57
60
  continue
58
- logger.info(possible_agg)
59
61
  if possible_agg.grain and possible_agg.grain != concept.grain:
60
62
  logger.info(
61
63
  f"{padding(depth)}{LOGGER_PREFIX} mismatched equivalent group by with grain {possible_agg.grain} for {concept.address}"
62
64
  )
63
65
 
64
66
  if possible_agg.grain and possible_agg.grain == concept.grain:
65
- agg_parents: List[Concept] = resolve_function_parent_concepts(
67
+ agg_parents: List[BuildConcept] = resolve_function_parent_concepts(
66
68
  possible_agg,
67
69
  environment=environment,
68
70
  )
@@ -73,7 +75,7 @@ def gen_group_node(
73
75
  logger.info(
74
76
  f"{padding(depth)}{LOGGER_PREFIX} found equivalent group by optional concept {possible_agg.address} for {concept.address}"
75
77
  )
76
- elif Grain.from_concepts(agg_parents) == Grain.from_concepts(
78
+ elif BuildGrain.from_concepts(agg_parents) == BuildGrain.from_concepts(
77
79
  parent_concepts
78
80
  ):
79
81
  extra = [x for x in agg_parents if x.address not in parent_concepts]
@@ -84,11 +86,11 @@ def gen_group_node(
84
86
  )
85
87
  else:
86
88
  logger.info(
87
- f"{padding(depth)}{LOGGER_PREFIX} mismatched grain {Grain.from_concepts(agg_parents)} vs {Grain.from_concepts(parent_concepts)}"
89
+ f"{padding(depth)}{LOGGER_PREFIX} cannot include optional agg; mismatched grain {BuildGrain.from_concepts(agg_parents)} vs {BuildGrain.from_concepts(parent_concepts)}"
88
90
  )
89
91
  if parent_concepts:
90
92
  logger.info(
91
- f"{padding(depth)}{LOGGER_PREFIX} fetching group node parents {LooseConceptList(concepts=parent_concepts)}"
93
+ f"{padding(depth)}{LOGGER_PREFIX} fetching group node parents {LooseBuildConceptList(concepts=parent_concepts)}"
92
94
  )
93
95
  parent_concepts = unique(parent_concepts, "address")
94
96
  parent = source_concepts(
@@ -124,6 +126,9 @@ def gen_group_node(
124
126
  # early exit if no optional
125
127
 
126
128
  if not local_optional:
129
+ logger.info(
130
+ f"{padding(depth)}{LOGGER_PREFIX} no optional concepts, returning group node"
131
+ )
127
132
  return group_node
128
133
  missing_optional = [
129
134
  x.address for x in local_optional if x.address not in group_node.output_concepts
@@ -1,7 +1,8 @@
1
1
  from typing import List
2
2
 
3
3
  from trilogy.constants import logger
4
- from trilogy.core.models import Concept, Environment, Function, WhereClause
4
+ from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
5
+ from trilogy.core.models.build_environment import BuildEnvironment
5
6
  from trilogy.core.processing.nodes import (
6
7
  GroupNode,
7
8
  History,
@@ -14,20 +15,22 @@ LOGGER_PREFIX = "[GEN_GROUP_TO_NODE]"
14
15
 
15
16
 
16
17
  def gen_group_to_node(
17
- concept: Concept,
18
+ concept: BuildConcept,
18
19
  local_optional,
19
- environment: Environment,
20
+ environment: BuildEnvironment,
20
21
  g,
21
22
  depth: int,
22
23
  source_concepts,
23
24
  history: History | None = None,
24
- conditions: WhereClause | None = None,
25
+ conditions: BuildWhereClause | None = None,
25
26
  ) -> GroupNode | MergeNode:
26
27
  # aggregates MUST always group to the proper grain
27
- if not isinstance(concept.lineage, Function):
28
- raise SyntaxError("Group to should have function lineage")
28
+ if not isinstance(concept.lineage, BuildFunction):
29
+ raise SyntaxError(
30
+ f"Group to should have function lineage, is {type(concept.lineage)}"
31
+ )
29
32
  group_arg = concept.lineage.arguments[0]
30
- parent_concepts: List[Concept] = concept.lineage.concept_arguments
33
+ parent_concepts: List[BuildConcept] = concept.lineage.concept_arguments
31
34
  logger.info(
32
35
  f"{padding(depth)}{LOGGER_PREFIX} group by node has required parents {[x.address for x in parent_concepts]}"
33
36
  )
@@ -49,9 +52,9 @@ def gen_group_to_node(
49
52
  parents=parents,
50
53
  depth=depth,
51
54
  preexisting_conditions=conditions.conditional if conditions else None,
52
- hidden_concepts=(
53
- [group_arg]
54
- if isinstance(group_arg, Concept)
55
+ hidden_concepts=set(
56
+ [group_arg.address]
57
+ if isinstance(group_arg, BuildConcept)
55
58
  and group_arg.address not in local_optional
56
59
  else []
57
60
  ),