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

@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from abc import ABC
4
4
  from collections import defaultdict
5
+ from dataclasses import dataclass, field
5
6
  from datetime import date, datetime
6
7
  from functools import cached_property, singledispatchmethod
7
8
  from typing import (
@@ -18,12 +19,7 @@ from typing import (
18
19
  )
19
20
 
20
21
  from pydantic import (
21
- BaseModel,
22
22
  ConfigDict,
23
- Field,
24
- ValidationInfo,
25
- computed_field,
26
- field_validator,
27
23
  )
28
24
 
29
25
  from trilogy.constants import DEFAULT_NAMESPACE, MagicConstants
@@ -93,7 +89,6 @@ from trilogy.core.models.datasource import (
93
89
  RawColumnExpr,
94
90
  )
95
91
  from trilogy.core.models.environment import Environment
96
- from trilogy.utility import unique
97
92
 
98
93
  # TODO: refactor to avoid these
99
94
  if TYPE_CHECKING:
@@ -149,8 +144,8 @@ def concept_is_relevant(
149
144
 
150
145
  def concepts_to_build_grain_concepts(
151
146
  concepts: Iterable[BuildConcept | str], environment: "BuildEnvironment" | None
152
- ) -> list[BuildConcept]:
153
- pconcepts = []
147
+ ) -> set[str]:
148
+ pconcepts: list[BuildConcept] = []
154
149
  for c in concepts:
155
150
  if isinstance(c, BuildConcept):
156
151
  pconcepts.append(c)
@@ -162,27 +157,23 @@ def concepts_to_build_grain_concepts(
162
157
  f"Unable to resolve input {c} without environment provided to concepts_to_grain call"
163
158
  )
164
159
 
165
- final: List[BuildConcept] = []
160
+ final: set[str] = set()
166
161
  for sub in pconcepts:
167
162
  if not concept_is_relevant(sub, pconcepts):
168
163
  continue
169
- final.append(sub)
170
- final = unique(final, "address")
171
- v2 = sorted(final, key=lambda x: x.name)
172
- return v2
164
+ final.add(sub.address)
173
165
 
166
+ return final
174
167
 
175
- class LooseBuildConceptList(BaseModel):
168
+
169
+ @dataclass
170
+ class LooseBuildConceptList:
176
171
  concepts: Sequence[BuildConcept]
177
172
 
178
173
  @cached_property
179
174
  def addresses(self) -> set[str]:
180
175
  return {s.address for s in self.concepts}
181
176
 
182
- @classmethod
183
- def validate(cls, v):
184
- return cls(v)
185
-
186
177
  @cached_property
187
178
  def sorted_addresses(self) -> List[str]:
188
179
  return sorted(list(self.addresses))
@@ -250,7 +241,8 @@ def get_concept_arguments(expr) -> List["BuildConcept"]:
250
241
  return output
251
242
 
252
243
 
253
- class BuildParamaterizedConceptReference(DataTyped, BaseModel):
244
+ @dataclass
245
+ class BuildParamaterizedConceptReference(DataTyped):
254
246
  concept: BuildConcept
255
247
 
256
248
  def __str__(self):
@@ -265,12 +257,17 @@ class BuildParamaterizedConceptReference(DataTyped, BaseModel):
265
257
  return self.concept.output_datatype
266
258
 
267
259
 
268
- class BuildGrain(BaseModel):
269
- components: set[str] = Field(default_factory=set)
260
+ @dataclass
261
+ class BuildGrain:
262
+ components: set[str] = field(default_factory=set)
270
263
  where_clause: Optional[BuildWhereClause] = None
264
+ _str: str | None = None
265
+ _str_no_condition: str | None = None
271
266
 
272
267
  def without_condition(self):
273
- return BuildGrain.model_construct(components=self.components)
268
+ if not self.where_clause:
269
+ return self
270
+ return BuildGrain(components=self.components)
274
271
 
275
272
  @classmethod
276
273
  def from_concepts(
@@ -281,32 +278,12 @@ class BuildGrain(BaseModel):
281
278
  ) -> "BuildGrain":
282
279
 
283
280
  return BuildGrain(
284
- components={
285
- c.address
286
- for c in concepts_to_build_grain_concepts(
287
- concepts, environment=environment
288
- )
289
- },
281
+ components=concepts_to_build_grain_concepts(
282
+ concepts, environment=environment
283
+ ),
290
284
  where_clause=where_clause,
291
285
  )
292
286
 
293
- @field_validator("components", mode="before")
294
- def component_validator(cls, v, info: ValidationInfo):
295
- output = set()
296
- if isinstance(v, list):
297
- for vc in v:
298
- if isinstance(vc, BuildConcept):
299
- output.add(vc.address)
300
- else:
301
- output.add(vc)
302
- else:
303
- output = v
304
- if not isinstance(output, set):
305
- raise ValueError(f"Invalid grain component {output}, is not set")
306
- if not all(isinstance(x, str) for x in output):
307
- raise ValueError(f"Invalid component {output}")
308
- return output
309
-
310
287
  def __add__(self, other: "BuildGrain") -> "BuildGrain":
311
288
  if not other:
312
289
  return self
@@ -325,12 +302,12 @@ class BuildGrain(BaseModel):
325
302
  # raise NotImplementedError(
326
303
  # f"Cannot merge grains with where clauses, self {self.where_clause} other {other.where_clause}"
327
304
  # )
328
- return BuildGrain.model_construct(
305
+ return BuildGrain(
329
306
  components=self.components.union(other.components), where_clause=where
330
307
  )
331
308
 
332
309
  def __sub__(self, other: "BuildGrain") -> "BuildGrain":
333
- return BuildGrain.model_construct(
310
+ return BuildGrain(
334
311
  components=self.components.difference(other.components),
335
312
  where_clause=self.where_clause,
336
313
  )
@@ -366,15 +343,35 @@ class BuildGrain(BaseModel):
366
343
  intersection = self.components.intersection(other.components)
367
344
  return BuildGrain(components=intersection)
368
345
 
369
- def __str__(self):
346
+ def _calculate_string(self):
370
347
  if self.abstract:
371
348
  base = "Grain<Abstract>"
372
349
  else:
373
- base = "Grain<" + ",".join([c for c in sorted(list(self.components))]) + ">"
350
+ base = "Grain<" + ",".join(sorted(self.components)) + ">"
374
351
  if self.where_clause:
375
352
  base += f"|{str(self.where_clause)}"
376
353
  return base
377
354
 
355
+ def _calculate_string_no_condition(self):
356
+ if self.abstract:
357
+ base = "Grain<Abstract>"
358
+ else:
359
+ base = "Grain<" + ",".join(sorted(self.components)) + ">"
360
+ return base
361
+
362
+ @property
363
+ def str_no_condition(self):
364
+ if self._str_no_condition:
365
+ return self._str_no_condition
366
+ self._str_no_condition = self._calculate_string_no_condition()
367
+ return self._str_no_condition
368
+
369
+ def __str__(self):
370
+ if self._str:
371
+ return self._str
372
+ self._str = self._calculate_string()
373
+ return self._str
374
+
378
375
  def __radd__(self, other) -> "BuildGrain":
379
376
  if other == 0:
380
377
  return self
@@ -382,7 +379,8 @@ class BuildGrain(BaseModel):
382
379
  return self.__add__(other)
383
380
 
384
381
 
385
- class BuildParenthetical(DataTyped, ConstantInlineable, BuildConceptArgs, BaseModel):
382
+ @dataclass
383
+ class BuildParenthetical(DataTyped, ConstantInlineable, BuildConceptArgs):
386
384
  content: "BuildExpr"
387
385
 
388
386
  def __add__(self, other) -> Union["BuildParenthetical", "BuildConditional"]:
@@ -436,7 +434,8 @@ class BuildParenthetical(DataTyped, ConstantInlineable, BuildConceptArgs, BaseMo
436
434
  return arg_to_datatype(self.content)
437
435
 
438
436
 
439
- class BuildConditional(BuildConceptArgs, ConstantInlineable, BaseModel):
437
+ @dataclass
438
+ class BuildConditional(BuildConceptArgs, ConstantInlineable):
440
439
  left: Union[
441
440
  int,
442
441
  str,
@@ -564,7 +563,8 @@ class BuildConditional(BuildConceptArgs, ConstantInlineable, BaseModel):
564
563
  return chunks
565
564
 
566
565
 
567
- class BuildWhereClause(BuildConceptArgs, BaseModel):
566
+ @dataclass
567
+ class BuildWhereClause(BuildConceptArgs):
568
568
  conditional: Union[
569
569
  BuildSubselectComparison,
570
570
  BuildComparison,
@@ -600,7 +600,8 @@ class BuildHavingClause(BuildWhereClause):
600
600
  pass
601
601
 
602
602
 
603
- class BuildComparison(BuildConceptArgs, ConstantInlineable, BaseModel):
603
+ @dataclass
604
+ class BuildComparison(BuildConceptArgs, ConstantInlineable):
604
605
 
605
606
  left: Union[
606
607
  int,
@@ -730,6 +731,7 @@ class BuildComparison(BuildConceptArgs, ConstantInlineable, BaseModel):
730
731
  return output
731
732
 
732
733
 
734
+ @dataclass
733
735
  class BuildSubselectComparison(BuildComparison):
734
736
  left: Union[
735
737
  int,
@@ -791,18 +793,18 @@ class BuildSubselectComparison(BuildComparison):
791
793
  return [tuple(get_concept_arguments(self.right))]
792
794
 
793
795
 
794
- class BuildConcept(Addressable, BuildConceptArgs, DataTyped, BaseModel):
796
+ @dataclass
797
+ class BuildConcept(Addressable, BuildConceptArgs, DataTyped):
795
798
  model_config = ConfigDict(extra="forbid")
796
799
  name: str
797
- datatype: DataType | ArrayType | StructType | MapType | NumericType
800
+ datatype: DataType | ArrayType | StructType | MapType | NumericType | TraitDataType
798
801
  purpose: Purpose
799
802
  build_is_aggregate: bool
800
803
  derivation: Derivation = Derivation.ROOT
801
804
  granularity: Granularity = Granularity.MULTI_ROW
802
- namespace: Optional[str] = Field(default=DEFAULT_NAMESPACE, validate_default=True)
803
- metadata: Metadata = Field(
805
+ namespace: Optional[str] = field(default=DEFAULT_NAMESPACE)
806
+ metadata: Metadata = field(
804
807
  default_factory=lambda: Metadata(description=None, line_number=None),
805
- validate_default=True,
806
808
  )
807
809
  lineage: Optional[
808
810
  Union[
@@ -816,22 +818,23 @@ class BuildConcept(Addressable, BuildConceptArgs, DataTyped, BaseModel):
816
818
  ] = None
817
819
 
818
820
  keys: Optional[set[str]] = None
819
- grain: BuildGrain = Field(default=None, validate_default=True) # type: ignore
820
- modifiers: List[Modifier] = Field(default_factory=list) # type: ignore
821
- pseudonyms: set[str] = Field(default_factory=set)
821
+ grain: BuildGrain = field(default=None) # type: ignore
822
+ modifiers: List[Modifier] = field(default_factory=list) # type: ignore
823
+ pseudonyms: set[str] = field(default_factory=set)
822
824
 
823
825
  @property
824
826
  def is_aggregate(self) -> bool:
825
827
  return self.build_is_aggregate
826
828
 
827
- def duplicate(self) -> BuildConcept:
828
- return self.model_copy(deep=True)
829
-
830
- def __hash__(self):
829
+ @cached_property
830
+ def hash(self) -> int:
831
831
  return hash(
832
832
  f"{self.name}+{self.datatype}+ {self.purpose} + {str(self.lineage)} + {self.namespace} + {str(self.grain)} + {str(self.keys)}"
833
833
  )
834
834
 
835
+ def __hash__(self):
836
+ return self.hash
837
+
835
838
  def __repr__(self):
836
839
  base = f"{self.address}@{self.grain}"
837
840
  return base
@@ -884,7 +887,7 @@ class BuildConcept(Addressable, BuildConceptArgs, DataTyped, BaseModel):
884
887
  ]
885
888
  )
886
889
  )
887
- return self.__class__.model_construct(
890
+ return self.__class__(
888
891
  name=self.name,
889
892
  datatype=self.datatype,
890
893
  purpose=self.purpose,
@@ -926,7 +929,7 @@ class BuildConcept(Addressable, BuildConceptArgs, DataTyped, BaseModel):
926
929
  grain = self.grain
927
930
  else:
928
931
  grain = self.grain # type: ignore
929
- return self.__class__.model_construct(
932
+ return self.__class__(
930
933
  name=self.name,
931
934
  datatype=self.datatype,
932
935
  purpose=self.purpose,
@@ -982,7 +985,8 @@ class BuildConcept(Addressable, BuildConceptArgs, DataTyped, BaseModel):
982
985
  return self.lineage.concept_arguments if self.lineage else []
983
986
 
984
987
 
985
- class BuildOrderItem(DataTyped, BuildConceptArgs, BaseModel):
988
+ @dataclass
989
+ class BuildOrderItem(DataTyped, BuildConceptArgs):
986
990
  expr: BuildExpr
987
991
  order: Ordering
988
992
 
@@ -1013,11 +1017,12 @@ class BuildOrderItem(DataTyped, BuildConceptArgs, BaseModel):
1013
1017
  return arg_to_datatype(self.expr)
1014
1018
 
1015
1019
 
1016
- class BuildWindowItem(DataTyped, BuildConceptArgs, BaseModel):
1020
+ @dataclass
1021
+ class BuildWindowItem(DataTyped, BuildConceptArgs):
1017
1022
  type: WindowType
1018
1023
  content: BuildConcept
1019
1024
  order_by: List[BuildOrderItem]
1020
- over: List["BuildConcept"] = Field(default_factory=list)
1025
+ over: List["BuildConcept"] = field(default_factory=list)
1021
1026
  index: Optional[int] = None
1022
1027
 
1023
1028
  def __repr__(self) -> str:
@@ -1050,7 +1055,8 @@ class BuildWindowItem(DataTyped, BuildConceptArgs, BaseModel):
1050
1055
  return Purpose.PROPERTY
1051
1056
 
1052
1057
 
1053
- class BuildCaseWhen(BuildConceptArgs, BaseModel):
1058
+ @dataclass
1059
+ class BuildCaseWhen(BuildConceptArgs):
1054
1060
  comparison: BuildConditional | BuildSubselectComparison | BuildComparison
1055
1061
  expr: "BuildExpr"
1056
1062
 
@@ -1071,30 +1077,18 @@ class BuildCaseWhen(BuildConceptArgs, BaseModel):
1071
1077
  )
1072
1078
 
1073
1079
 
1074
- class BuildCaseElse(BuildConceptArgs, BaseModel):
1080
+ @dataclass
1081
+ class BuildCaseElse(BuildConceptArgs):
1075
1082
  expr: "BuildExpr"
1076
- # this ensures that it's easily differentiable from CaseWhen
1077
- discriminant: ComparisonOperator = ComparisonOperator.ELSE
1078
1083
 
1079
1084
  @property
1080
1085
  def concept_arguments(self):
1081
1086
  return get_concept_arguments(self.expr)
1082
1087
 
1083
1088
 
1084
- class BuildFunction(DataTyped, BuildConceptArgs, BaseModel):
1085
- # class BuildFunction(Function):
1089
+ @dataclass
1090
+ class BuildFunction(DataTyped, BuildConceptArgs):
1086
1091
  operator: FunctionType
1087
- arg_count: int = Field(default=1)
1088
- output_datatype: (
1089
- DataType | ArrayType | StructType | MapType | NumericType | TraitDataType
1090
- )
1091
- output_purpose: Purpose
1092
- valid_inputs: Optional[
1093
- Union[
1094
- Set[DataType],
1095
- List[Set[DataType]],
1096
- ]
1097
- ] = None
1098
1092
  arguments: Sequence[
1099
1093
  Union[
1100
1094
  int,
@@ -1120,6 +1114,17 @@ class BuildFunction(DataTyped, BuildConceptArgs, BaseModel):
1120
1114
  ListWrapper[Any],
1121
1115
  ]
1122
1116
  ]
1117
+ output_data_type: (
1118
+ DataType | ArrayType | StructType | MapType | NumericType | TraitDataType
1119
+ )
1120
+ output_purpose: Purpose = field(default=Purpose.KEY)
1121
+ arg_count: int = field(default=1)
1122
+ valid_inputs: Optional[
1123
+ Union[
1124
+ Set[DataType],
1125
+ List[Set[DataType]],
1126
+ ]
1127
+ ] = None
1123
1128
 
1124
1129
  def __repr__(self):
1125
1130
  return f'{self.operator.value}({",".join([str(a) for a in self.arguments])})'
@@ -1129,7 +1134,11 @@ class BuildFunction(DataTyped, BuildConceptArgs, BaseModel):
1129
1134
 
1130
1135
  @property
1131
1136
  def datatype(self):
1132
- return self.output_datatype
1137
+ return self.output_data_type
1138
+
1139
+ @property
1140
+ def output_datatype(self):
1141
+ return self.output_data_type
1133
1142
 
1134
1143
  @property
1135
1144
  def concept_arguments(self) -> List[BuildConcept]:
@@ -1141,18 +1150,19 @@ class BuildFunction(DataTyped, BuildConceptArgs, BaseModel):
1141
1150
  @property
1142
1151
  def output_grain(self):
1143
1152
  # aggregates have an abstract grain
1144
- base_grain = BuildGrain(components=[])
1145
1153
  if self.operator in FunctionClass.AGGREGATE_FUNCTIONS.value:
1146
- return base_grain
1154
+ return BuildGrain(components=[])
1147
1155
  # scalars have implicit grain of all arguments
1156
+ args = set()
1148
1157
  for input in self.concept_arguments:
1149
- base_grain += input.grain
1150
- return base_grain
1158
+ args += input.grain.components
1159
+ return BuildGrain(components=args)
1151
1160
 
1152
1161
 
1153
- class BuildAggregateWrapper(BuildConceptArgs, DataTyped, BaseModel):
1162
+ @dataclass
1163
+ class BuildAggregateWrapper(BuildConceptArgs, DataTyped):
1154
1164
  function: BuildFunction
1155
- by: List[BuildConcept] = Field(default_factory=list)
1165
+ by: List[BuildConcept] = field(default_factory=list)
1156
1166
 
1157
1167
  def __str__(self):
1158
1168
  grain_str = [str(c) for c in self.by] if self.by else "abstract"
@@ -1175,7 +1185,8 @@ class BuildAggregateWrapper(BuildConceptArgs, DataTyped, BaseModel):
1175
1185
  return self.function.output_purpose
1176
1186
 
1177
1187
 
1178
- class BuildFilterItem(BuildConceptArgs, BaseModel):
1188
+ @dataclass
1189
+ class BuildFilterItem(BuildConceptArgs):
1179
1190
  content: "BuildExpr"
1180
1191
  where: BuildWhereClause
1181
1192
 
@@ -1207,13 +1218,15 @@ class BuildFilterItem(BuildConceptArgs, BaseModel):
1207
1218
  return self.where.concept_arguments
1208
1219
 
1209
1220
 
1210
- class BuildRowsetLineage(BuildConceptArgs, BaseModel):
1221
+ @dataclass
1222
+ class BuildRowsetLineage(BuildConceptArgs):
1211
1223
  name: str
1212
1224
  derived_concepts: List[str]
1213
1225
  select: SelectLineage | MultiSelectLineage
1214
1226
 
1215
1227
 
1216
- class BuildRowsetItem(DataTyped, BuildConceptArgs, BaseModel):
1228
+ @dataclass
1229
+ class BuildRowsetItem(DataTyped, BuildConceptArgs):
1217
1230
  content: BuildConcept
1218
1231
  rowset: BuildRowsetLineage
1219
1232
 
@@ -1240,7 +1253,8 @@ class BuildRowsetItem(DataTyped, BuildConceptArgs, BaseModel):
1240
1253
  return [self.content]
1241
1254
 
1242
1255
 
1243
- class BuildOrderBy(BaseModel):
1256
+ @dataclass
1257
+ class BuildOrderBy:
1244
1258
  items: List[BuildOrderItem]
1245
1259
 
1246
1260
  @property
@@ -1248,39 +1262,42 @@ class BuildOrderBy(BaseModel):
1248
1262
  return [x.expr for x in self.items]
1249
1263
 
1250
1264
 
1251
- class BuildAlignClause(BaseModel):
1265
+ @dataclass
1266
+ class BuildAlignClause:
1252
1267
  items: List[BuildAlignItem]
1253
1268
 
1254
1269
 
1255
- class BuildSelectLineage(BaseModel):
1270
+ @dataclass
1271
+ class BuildSelectLineage:
1256
1272
  selection: List[BuildConcept]
1257
1273
  hidden_components: set[str]
1258
1274
  local_concepts: dict[str, BuildConcept]
1259
1275
  order_by: Optional[BuildOrderBy] = None
1260
1276
  limit: Optional[int] = None
1261
- meta: Metadata = Field(default_factory=lambda: Metadata())
1262
- grain: BuildGrain = Field(default_factory=BuildGrain)
1263
- where_clause: BuildWhereClause | None = Field(default=None)
1264
- having_clause: BuildHavingClause | None = Field(default=None)
1277
+ meta: Metadata = field(default_factory=lambda: Metadata())
1278
+ grain: BuildGrain = field(default_factory=BuildGrain)
1279
+ where_clause: BuildWhereClause | None = field(default=None)
1280
+ having_clause: BuildHavingClause | None = field(default=None)
1265
1281
 
1266
1282
  @property
1267
1283
  def output_components(self) -> List[BuildConcept]:
1268
1284
  return self.selection
1269
1285
 
1270
1286
 
1271
- class BuildMultiSelectLineage(BuildConceptArgs, BaseModel):
1287
+ @dataclass
1288
+ class BuildMultiSelectLineage(BuildConceptArgs):
1272
1289
  selects: List[SelectLineage]
1273
1290
  grain: BuildGrain
1274
1291
  align: BuildAlignClause
1275
1292
  namespace: str
1276
- order_by: Optional[BuildOrderBy] = None
1277
- limit: Optional[int] = None
1278
- where_clause: Union["BuildWhereClause", None] = Field(default=None)
1279
- having_clause: Union["BuildHavingClause", None] = Field(default=None)
1280
1293
  local_concepts: dict[str, BuildConcept]
1281
1294
  build_concept_arguments: list[BuildConcept]
1282
1295
  build_output_components: list[BuildConcept]
1283
1296
  hidden_components: set[str]
1297
+ order_by: Optional[BuildOrderBy] = None
1298
+ limit: Optional[int] = None
1299
+ where_clause: Union["BuildWhereClause", None] = field(default=None)
1300
+ having_clause: Union["BuildHavingClause", None] = field(default=None)
1284
1301
 
1285
1302
  @property
1286
1303
  def derived_concepts(self) -> set[str]:
@@ -1322,12 +1339,12 @@ class BuildMultiSelectLineage(BuildConceptArgs, BaseModel):
1322
1339
  )
1323
1340
 
1324
1341
 
1325
- class BuildAlignItem(BaseModel):
1342
+ @dataclass
1343
+ class BuildAlignItem:
1326
1344
  alias: str
1327
1345
  concepts: List[BuildConcept]
1328
- namespace: str = Field(default=DEFAULT_NAMESPACE, validate_default=True)
1346
+ namespace: str = field(default=DEFAULT_NAMESPACE)
1329
1347
 
1330
- @computed_field # type: ignore
1331
1348
  @cached_property
1332
1349
  def concepts_lcl(self) -> LooseBuildConceptList:
1333
1350
  return LooseBuildConceptList(concepts=self.concepts)
@@ -1337,10 +1354,11 @@ class BuildAlignItem(BaseModel):
1337
1354
  return f"{self.namespace}.{self.alias}"
1338
1355
 
1339
1356
 
1340
- class BuildColumnAssignment(BaseModel):
1341
- alias: str | RawColumnExpr | BuildFunction
1357
+ @dataclass
1358
+ class BuildColumnAssignment:
1359
+ alias: str | RawColumnExpr | BuildFunction | BuildAggregateWrapper
1342
1360
  concept: BuildConcept
1343
- modifiers: List[Modifier] = Field(default_factory=list)
1361
+ modifiers: List[Modifier] = field(default_factory=list)
1344
1362
 
1345
1363
  @property
1346
1364
  def is_complete(self) -> bool:
@@ -1351,15 +1369,16 @@ class BuildColumnAssignment(BaseModel):
1351
1369
  return Modifier.NULLABLE in self.modifiers
1352
1370
 
1353
1371
 
1354
- class BuildDatasource(BaseModel):
1372
+ @dataclass
1373
+ class BuildDatasource:
1355
1374
  name: str
1356
1375
  columns: List[BuildColumnAssignment]
1357
1376
  address: Union[Address, str]
1358
- grain: BuildGrain = Field(
1359
- default_factory=lambda: BuildGrain(components=set()), validate_default=True
1377
+ grain: BuildGrain = field(
1378
+ default_factory=lambda: BuildGrain(components=set()),
1360
1379
  )
1361
- namespace: Optional[str] = Field(default=DEFAULT_NAMESPACE, validate_default=True)
1362
- metadata: DatasourceMetadata = Field(
1380
+ namespace: Optional[str] = field(default=DEFAULT_NAMESPACE)
1381
+ metadata: DatasourceMetadata = field(
1363
1382
  default_factory=lambda: DatasourceMetadata(freshness_concept=None)
1364
1383
  )
1365
1384
  where: Optional[BuildWhereClause] = None
@@ -1428,7 +1447,7 @@ class BuildDatasource(BaseModel):
1428
1447
  concept: BuildConcept,
1429
1448
  use_raw_name: bool = True,
1430
1449
  force_alias: bool = False,
1431
- ) -> Optional[str | RawColumnExpr] | BuildFunction:
1450
+ ) -> Optional[str | RawColumnExpr] | BuildFunction | BuildAggregateWrapper:
1432
1451
  # 2022-01-22
1433
1452
  # this logic needs to be refined.
1434
1453
  # if concept.lineage:
@@ -1472,8 +1491,6 @@ BuildExpr = (
1472
1491
  | list
1473
1492
  )
1474
1493
 
1475
- BuildConcept.model_rebuild()
1476
-
1477
1494
 
1478
1495
  def get_canonical_pseudonyms(environment: Environment) -> dict[str, set[str]]:
1479
1496
  roots: dict[str, set[str]] = defaultdict(set)
@@ -1507,13 +1524,15 @@ class Factory:
1507
1524
  environment: Environment,
1508
1525
  local_concepts: dict[str, BuildConcept] | None = None,
1509
1526
  grain: Grain | None = None,
1527
+ pseudonym_map: dict[str, set[str]] | None = None,
1510
1528
  ):
1511
1529
  self.grain = grain or Grain()
1512
1530
  self.environment = environment
1513
1531
  self.local_concepts: dict[str, BuildConcept] = (
1514
1532
  {} if local_concepts is None else local_concepts
1515
1533
  )
1516
- self.pseudonym_map = get_canonical_pseudonyms(environment)
1534
+ self.local_non_build_concepts: dict[str, Concept] = {}
1535
+ self.pseudonym_map = pseudonym_map or get_canonical_pseudonyms(environment)
1517
1536
 
1518
1537
  def instantiate_concept(
1519
1538
  self,
@@ -1531,14 +1550,19 @@ class Factory:
1531
1550
  | date
1532
1551
  ),
1533
1552
  ) -> tuple[Concept, BuildConcept]:
1534
- from trilogy.parsing.common import arbitrary_to_concept
1553
+ from trilogy.parsing.common import arbitrary_to_concept, generate_concept_name
1535
1554
 
1555
+ name = generate_concept_name(arg)
1556
+ if name in self.local_concepts and name in self.local_non_build_concepts:
1557
+ # if we already have this concept, return it
1558
+ return self.local_non_build_concepts[name], self.local_concepts[name]
1536
1559
  new = arbitrary_to_concept(
1537
1560
  arg,
1538
1561
  environment=self.environment,
1539
1562
  )
1540
- built = self.build(new)
1541
- self.local_concepts[new.address] = built
1563
+ built = self._build_concept(new)
1564
+ self.local_concepts[name] = built
1565
+ self.local_non_build_concepts[name] = new
1542
1566
  return new, built
1543
1567
 
1544
1568
  @singledispatchmethod
@@ -1576,15 +1600,23 @@ class Factory:
1576
1600
  | DatePart
1577
1601
  | NumericType
1578
1602
  ):
1603
+ return self._build_primitive(base)
1604
+
1605
+ def _build_primitive(self, base):
1579
1606
  return base
1580
1607
 
1581
1608
  @build.register
1582
1609
  def _(self, base: None) -> None:
1610
+ return self._build_none(base)
1611
+
1612
+ def _build_none(self, base):
1583
1613
  return base
1584
1614
 
1585
1615
  @build.register
1586
1616
  def _(self, base: Function) -> BuildFunction | BuildAggregateWrapper:
1617
+ return self._build_function(base)
1587
1618
 
1619
+ def _build_function(self, base: Function) -> BuildFunction | BuildAggregateWrapper:
1588
1620
  raw_args: list[Concept | FuncArgs] = []
1589
1621
  for arg in base.arguments:
1590
1622
  # to do proper discovery, we need to inject virtual intermediate concepts
@@ -1621,22 +1653,22 @@ class Factory:
1621
1653
  )
1622
1654
  )
1623
1655
 
1624
- return BuildFunction.model_construct(
1656
+ return BuildFunction(
1625
1657
  operator=base.operator,
1626
1658
  arguments=[
1627
1659
  rval,
1628
1660
  *[self.handle_constant(self.build(c)) for c in raw_args[1:]],
1629
1661
  ],
1630
- output_datatype=base.output_datatype,
1662
+ output_data_type=base.output_datatype,
1631
1663
  output_purpose=base.output_purpose,
1632
1664
  valid_inputs=base.valid_inputs,
1633
1665
  arg_count=base.arg_count,
1634
1666
  )
1635
1667
 
1636
- new = BuildFunction.model_construct(
1668
+ new = BuildFunction(
1637
1669
  operator=base.operator,
1638
1670
  arguments=[self.handle_constant(self.build(c)) for c in raw_args],
1639
- output_datatype=base.output_datatype,
1671
+ output_data_type=base.output_datatype,
1640
1672
  output_purpose=base.output_purpose,
1641
1673
  valid_inputs=base.valid_inputs,
1642
1674
  arg_count=base.arg_count,
@@ -1645,40 +1677,50 @@ class Factory:
1645
1677
 
1646
1678
  @build.register
1647
1679
  def _(self, base: ConceptRef) -> BuildConcept:
1680
+ return self._build_concept_ref(base)
1681
+
1682
+ def _build_concept_ref(self, base: ConceptRef) -> BuildConcept:
1648
1683
  if base.address in self.local_concepts:
1649
1684
  full = self.local_concepts[base.address]
1650
1685
  if isinstance(full, BuildConcept):
1651
1686
  return full
1652
1687
  if base.address in self.environment.concepts:
1653
1688
  raw = self.environment.concepts[base.address]
1654
- return self.build(raw)
1689
+ return self._build_concept(raw)
1655
1690
  # this will error by design - TODO - more helpful message?
1656
- return self.build(self.environment.concepts[base.address])
1691
+ return self._build_concept(self.environment.concepts[base.address])
1657
1692
 
1658
1693
  @build.register
1659
1694
  def _(self, base: CaseWhen) -> BuildCaseWhen:
1695
+ return self._build_case_when(base)
1660
1696
 
1697
+ def _build_case_when(self, base: CaseWhen) -> BuildCaseWhen:
1661
1698
  comparison = base.comparison
1662
1699
  expr: Concept | FuncArgs = base.expr
1663
1700
  validation = requires_concept_nesting(expr)
1664
1701
  if validation:
1665
1702
  expr, _ = self.instantiate_concept(validation)
1666
- return BuildCaseWhen.model_construct(
1703
+ return BuildCaseWhen(
1667
1704
  comparison=self.build(comparison),
1668
1705
  expr=self.build(expr),
1669
1706
  )
1670
1707
 
1671
1708
  @build.register
1672
1709
  def _(self, base: CaseElse) -> BuildCaseElse:
1710
+ return self._build_case_else(base)
1711
+
1712
+ def _build_case_else(self, base: CaseElse) -> BuildCaseElse:
1673
1713
  expr: Concept | FuncArgs = base.expr
1674
1714
  validation = requires_concept_nesting(expr)
1675
1715
  if validation:
1676
1716
  expr, _ = self.instantiate_concept(validation)
1677
- return BuildCaseElse.model_construct(expr=self.build(expr))
1717
+ return BuildCaseElse(expr=self.build(expr))
1678
1718
 
1679
1719
  @build.register
1680
1720
  def _(self, base: Concept) -> BuildConcept:
1721
+ return self._build_concept(base)
1681
1722
 
1723
+ def _build_concept(self, base: Concept) -> BuildConcept:
1682
1724
  # TODO: if we are using parameters, wrap it in a new model and use that in rendering
1683
1725
  if base.address in self.local_concepts:
1684
1726
  return self.local_concepts[base.address]
@@ -1707,13 +1749,13 @@ class Factory:
1707
1749
  for x in self.pseudonym_map.get(base.address, set())
1708
1750
  if x != base.address
1709
1751
  }
1710
- rval = BuildConcept.model_construct(
1752
+ rval = BuildConcept(
1711
1753
  name=base.name,
1712
1754
  datatype=base.datatype,
1713
1755
  purpose=base.purpose,
1714
1756
  metadata=base.metadata,
1715
1757
  lineage=build_lineage,
1716
- grain=self.build(final_grain),
1758
+ grain=self._build_grain(final_grain),
1717
1759
  namespace=base.namespace,
1718
1760
  keys=base.keys,
1719
1761
  modifiers=base.modifiers,
@@ -1723,34 +1765,44 @@ class Factory:
1723
1765
  granularity=granularity,
1724
1766
  build_is_aggregate=is_aggregate,
1725
1767
  )
1768
+ self.local_concepts[base.address] = rval
1726
1769
  return rval
1727
1770
 
1728
1771
  @build.register
1729
1772
  def _(self, base: AggregateWrapper) -> BuildAggregateWrapper:
1773
+ return self._build_aggregate_wrapper(base)
1774
+
1775
+ def _build_aggregate_wrapper(self, base: AggregateWrapper) -> BuildAggregateWrapper:
1730
1776
  if not base.by:
1731
1777
  by = [
1732
- self.build(self.environment.concepts[c]) for c in self.grain.components
1778
+ self._build_concept(self.environment.concepts[c])
1779
+ for c in self.grain.components
1733
1780
  ]
1734
1781
  else:
1735
1782
  by = [self.build(x) for x in base.by]
1736
1783
 
1737
- parent = self.build(base.function)
1738
- return BuildAggregateWrapper.model_construct(function=parent, by=by)
1784
+ parent: BuildFunction = self._build_function(base.function) # type: ignore
1785
+ return BuildAggregateWrapper(function=parent, by=by)
1739
1786
 
1740
1787
  @build.register
1741
1788
  def _(self, base: ColumnAssignment) -> BuildColumnAssignment:
1789
+ return self._build_column_assignment(base)
1790
+
1791
+ def _build_column_assignment(self, base: ColumnAssignment) -> BuildColumnAssignment:
1742
1792
  address = base.concept.address
1743
1793
  fetched = (
1744
- self.build(
1794
+ self._build_concept(
1745
1795
  self.environment.alias_origin_lookup[address].with_grain(self.grain)
1746
1796
  )
1747
1797
  if address in self.environment.alias_origin_lookup
1748
- else self.build(self.environment.concepts[address].with_grain(self.grain))
1798
+ else self._build_concept(
1799
+ self.environment.concepts[address].with_grain(self.grain)
1800
+ )
1749
1801
  )
1750
1802
 
1751
- return BuildColumnAssignment.model_construct(
1803
+ return BuildColumnAssignment(
1752
1804
  alias=(
1753
- self.build(base.alias)
1805
+ self._build_function(base.alias)
1754
1806
  if isinstance(base.alias, Function)
1755
1807
  else base.alias
1756
1808
  ),
@@ -1760,44 +1812,55 @@ class Factory:
1760
1812
 
1761
1813
  @build.register
1762
1814
  def _(self, base: OrderBy) -> BuildOrderBy:
1763
- return BuildOrderBy.model_construct(items=[self.build(x) for x in base.items])
1815
+ return self._build_order_by(base)
1816
+
1817
+ def _build_order_by(self, base: OrderBy) -> BuildOrderBy:
1818
+ return BuildOrderBy(items=[self._build_order_item(x) for x in base.items])
1764
1819
 
1765
1820
  @build.register
1766
1821
  def _(self, base: FunctionCallWrapper) -> BuildExpr:
1822
+ return self._build_function_call_wrapper(base)
1823
+
1824
+ def _build_function_call_wrapper(self, base: FunctionCallWrapper) -> BuildExpr:
1767
1825
  # function calls are kept around purely for the parse tree
1768
1826
  # so discard at the build point
1769
1827
  return self.build(base.content)
1770
1828
 
1771
1829
  @build.register
1772
1830
  def _(self, base: OrderItem) -> BuildOrderItem:
1831
+ return self._build_order_item(base)
1773
1832
 
1833
+ def _build_order_item(self, base: OrderItem) -> BuildOrderItem:
1774
1834
  bexpr: Any
1775
1835
  validation = requires_concept_nesting(base.expr)
1776
1836
  if validation:
1777
1837
  bexpr, _ = self.instantiate_concept(validation)
1778
1838
  else:
1779
1839
  bexpr = base.expr
1780
- return BuildOrderItem.model_construct(
1840
+ return BuildOrderItem(
1781
1841
  expr=(self.build(bexpr)),
1782
1842
  order=base.order,
1783
1843
  )
1784
1844
 
1785
1845
  @build.register
1786
1846
  def _(self, base: WhereClause) -> BuildWhereClause:
1847
+ return self._build_where_clause(base)
1787
1848
 
1788
- return BuildWhereClause.model_construct(
1789
- conditional=self.build(base.conditional)
1790
- )
1849
+ def _build_where_clause(self, base: WhereClause) -> BuildWhereClause:
1850
+ return BuildWhereClause(conditional=self.build(base.conditional))
1791
1851
 
1792
1852
  @build.register
1793
1853
  def _(self, base: HavingClause) -> BuildHavingClause:
1794
- return BuildHavingClause.model_construct(
1795
- conditional=self.build(base.conditional)
1796
- )
1854
+ return self._build_having_clause(base)
1855
+
1856
+ def _build_having_clause(self, base: HavingClause) -> BuildHavingClause:
1857
+ return BuildHavingClause(conditional=self.build(base.conditional))
1797
1858
 
1798
1859
  @build.register
1799
1860
  def _(self, base: WindowItem) -> BuildWindowItem:
1861
+ return self._build_window_item(base)
1800
1862
 
1863
+ def _build_window_item(self, base: WindowItem) -> BuildWindowItem:
1801
1864
  content: Concept | FuncArgs = base.content
1802
1865
  validation = requires_concept_nesting(base.content)
1803
1866
  if validation:
@@ -1811,17 +1874,20 @@ class Factory:
1811
1874
  ):
1812
1875
  x.expr.by = [content]
1813
1876
  final_by.append(x)
1814
- return BuildWindowItem.model_construct(
1877
+ return BuildWindowItem(
1815
1878
  type=base.type,
1816
1879
  content=self.build(content),
1817
1880
  order_by=[self.build(x) for x in final_by],
1818
- over=[self.build(x) for x in base.over],
1881
+ over=[self._build_concept_ref(x) for x in base.over],
1819
1882
  index=base.index,
1820
1883
  )
1821
1884
 
1822
1885
  @build.register
1823
1886
  def _(self, base: Conditional) -> BuildConditional:
1824
- return BuildConditional.model_construct(
1887
+ return self._build_conditional(base)
1888
+
1889
+ def _build_conditional(self, base: Conditional) -> BuildConditional:
1890
+ return BuildConditional(
1825
1891
  left=self.handle_constant(self.build(base.left)),
1826
1892
  right=self.handle_constant(self.build(base.right)),
1827
1893
  operator=base.operator,
@@ -1829,12 +1895,17 @@ class Factory:
1829
1895
 
1830
1896
  @build.register
1831
1897
  def _(self, base: SubselectComparison) -> BuildSubselectComparison:
1898
+ return self._build_subselect_comparison(base)
1899
+
1900
+ def _build_subselect_comparison(
1901
+ self, base: SubselectComparison
1902
+ ) -> BuildSubselectComparison:
1832
1903
  right: Any = base.right
1833
1904
  # this has specialized logic - include all Functions
1834
1905
  if isinstance(base.right, (AggregateWrapper, WindowItem, FilterItem, Function)):
1835
1906
  right_c, _ = self.instantiate_concept(base.right)
1836
1907
  right = right_c
1837
- return BuildSubselectComparison.model_construct(
1908
+ return BuildSubselectComparison(
1838
1909
  left=self.handle_constant(self.build(base.left)),
1839
1910
  right=self.handle_constant(self.build(right)),
1840
1911
  operator=base.operator,
@@ -1842,7 +1913,9 @@ class Factory:
1842
1913
 
1843
1914
  @build.register
1844
1915
  def _(self, base: Comparison) -> BuildComparison:
1916
+ return self._build_comparison(base)
1845
1917
 
1918
+ def _build_comparison(self, base: Comparison) -> BuildComparison:
1846
1919
  left = base.left
1847
1920
  validation = requires_concept_nesting(base.left)
1848
1921
  if validation:
@@ -1853,7 +1926,7 @@ class Factory:
1853
1926
  if validation:
1854
1927
  right_c, _ = self.instantiate_concept(validation)
1855
1928
  right = right_c # type: ignore
1856
- return BuildComparison.model_construct(
1929
+ return BuildComparison(
1857
1930
  left=self.handle_constant(self.build(left)),
1858
1931
  right=self.handle_constant(self.build(right)),
1859
1932
  operator=base.operator,
@@ -1861,32 +1934,43 @@ class Factory:
1861
1934
 
1862
1935
  @build.register
1863
1936
  def _(self, base: AlignItem) -> BuildAlignItem:
1864
- return BuildAlignItem.model_construct(
1937
+ return self._build_align_item(base)
1938
+
1939
+ def _build_align_item(self, base: AlignItem) -> BuildAlignItem:
1940
+ return BuildAlignItem(
1865
1941
  alias=base.alias,
1866
- concepts=[self.build(x) for x in base.concepts],
1942
+ concepts=[self._build_concept_ref(x) for x in base.concepts],
1867
1943
  namespace=base.namespace,
1868
1944
  )
1869
1945
 
1870
1946
  @build.register
1871
1947
  def _(self, base: AlignClause) -> BuildAlignClause:
1872
- return BuildAlignClause.model_construct(
1873
- items=[self.build(x) for x in base.items]
1874
- )
1948
+ return self._build_align_clause(base)
1949
+
1950
+ def _build_align_clause(self, base: AlignClause) -> BuildAlignClause:
1951
+ return BuildAlignClause(items=[self._build_align_item(x) for x in base.items])
1875
1952
 
1876
1953
  @build.register
1877
1954
  def _(self, base: RowsetItem) -> BuildRowsetItem:
1955
+ return self._build_rowset_item(base)
1878
1956
 
1957
+ def _build_rowset_item(self, base: RowsetItem) -> BuildRowsetItem:
1879
1958
  factory = Factory(
1880
1959
  environment=self.environment,
1881
1960
  local_concepts={},
1882
1961
  grain=base.rowset.select.grain,
1962
+ pseudonym_map=self.pseudonym_map,
1883
1963
  )
1884
1964
  return BuildRowsetItem(
1885
- content=factory.build(base.content), rowset=factory.build(base.rowset)
1965
+ content=factory._build_concept_ref(base.content),
1966
+ rowset=factory._build_rowset_lineage(base.rowset),
1886
1967
  )
1887
1968
 
1888
1969
  @build.register
1889
1970
  def _(self, base: RowsetLineage) -> BuildRowsetLineage:
1971
+ return self._build_rowset_lineage(base)
1972
+
1973
+ def _build_rowset_lineage(self, base: RowsetLineage) -> BuildRowsetLineage:
1890
1974
  out = BuildRowsetLineage(
1891
1975
  name=base.name,
1892
1976
  derived_concepts=[x.address for x in base.derived_concepts],
@@ -1896,39 +1980,51 @@ class Factory:
1896
1980
 
1897
1981
  @build.register
1898
1982
  def _(self, base: Grain) -> BuildGrain:
1983
+ return self._build_grain(base)
1984
+
1985
+ def _build_grain(self, base: Grain) -> BuildGrain:
1899
1986
  if base.where_clause:
1900
- factory = Factory(environment=self.environment)
1901
- where = factory.build(base.where_clause)
1987
+ factory = Factory(
1988
+ environment=self.environment, pseudonym_map=self.pseudonym_map
1989
+ )
1990
+ where = factory._build_where_clause(base.where_clause)
1902
1991
  else:
1903
1992
  where = None
1904
- return BuildGrain.model_construct(
1905
- components=base.components, where_clause=where
1906
- )
1993
+ return BuildGrain(components=base.components, where_clause=where)
1907
1994
 
1908
1995
  @build.register
1909
1996
  def _(self, base: TupleWrapper) -> TupleWrapper:
1997
+ return self._build_tuple_wrapper(base)
1998
+
1999
+ def _build_tuple_wrapper(self, base: TupleWrapper) -> TupleWrapper:
1910
2000
  return TupleWrapper(val=[self.build(x) for x in base.val], type=base.type)
1911
2001
 
1912
2002
  @build.register
1913
2003
  def _(self, base: FilterItem) -> BuildFilterItem:
2004
+ return self._build_filter_item(base)
2005
+
2006
+ def _build_filter_item(self, base: FilterItem) -> BuildFilterItem:
1914
2007
  if isinstance(
1915
2008
  base.content, (Function, AggregateWrapper, WindowItem, FilterItem)
1916
2009
  ):
1917
2010
  _, built = self.instantiate_concept(base.content)
1918
- return BuildFilterItem.model_construct(
1919
- content=built, where=self.build(base.where)
1920
- )
1921
- return BuildFilterItem.model_construct(
2011
+ return BuildFilterItem(content=built, where=self.build(base.where))
2012
+ return BuildFilterItem(
1922
2013
  content=self.build(base.content), where=self.build(base.where)
1923
2014
  )
1924
2015
 
1925
2016
  @build.register
1926
2017
  def _(self, base: Parenthetical) -> BuildParenthetical:
1927
- return BuildParenthetical.model_construct(content=(self.build(base.content)))
2018
+ return self._build_parenthetical(base)
2019
+
2020
+ def _build_parenthetical(self, base: Parenthetical) -> BuildParenthetical:
2021
+ return BuildParenthetical(content=(self.build(base.content)))
1928
2022
 
1929
2023
  @build.register
1930
2024
  def _(self, base: SelectLineage) -> BuildSelectLineage:
2025
+ return self._build_select_lineage(base)
1931
2026
 
2027
+ def _build_select_lineage(self, base: SelectLineage) -> BuildSelectLineage:
1932
2028
  from trilogy.core.models.build import (
1933
2029
  BuildSelectLineage,
1934
2030
  Factory,
@@ -1936,12 +2032,18 @@ class Factory:
1936
2032
 
1937
2033
  materialized: dict[str, BuildConcept] = {}
1938
2034
  factory = Factory(
1939
- grain=base.grain, environment=self.environment, local_concepts=materialized
2035
+ grain=base.grain,
2036
+ environment=self.environment,
2037
+ local_concepts=materialized,
2038
+ pseudonym_map=self.pseudonym_map,
1940
2039
  )
1941
2040
  for k, v in base.local_concepts.items():
1942
2041
  materialized[k] = factory.build(v)
1943
2042
  where_factory = Factory(
1944
- grain=Grain(), environment=self.environment, local_concepts={}
2043
+ grain=Grain(),
2044
+ environment=self.environment,
2045
+ local_concepts={},
2046
+ pseudonym_map=self.pseudonym_map,
1945
2047
  )
1946
2048
  where_clause = (
1947
2049
  where_factory.build(base.where_clause) if base.where_clause else None
@@ -1985,7 +2087,11 @@ class Factory:
1985
2087
 
1986
2088
  @build.register
1987
2089
  def _(self, base: MultiSelectLineage) -> BuildMultiSelectLineage:
2090
+ return self._build_multi_select_lineage(base)
1988
2091
 
2092
+ def _build_multi_select_lineage(
2093
+ self, base: MultiSelectLineage
2094
+ ) -> BuildMultiSelectLineage:
1989
2095
  local_build_cache: dict[str, BuildConcept] = {}
1990
2096
 
1991
2097
  parents: list[BuildSelectLineage] = [self.build(x) for x in base.selects]
@@ -2000,7 +2106,7 @@ class Factory:
2000
2106
  derived_base = []
2001
2107
  for k in base.derived_concepts:
2002
2108
  base_concept = self.environment.concepts[k]
2003
- x = BuildConcept.model_construct(
2109
+ x = BuildConcept(
2004
2110
  name=base_concept.name,
2005
2111
  datatype=base_concept.datatype,
2006
2112
  purpose=base_concept.purpose,
@@ -2023,9 +2129,12 @@ class Factory:
2023
2129
  grain=base.grain,
2024
2130
  environment=self.environment,
2025
2131
  local_concepts=local_build_cache,
2132
+ pseudonym_map=self.pseudonym_map,
2026
2133
  )
2027
- where_factory = Factory(environment=self.environment)
2028
- lineage = BuildMultiSelectLineage.model_construct(
2134
+ where_factory = Factory(
2135
+ environment=self.environment, pseudonym_map=self.pseudonym_map
2136
+ )
2137
+ lineage = BuildMultiSelectLineage(
2029
2138
  # we don't build selects here; they'll be built automatically in query discovery
2030
2139
  selects=base.selects,
2031
2140
  grain=final_grain,
@@ -2053,6 +2162,9 @@ class Factory:
2053
2162
 
2054
2163
  @build.register
2055
2164
  def _(self, base: Environment):
2165
+ return self._build_environment(base)
2166
+
2167
+ def _build_environment(self, base: Environment):
2056
2168
  from trilogy.core.models.build_environment import BuildEnvironment
2057
2169
 
2058
2170
  new = BuildEnvironment(
@@ -2061,14 +2173,14 @@ class Factory:
2061
2173
  )
2062
2174
 
2063
2175
  for k, v in base.concepts.items():
2064
- new.concepts[k] = self.build(v)
2176
+ new.concepts[k] = self._build_concept(v)
2065
2177
  for (
2066
2178
  k,
2067
2179
  d,
2068
2180
  ) in base.datasources.items():
2069
- new.datasources[k] = self.build(d)
2181
+ new.datasources[k] = self._build_datasource(d)
2070
2182
  for k, a in base.alias_origin_lookup.items():
2071
- new.alias_origin_lookup[k] = self.build(a)
2183
+ new.alias_origin_lookup[k] = self._build_concept(a)
2072
2184
  # add in anything that was built as a side-effect
2073
2185
  for bk, bv in self.local_concepts.items():
2074
2186
  if bk not in new.concepts:
@@ -2078,39 +2190,63 @@ class Factory:
2078
2190
 
2079
2191
  @build.register
2080
2192
  def _(self, base: TraitDataType):
2193
+ return self._build_trait_data_type(base)
2194
+
2195
+ def _build_trait_data_type(self, base: TraitDataType):
2081
2196
  return base
2082
2197
 
2083
2198
  @build.register
2084
2199
  def _(self, base: ArrayType):
2200
+ return self._build_array_type(base)
2201
+
2202
+ def _build_array_type(self, base: ArrayType):
2085
2203
  return base
2086
2204
 
2087
2205
  @build.register
2088
2206
  def _(self, base: StructType):
2207
+ return self._build_struct_type(base)
2208
+
2209
+ def _build_struct_type(self, base: StructType):
2089
2210
  return base
2090
2211
 
2091
2212
  @build.register
2092
2213
  def _(self, base: MapType):
2214
+ return self._build_map_type(base)
2215
+
2216
+ def _build_map_type(self, base: MapType):
2093
2217
  return base
2094
2218
 
2095
2219
  @build.register
2096
2220
  def _(self, base: ArgBinding):
2221
+ return self._build_arg_binding(base)
2222
+
2223
+ def _build_arg_binding(self, base: ArgBinding):
2097
2224
  return base
2098
2225
 
2099
2226
  @build.register
2100
2227
  def _(self, base: Ordering):
2228
+ return self._build_ordering(base)
2229
+
2230
+ def _build_ordering(self, base: Ordering):
2101
2231
  return base
2102
2232
 
2103
2233
  @build.register
2104
2234
  def _(self, base: Datasource):
2235
+ return self._build_datasource(base)
2236
+
2237
+ def _build_datasource(self, base: Datasource):
2105
2238
  local_cache: dict[str, BuildConcept] = {}
2106
2239
  factory = Factory(
2107
- grain=base.grain, environment=self.environment, local_concepts=local_cache
2240
+ grain=base.grain,
2241
+ environment=self.environment,
2242
+ local_concepts=local_cache,
2243
+ pseudonym_map=self.pseudonym_map,
2108
2244
  )
2109
- return BuildDatasource.model_construct(
2245
+ return BuildDatasource(
2110
2246
  name=base.name,
2111
- columns=[factory.build(c) for c in base.columns],
2247
+ columns=[factory._build_column_assignment(c) for c in base.columns],
2112
2248
  address=base.address,
2113
- grain=factory.build(base.grain),
2249
+ grain=factory._build_grain(base.grain),
2114
2250
  namespace=base.namespace,
2115
2251
  metadata=base.metadata,
2116
2252
  where=(factory.build(base.where) if base.where else None),