pytrilogy 0.0.3.92__py3-none-any.whl → 0.0.3.94__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.92
3
+ Version: 0.0.3.94
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,5 +1,5 @@
1
- pytrilogy-0.0.3.92.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=f4OB7VJAEeTW-QiyNBxHqoUJrj_7Dk6fryVxppzYalc,303
1
+ pytrilogy-0.0.3.94.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=GEadNJkWn6lKPBKuEwL1P2slgqo7djyKbAwsmVQZeLI,303
3
3
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  trilogy/constants.py,sha256=eKb_EJvSqjN9tGbdVEViwdtwwh8fZ3-jpOEDqL71y70,1691
5
5
  trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
@@ -12,24 +12,24 @@ trilogy/authoring/__init__.py,sha256=e74k-Jep4DLYPQU_2m0aVsYlw5HKTOucAKtdTbd6f2g
12
12
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  trilogy/core/constants.py,sha256=nizWYDCJQ1bigQMtkNIEMNTcN0NoEAXiIHLzpelxQ24,201
14
14
  trilogy/core/enums.py,sha256=RQRkpGHLtcBKAO6jZnmGVtSUnb00Q2rP56ltYGdfTok,8294
15
- trilogy/core/env_processor.py,sha256=pD_YYuDG6CMybmwW9H2w958RloA7lEeVbzKXP6ltz2o,4078
15
+ trilogy/core/env_processor.py,sha256=_DCnD_Qt-kxp5Ta7Apje2TLlgfc7sOpkRYXXQtQNsrw,4154
16
16
  trilogy/core/environment_helpers.py,sha256=VvPIiFemqaLLpIpLIqprfu63K7muZ1YzNg7UZIUph8w,8267
17
17
  trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
18
18
  trilogy/core/exceptions.py,sha256=jYEduuMehcMkmCpf-OC_taELPZm7qNfeSNzIWkDYScs,707
19
19
  trilogy/core/functions.py,sha256=hnfcNjAD-XQ572vEwuUEAdBf8zKFWYwPeHIpESjUpZs,32928
20
- trilogy/core/graph_models.py,sha256=zBzUwhYpnDJG91pWtk9ngw1WiTgHkMawyrqXptcGWGA,3847
20
+ trilogy/core/graph_models.py,sha256=S-0M4_OrU6W4AE2uf1LeS4RU-6CEUv59qvQ32lhav1Y,4479
21
21
  trilogy/core/internal.py,sha256=wFx4e1I0mtx159YFShSXeUBSQ82NINtAbOI-92RX4i8,2151
22
22
  trilogy/core/optimization.py,sha256=ojpn-p79lr03SSVQbbw74iPCyoYpDYBmj1dbZ3oXCjI,8860
23
23
  trilogy/core/query_processor.py,sha256=5aFgv-2LVM1Uku9cR_tFuTRDwyLnxc95bCMAHeFy2AY,20332
24
24
  trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
25
25
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- trilogy/core/models/author.py,sha256=iNnj2exehqj8gzoIwlfpTxhZpS-WqVJwc8OZbiDx1f4,81537
27
- trilogy/core/models/build.py,sha256=vPMVF7yv_1-sVVpUMupMfWt4BNXFYNSB-WjAL7Eo6lk,71968
28
- trilogy/core/models/build_environment.py,sha256=s_C9xAHuD3yZ26T15pWVBvoqvlp2LdZ8yjsv2_HdXLk,5363
26
+ trilogy/core/models/author.py,sha256=ZSKEJ6Vg4otpI_m7_JuGyrFM8dZV1HaxBwprvDSwUzo,81149
27
+ trilogy/core/models/build.py,sha256=ZwJJGyp4rVsISvL8Er_AxQdVJrafYc4fesSj4MNgoxU,70615
28
+ trilogy/core/models/build_environment.py,sha256=mpx7MKGc60fnZLVdeLi2YSREy7eQbQYycCrP4zF-rHU,5258
29
29
  trilogy/core/models/core.py,sha256=nnz3ZROlVT18uygEWqqbfbHmcJkm2UC3VVCrsri_-K0,12836
30
30
  trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
31
31
  trilogy/core/models/environment.py,sha256=0IHSCFf5e5b4LPQN3vmjumtfM1iD1tN4WMoUr0UqxZI,27855
32
- trilogy/core/models/execute.py,sha256=sVWhjwWull-T6pUJizhrYVGCWHY3eZivVN6KNlhcHig,41839
32
+ trilogy/core/models/execute.py,sha256=PLSwllj0RnViY_R5ZKjyuZIZJvfOYDJn_Af-tBthzGM,41842
33
33
  trilogy/core/optimizations/__init__.py,sha256=YH2-mGXZnVDnBcWVi8vTbrdw7Qs5TivG4h38rH3js_I,290
34
34
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
35
35
  trilogy/core/optimizations/inline_datasource.py,sha256=2sWNRpoRInnTgo9wExVT_r9RfLAQHI57reEV5cGHUcg,4329
@@ -47,20 +47,20 @@ trilogy/core/processing/node_generators/basic_node.py,sha256=TLZCv4WS196a-0g5xgK
47
47
  trilogy/core/processing/node_generators/common.py,sha256=PdysdroW9DUADP7f5Wv_GKPUyCTROZV1g3L45fawxi8,9443
48
48
  trilogy/core/processing/node_generators/constant_node.py,sha256=LfpDq2WrBRZ3tGsLxw77LuigKfhbteWWh9L8BGdMGwk,1146
49
49
  trilogy/core/processing/node_generators/filter_node.py,sha256=ArBsQJl-4fWBJWCE28CRQ7UT7ErnFfbcseoQQZrBodY,11220
50
- trilogy/core/processing/node_generators/group_node.py,sha256=1QJhRxsTklJ5xq8wHlAURZaN9gL9FPpeCa1OJ7IwXnY,6769
50
+ trilogy/core/processing/node_generators/group_node.py,sha256=8HJ1lkOvIXfX3xoS2IMbM_wCu_mT0J_hQ7xnTaxsVlo,6611
51
51
  trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
52
52
  trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
53
- trilogy/core/processing/node_generators/node_merge_node.py,sha256=rb6ltJyhAUFairG6LZJ111Zm2uQhXWsSZfSERahUNGc,18258
53
+ trilogy/core/processing/node_generators/node_merge_node.py,sha256=83FkcYuOFyDY0_0NWhL45MAT5J_6Y6L1h357WrJPzaI,18230
54
54
  trilogy/core/processing/node_generators/recursive_node.py,sha256=l5zdh0dURKwmAy8kK4OpMtZfyUEQRk6N-PwSWIyBpSM,2468
55
55
  trilogy/core/processing/node_generators/rowset_node.py,sha256=5L5u6xz1In8EaHQdcYgR2si-tz9WB9YLXURo4AkUT9A,6630
56
- trilogy/core/processing/node_generators/select_merge_node.py,sha256=Cv2GwNiYSmwewjuK8T3JB3pbgrLZFPsB75DCP153BMA,22818
56
+ trilogy/core/processing/node_generators/select_merge_node.py,sha256=CiuBzWX4KyO3G5_FKQmYzCH0R1qifvfj9LHJs9o5_Ro,22259
57
57
  trilogy/core/processing/node_generators/select_node.py,sha256=Ta1G39V94gjX_AgyZDz9OqnwLz4BjY3D6Drx9YpziMQ,3555
58
58
  trilogy/core/processing/node_generators/synonym_node.py,sha256=AnAsa_Wj50NJ_IK0HSgab_7klYmKVrv0WI1uUe-GvEY,3766
59
59
  trilogy/core/processing/node_generators/union_node.py,sha256=VNo6Oey4p8etU9xrOh2oTT2lIOTvY6PULUPRvVa2uxU,2877
60
60
  trilogy/core/processing/node_generators/unnest_node.py,sha256=ueOQtoTf2iJHO09RzWHDFQ5iKZq2fVhGf2KAF2U2kU8,2677
61
61
  trilogy/core/processing/node_generators/window_node.py,sha256=A90linr4pkZtTNfn9k2YNLqrJ_SFII3lbHxB-BC6mI8,6688
62
62
  trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
- trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=mKCDHxr2eyrdozXEHq_HvuwtBgnb9JPlImJIx6JGX34,7834
63
+ trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=m2YQ4OmG0N2O61a7NEq1ZzbTa7JsCC00lxB2ymjcYRI,8224
64
64
  trilogy/core/processing/nodes/__init__.py,sha256=zTge1EzwzEydlcMliIFO_TT7h7lS8l37lyZuQDir1h0,5487
65
65
  trilogy/core/processing/nodes/base_node.py,sha256=C_CjlOzlGMXckyV0b_PJZerpopNesRCKfambMq7Asvc,18221
66
66
  trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
@@ -98,7 +98,7 @@ trilogy/parsing/common.py,sha256=550-L0444GUuBFdiDWkOg_DxnMXtcJFUMES2R5zlwik,310
98
98
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
99
99
  trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
100
100
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
101
- trilogy/parsing/parse_engine.py,sha256=minAI04kKs5uZqRumafMCvC9lRwlcXCLmDigcNOF_7w,80639
101
+ trilogy/parsing/parse_engine.py,sha256=FrmEXEBs54z1RIQuHWBonuqsqjMynzuP03NP9GdFN-g,80655
102
102
  trilogy/parsing/render.py,sha256=HSNntD82GiiwHT-TWPLXAaIMWLYVV5B5zQEsgwrHIBE,19605
103
103
  trilogy/parsing/trilogy.lark,sha256=e2YVSxqzRov08AydtDSA8aqSJU2M1eJaidMEkHCdsYE,15896
104
104
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -111,8 +111,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
111
111
  trilogy/std/net.preql,sha256=WZCuvH87_rZntZiuGJMmBDMVKkdhTtxeHOkrXNwJ1EE,416
112
112
  trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
113
113
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
114
- pytrilogy-0.0.3.92.dist-info/METADATA,sha256=6MSVrJ4ZFyG-BbBG4-pDrhVYne9GLVR0sc8Olis00_g,9746
115
- pytrilogy-0.0.3.92.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
116
- pytrilogy-0.0.3.92.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
117
- pytrilogy-0.0.3.92.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
118
- pytrilogy-0.0.3.92.dist-info/RECORD,,
114
+ pytrilogy-0.0.3.94.dist-info/METADATA,sha256=DN1uvVPThwfS9W6P1Tgo8igcIUuVIXtN26iDO_sllSw,9746
115
+ pytrilogy-0.0.3.94.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
116
+ pytrilogy-0.0.3.94.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
117
+ pytrilogy-0.0.3.94.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
118
+ pytrilogy-0.0.3.94.dist-info/RECORD,,
trilogy/__init__.py CHANGED
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.92"
7
+ __version__ = "0.0.3.94"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -44,8 +44,10 @@ def add_concept(
44
44
  continue
45
45
  if pseudonym_node.split("@")[0] == node_name.split("@")[0]:
46
46
  continue
47
- g.add_edge(pseudonym_node, node_name, pseudonym=True)
48
- g.add_edge(node_name, pseudonym_node, pseudonym=True)
47
+ g.add_edge(pseudonym_node, node_name)
48
+ g.add_edge(node_name, pseudonym_node)
49
+ g.pseudonyms.add((pseudonym_node, node_name))
50
+ g.pseudonyms.add((node_name, pseudonym_node))
49
51
  add_concept(pseudonym, g, concept_mapping, default_concept_graph, seen)
50
52
 
51
53
 
@@ -1,50 +1,48 @@
1
+ from typing import Union
2
+
1
3
  import networkx as nx
2
4
 
3
5
  from trilogy.core.models.build import BuildConcept, BuildDatasource, BuildWhereClause
4
6
 
5
7
 
6
8
  def get_graph_exact_match(
7
- g: nx.DiGraph, accept_partial: bool, conditions: BuildWhereClause | None
9
+ g: Union[nx.DiGraph, "ReferenceGraph"],
10
+ accept_partial: bool,
11
+ conditions: BuildWhereClause | None,
8
12
  ) -> set[str]:
9
- datasources: dict[str, BuildDatasource | list[BuildDatasource]] = (
10
- nx.get_node_attributes(g, "datasource")
11
- )
12
13
  exact: set[str] = set()
13
- for node in g.nodes:
14
- if node in datasources:
15
- ds = datasources[node]
16
- if isinstance(ds, list):
17
- exact.add(node)
14
+ for node, ds in g.datasources.items():
15
+ if isinstance(ds, list):
16
+ exact.add(node)
17
+ continue
18
+
19
+ if not conditions and not ds.non_partial_for:
20
+ exact.add(node)
21
+ continue
22
+ elif not conditions and accept_partial and ds.non_partial_for:
23
+ exact.add(node)
24
+ continue
25
+ elif conditions:
26
+ if not ds.non_partial_for:
18
27
  continue
19
-
20
- if not conditions and not ds.non_partial_for:
21
- exact.add(node)
22
- continue
23
- elif not conditions and accept_partial and ds.non_partial_for:
28
+ if ds.non_partial_for and conditions == ds.non_partial_for:
24
29
  exact.add(node)
25
30
  continue
26
- elif conditions:
27
- if not ds.non_partial_for:
28
- continue
29
- if ds.non_partial_for and conditions == ds.non_partial_for:
30
- exact.add(node)
31
- continue
32
- else:
33
- continue
31
+ else:
32
+ continue
34
33
 
35
34
  return exact
36
35
 
37
36
 
38
37
  def prune_sources_for_conditions(
39
- g: nx.DiGraph,
38
+ g: "ReferenceGraph",
40
39
  accept_partial: bool,
41
40
  conditions: BuildWhereClause | None,
42
41
  ):
43
-
44
42
  complete = get_graph_exact_match(g, accept_partial, conditions)
45
43
  to_remove = []
46
- for node in g.nodes:
47
- if node.startswith("ds~") and node not in complete:
44
+ for node in g.datasources:
45
+ if node not in complete:
48
46
  to_remove.append(node)
49
47
 
50
48
  for node in to_remove:
@@ -68,37 +66,58 @@ def datasource_to_node(input: BuildDatasource) -> str:
68
66
  class ReferenceGraph(nx.DiGraph):
69
67
  def __init__(self, *args, **kwargs):
70
68
  super().__init__(*args, **kwargs)
71
-
72
- def add_node(self, node_for_adding, **attr):
69
+ self.concepts: dict[str, BuildConcept] = {}
70
+ self.datasources: dict[str, BuildDatasource] = {}
71
+ self.pseudonyms: set[tuple[str, str]] = set()
72
+
73
+ def copy(self):
74
+ g = ReferenceGraph()
75
+ g.concepts = self.concepts.copy()
76
+ g.datasources = self.datasources.copy()
77
+ g.pseudonyms = {*self.pseudonyms}
78
+ # g.add_nodes_from(self.nodes(data=True))
79
+ for node in self.nodes:
80
+ g.add_node(node, fast=True)
81
+ for edge in self.edges:
82
+ g.add_edge(edge[0], edge[1], fast=True)
83
+ # g.add_edges_from(self.edges(data=True))
84
+ return g
85
+
86
+ def remove_node(self, n):
87
+ if n in self.concepts:
88
+ del self.concepts[n]
89
+ if n in self.datasources:
90
+ del self.datasources[n]
91
+ super().remove_node(n)
92
+
93
+ def add_node(self, node_for_adding, fast: bool = False, **attr):
94
+ if fast:
95
+ return super().add_node(node_for_adding, **attr)
73
96
  if isinstance(node_for_adding, BuildConcept):
74
97
  node_name = concept_to_node(node_for_adding)
75
- # if node_name in self.nodes:
76
- # return
77
- attr["type"] = "concept"
78
- attr["concept"] = node_for_adding
79
- attr["grain"] = node_for_adding.grain
98
+ self.concepts[node_name] = node_for_adding
80
99
  elif isinstance(node_for_adding, BuildDatasource):
81
100
  node_name = datasource_to_node(node_for_adding)
82
- # if node_name in self.nodes:
83
- # return
84
- attr["type"] = "datasource"
85
- attr["ds"] = node_for_adding
86
- attr["grain"] = node_for_adding.grain
101
+ self.datasources[node_name] = node_for_adding
87
102
  else:
88
103
  node_name = node_for_adding
104
+ if attr.get("datasource"):
105
+ self.datasources[node_name] = attr["datasource"]
89
106
  super().add_node(node_name, **attr)
90
107
 
91
- def add_edge(self, u_of_edge, v_of_edge, **attr):
108
+ def add_edge(self, u_of_edge, v_of_edge, fast: bool = False, **attr):
109
+ if fast:
110
+ return super().add_edge(u_of_edge, v_of_edge, **attr)
92
111
  if isinstance(u_of_edge, BuildConcept):
93
112
  orig = u_of_edge
94
113
  u_of_edge = concept_to_node(u_of_edge)
95
114
  if u_of_edge not in self.nodes:
96
115
  self.add_node(orig)
97
116
  elif isinstance(u_of_edge, BuildDatasource):
98
- orig = u_of_edge
117
+ origd = u_of_edge
99
118
  u_of_edge = datasource_to_node(u_of_edge)
100
119
  if u_of_edge not in self.nodes:
101
- self.add_node(orig)
120
+ self.add_node(origd)
102
121
 
103
122
  if isinstance(v_of_edge, BuildConcept):
104
123
  orig = v_of_edge
@@ -106,8 +125,8 @@ class ReferenceGraph(nx.DiGraph):
106
125
  if v_of_edge not in self.nodes:
107
126
  self.add_node(orig)
108
127
  elif isinstance(v_of_edge, BuildDatasource):
109
- orig = v_of_edge
128
+ origd = v_of_edge
110
129
  v_of_edge = datasource_to_node(v_of_edge)
111
130
  if v_of_edge not in self.nodes:
112
- self.add_node(orig)
113
- super().add_edge(u_of_edge, v_of_edge, **attr)
131
+ self.add_node(origd)
132
+ super().add_edge(u_of_edge, v_of_edge)
@@ -902,7 +902,11 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
902
902
 
903
903
  @property
904
904
  def is_aggregate(self):
905
- return self.calculate_is_aggregate(self.lineage)
905
+ base = getattr(self, "_is_aggregate", None)
906
+ if base:
907
+ return base
908
+ setattr(self, "_is_aggregate", self.calculate_is_aggregate(self.lineage))
909
+ return self._is_aggregate
906
910
 
907
911
  def with_merge(self, source: Self, target: Self, modifiers: List[Modifier]) -> Self:
908
912
  if self.address == source.address:
@@ -1069,18 +1073,25 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1069
1073
  final_grain = grain if not self.grain.components else self.grain
1070
1074
  keys = self.keys
1071
1075
 
1072
- if self.is_aggregate and isinstance(new_lineage, Function) and grain.components:
1076
+ if self.is_aggregate and grain.components and isinstance(new_lineage, Function):
1073
1077
  grain_components: list[ConceptRef | Concept] = [
1074
1078
  environment.concepts[c].reference for c in grain.components
1075
1079
  ]
1076
- new_lineage = AggregateWrapper(function=new_lineage, by=grain_components)
1080
+ new_lineage = AggregateWrapper.model_construct(
1081
+ function=new_lineage, by=grain_components
1082
+ )
1077
1083
  final_grain = grain
1078
1084
  keys = set(grain.components)
1079
- elif isinstance(new_lineage, AggregateWrapper) and not new_lineage.by and grain:
1085
+ elif (
1086
+ grain
1087
+ and new_lineage
1088
+ and isinstance(new_lineage, AggregateWrapper)
1089
+ and not new_lineage.by
1090
+ ):
1080
1091
  grain_components = [
1081
1092
  environment.concepts[c].reference for c in grain.components
1082
1093
  ]
1083
- new_lineage = AggregateWrapper(
1094
+ new_lineage = AggregateWrapper.model_construct(
1084
1095
  function=new_lineage.function, by=grain_components
1085
1096
  )
1086
1097
  final_grain = grain
@@ -1133,7 +1144,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1133
1144
  granularity=self.granularity,
1134
1145
  derivation=self.derivation,
1135
1146
  lineage=self.lineage,
1136
- grain=grain if grain else Grain(components=set()),
1147
+ grain=grain if grain else Grain.model_construct(components=set()),
1137
1148
  namespace=self.namespace,
1138
1149
  keys=self.keys,
1139
1150
  modifiers=self.modifiers,
@@ -1670,15 +1681,6 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1670
1681
  def datatype(self):
1671
1682
  return self.output_datatype
1672
1683
 
1673
- @field_validator("output_datatype")
1674
- @classmethod
1675
- def parse_output_datatype(cls, v, info: ValidationInfo):
1676
- values = info.data
1677
- if values.get("operator") == FunctionType.ATTR_ACCESS:
1678
- if isinstance(v, StructType):
1679
- raise SyntaxError
1680
- return v
1681
-
1682
1684
  @field_validator("arguments", mode="before")
1683
1685
  @classmethod
1684
1686
  def parse_arguments(cls, v, info: ValidationInfo):
@@ -1845,17 +1847,6 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1845
1847
  base += get_concept_arguments(arg)
1846
1848
  return base
1847
1849
 
1848
- @property
1849
- def output_grain(self):
1850
- # aggregates have an abstract grain
1851
- base_grain = Grain(components=[])
1852
- if self.operator in FunctionClass.AGGREGATE_FUNCTIONS.value:
1853
- return base_grain
1854
- # scalars have implicit grain of all arguments
1855
- for input in self.concept_arguments:
1856
- base_grain += input.grain
1857
- return base_grain
1858
-
1859
1850
 
1860
1851
  class FunctionCallWrapper(
1861
1852
  DataTyped,