pytrilogy 0.0.2.50__py3-none-any.whl → 0.0.2.52__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pytrilogy might be problematic. Click here for more details.
- {pytrilogy-0.0.2.50.dist-info → pytrilogy-0.0.2.52.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.2.50.dist-info → pytrilogy-0.0.2.52.dist-info}/RECORD +27 -25
- trilogy/__init__.py +1 -1
- trilogy/core/internal.py +5 -1
- trilogy/core/models.py +124 -263
- trilogy/core/processing/concept_strategies_v3.py +14 -4
- trilogy/core/processing/node_generators/basic_node.py +7 -3
- trilogy/core/processing/node_generators/common.py +8 -3
- trilogy/core/processing/node_generators/filter_node.py +5 -5
- trilogy/core/processing/node_generators/group_node.py +24 -8
- trilogy/core/processing/node_generators/multiselect_node.py +4 -3
- trilogy/core/processing/node_generators/node_merge_node.py +14 -2
- trilogy/core/processing/node_generators/rowset_node.py +3 -4
- trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +203 -0
- trilogy/core/processing/node_generators/select_merge_node.py +17 -9
- trilogy/core/processing/nodes/base_node.py +2 -33
- trilogy/core/processing/nodes/group_node.py +19 -10
- trilogy/core/processing/nodes/merge_node.py +2 -2
- trilogy/hooks/graph_hook.py +3 -1
- trilogy/parsing/common.py +54 -12
- trilogy/parsing/parse_engine.py +39 -20
- trilogy/parsing/render.py +17 -1
- {pytrilogy-0.0.2.50.dist-info → pytrilogy-0.0.2.52.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.50.dist-info → pytrilogy-0.0.2.52.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.50.dist-info → pytrilogy-0.0.2.52.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.50.dist-info → pytrilogy-0.0.2.52.dist-info}/top_level.txt +0 -0
trilogy/core/models.py
CHANGED
|
@@ -36,7 +36,6 @@ from pydantic import (
|
|
|
36
36
|
ConfigDict,
|
|
37
37
|
Field,
|
|
38
38
|
ValidationInfo,
|
|
39
|
-
ValidatorFunctionWrapHandler,
|
|
40
39
|
computed_field,
|
|
41
40
|
field_validator,
|
|
42
41
|
)
|
|
@@ -409,31 +408,6 @@ class Metadata(BaseModel):
|
|
|
409
408
|
concept_source: ConceptSource = ConceptSource.MANUAL
|
|
410
409
|
|
|
411
410
|
|
|
412
|
-
def lineage_validator(
|
|
413
|
-
v: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
|
|
414
|
-
) -> Union[Function, WindowItem, FilterItem, AggregateWrapper]:
|
|
415
|
-
if v and not isinstance(v, (Function, WindowItem, FilterItem, AggregateWrapper)):
|
|
416
|
-
raise ValueError(v)
|
|
417
|
-
return v
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
def empty_grain() -> Grain:
|
|
421
|
-
return Grain(components=[])
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
class MultiLineage(BaseModel):
|
|
425
|
-
lineages: list[
|
|
426
|
-
Union[
|
|
427
|
-
Function,
|
|
428
|
-
WindowItem,
|
|
429
|
-
FilterItem,
|
|
430
|
-
AggregateWrapper,
|
|
431
|
-
RowsetItem,
|
|
432
|
-
MultiSelectStatement,
|
|
433
|
-
]
|
|
434
|
-
]
|
|
435
|
-
|
|
436
|
-
|
|
437
411
|
class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
438
412
|
name: str
|
|
439
413
|
datatype: DataType | ListType | StructType | MapType | NumericType
|
|
@@ -550,28 +524,22 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
550
524
|
values = info.data
|
|
551
525
|
if not v and values.get("purpose", None) == Purpose.KEY:
|
|
552
526
|
v = Grain(
|
|
553
|
-
components=
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
name=values["name"],
|
|
557
|
-
datatype=values["datatype"],
|
|
558
|
-
purpose=values["purpose"],
|
|
559
|
-
grain=Grain(),
|
|
560
|
-
)
|
|
561
|
-
]
|
|
527
|
+
components={
|
|
528
|
+
f'{values.get("namespace", DEFAULT_NAMESPACE)}.{values["name"]}'
|
|
529
|
+
}
|
|
562
530
|
)
|
|
563
531
|
elif (
|
|
564
532
|
"lineage" in values
|
|
565
533
|
and isinstance(values["lineage"], AggregateWrapper)
|
|
566
534
|
and values["lineage"].by
|
|
567
535
|
):
|
|
568
|
-
v = Grain(components=values["lineage"].by)
|
|
536
|
+
v = Grain(components={c.address for c in values["lineage"].by})
|
|
569
537
|
elif not v:
|
|
570
|
-
v = Grain(components=
|
|
538
|
+
v = Grain(components=set())
|
|
571
539
|
elif isinstance(v, Grain):
|
|
572
|
-
|
|
540
|
+
pass
|
|
573
541
|
elif isinstance(v, Concept):
|
|
574
|
-
v = Grain(components=
|
|
542
|
+
v = Grain(components={v.address})
|
|
575
543
|
elif isinstance(v, dict):
|
|
576
544
|
v = Grain.model_validate(v)
|
|
577
545
|
else:
|
|
@@ -594,8 +562,8 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
594
562
|
)
|
|
595
563
|
|
|
596
564
|
def __str__(self):
|
|
597
|
-
grain =
|
|
598
|
-
return f"{self.namespace}.{self.name}
|
|
565
|
+
grain = str(self.grain) if self.grain else "Grain<>"
|
|
566
|
+
return f"{self.namespace}.{self.name}@{grain}"
|
|
599
567
|
|
|
600
568
|
@cached_property
|
|
601
569
|
def address(self) -> str:
|
|
@@ -620,10 +588,6 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
620
588
|
return f"{self.namespace.replace('.','_')}_{self.name.replace('.','_')}"
|
|
621
589
|
return self.name.replace(".", "_")
|
|
622
590
|
|
|
623
|
-
@property
|
|
624
|
-
def grain_components(self) -> List["Concept"]:
|
|
625
|
-
return self.grain.components_copy if self.grain else []
|
|
626
|
-
|
|
627
591
|
def with_namespace(self, namespace: str) -> Self:
|
|
628
592
|
if namespace == self.namespace:
|
|
629
593
|
return self
|
|
@@ -636,7 +600,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
636
600
|
grain=(
|
|
637
601
|
self.grain.with_namespace(namespace)
|
|
638
602
|
if self.grain
|
|
639
|
-
else Grain(components=
|
|
603
|
+
else Grain(components=set())
|
|
640
604
|
),
|
|
641
605
|
namespace=(
|
|
642
606
|
namespace + "." + self.namespace
|
|
@@ -662,7 +626,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
662
626
|
new_lineage = self.lineage.with_select_context(
|
|
663
627
|
local_concepts=local_concepts, grain=grain, environment=environment
|
|
664
628
|
)
|
|
665
|
-
final_grain = self.grain
|
|
629
|
+
final_grain = self.grain or grain
|
|
666
630
|
keys = (
|
|
667
631
|
tuple(
|
|
668
632
|
[
|
|
@@ -674,9 +638,10 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
674
638
|
else None
|
|
675
639
|
)
|
|
676
640
|
if self.is_aggregate and isinstance(new_lineage, Function):
|
|
677
|
-
|
|
641
|
+
grain_components = [environment.concepts[c] for c in grain.components]
|
|
642
|
+
new_lineage = AggregateWrapper(function=new_lineage, by=grain_components)
|
|
678
643
|
final_grain = grain
|
|
679
|
-
keys = tuple(
|
|
644
|
+
keys = tuple(grain_components)
|
|
680
645
|
elif (
|
|
681
646
|
self.is_aggregate and not keys and isinstance(new_lineage, AggregateWrapper)
|
|
682
647
|
):
|
|
@@ -697,15 +662,13 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
697
662
|
)
|
|
698
663
|
|
|
699
664
|
def with_grain(self, grain: Optional["Grain"] = None) -> Self:
|
|
700
|
-
if not all([isinstance(x, Concept) for x in self.keys or []]):
|
|
701
|
-
raise ValueError(f"Invalid keys {self.keys} for concept {self.address}")
|
|
702
665
|
return self.__class__(
|
|
703
666
|
name=self.name,
|
|
704
667
|
datatype=self.datatype,
|
|
705
668
|
purpose=self.purpose,
|
|
706
669
|
metadata=self.metadata,
|
|
707
670
|
lineage=self.lineage,
|
|
708
|
-
grain=grain if grain else Grain(components=
|
|
671
|
+
grain=grain if grain else Grain(components=set()),
|
|
709
672
|
namespace=self.namespace,
|
|
710
673
|
keys=self.keys,
|
|
711
674
|
modifiers=self.modifiers,
|
|
@@ -716,7 +679,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
716
679
|
def _with_default_grain(self) -> Self:
|
|
717
680
|
if self.purpose == Purpose.KEY:
|
|
718
681
|
# we need to make this abstract
|
|
719
|
-
grain = Grain(components=
|
|
682
|
+
grain = Grain(components={self.address})
|
|
720
683
|
elif self.purpose == Purpose.PROPERTY:
|
|
721
684
|
components = []
|
|
722
685
|
if self.keys:
|
|
@@ -728,12 +691,15 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
728
691
|
components += item.sources
|
|
729
692
|
else:
|
|
730
693
|
components += item.sources
|
|
731
|
-
|
|
694
|
+
# TODO: set synonyms
|
|
695
|
+
grain = Grain(
|
|
696
|
+
components=set([x.address for x in components]),
|
|
697
|
+
) # synonym_set=generate_concept_synonyms(components))
|
|
732
698
|
elif self.purpose == Purpose.METRIC:
|
|
733
699
|
grain = Grain()
|
|
734
700
|
elif self.purpose == Purpose.CONSTANT:
|
|
735
701
|
if self.derivation != PurposeLineage.CONSTANT:
|
|
736
|
-
grain = Grain(components=
|
|
702
|
+
grain = Grain(components={self.address})
|
|
737
703
|
else:
|
|
738
704
|
grain = self.grain
|
|
739
705
|
else:
|
|
@@ -855,7 +821,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
855
821
|
elif self.derivation == PurposeLineage.AGGREGATE:
|
|
856
822
|
# if it's an aggregate grouped over all rows
|
|
857
823
|
# there is only one row left and it's fine to cross_join
|
|
858
|
-
if all([x.
|
|
824
|
+
if all([x.endswith(ALL_ROWS_CONCEPT) for x in self.grain.components]):
|
|
859
825
|
return Granularity.SINGLE_ROW
|
|
860
826
|
elif self.namespace == INTERNAL_NAMESPACE and self.name == ALL_ROWS_CONCEPT:
|
|
861
827
|
return Granularity.SINGLE_ROW
|
|
@@ -893,7 +859,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
893
859
|
metadata=self.metadata,
|
|
894
860
|
lineage=FilterItem(content=self, where=WhereClause(conditional=condition)),
|
|
895
861
|
keys=(self.keys if self.purpose == Purpose.PROPERTY else None),
|
|
896
|
-
grain=self.grain if self.grain else Grain(components=
|
|
862
|
+
grain=self.grain if self.grain else Grain(components=set()),
|
|
897
863
|
namespace=self.namespace,
|
|
898
864
|
modifiers=self.modifiers,
|
|
899
865
|
pseudonyms=self.pseudonyms,
|
|
@@ -911,161 +877,123 @@ class ConceptRef(BaseModel):
|
|
|
911
877
|
return environment.concepts.__getitem__(self.address, self.line_no)
|
|
912
878
|
|
|
913
879
|
|
|
914
|
-
class Grain(
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
where_clause: Optional[WhereClause] = Field(default=None)
|
|
880
|
+
class Grain(Namespaced, BaseModel):
|
|
881
|
+
components: set[str] = Field(default_factory=set)
|
|
882
|
+
where_clause: Optional["WhereClause"] = None
|
|
918
883
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
v2 = unique(v, "address")
|
|
928
|
-
final: List[Concept] = []
|
|
929
|
-
for sub in v2:
|
|
930
|
-
if sub.purpose in (Purpose.PROPERTY, Purpose.METRIC) and sub.keys:
|
|
931
|
-
if all([c in v2 for c in sub.keys]):
|
|
932
|
-
continue
|
|
933
|
-
final.append(sub)
|
|
934
|
-
v2 = sorted(final, key=lambda x: x.name)
|
|
935
|
-
return v2
|
|
936
|
-
|
|
937
|
-
def with_select_context(
|
|
938
|
-
self, local_concepts: dict[str, Concept], grain: Grain, environment: Environment
|
|
939
|
-
):
|
|
940
|
-
if self.nested:
|
|
941
|
-
return self
|
|
942
|
-
return Grain(
|
|
943
|
-
components=[
|
|
944
|
-
x.with_select_context(local_concepts, grain, environment)
|
|
945
|
-
for x in self.components
|
|
946
|
-
],
|
|
947
|
-
where_clause=self.where_clause,
|
|
948
|
-
nested=self.nested,
|
|
949
|
-
)
|
|
884
|
+
def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
|
|
885
|
+
new_components = set()
|
|
886
|
+
for c in self.components:
|
|
887
|
+
if c == source.address:
|
|
888
|
+
new_components.add(target.address)
|
|
889
|
+
else:
|
|
890
|
+
new_components.add(c)
|
|
891
|
+
return Grain(components=new_components)
|
|
950
892
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
893
|
+
@classmethod
|
|
894
|
+
def from_concepts(
|
|
895
|
+
cls,
|
|
896
|
+
concepts: List[Concept],
|
|
954
897
|
environment: Environment | None = None,
|
|
898
|
+
where_clause: WhereClause | None = None,
|
|
955
899
|
) -> "Grain":
|
|
900
|
+
from trilogy.parsing.common import concepts_to_grain_concepts
|
|
901
|
+
|
|
956
902
|
return Grain(
|
|
957
|
-
components=
|
|
958
|
-
|
|
903
|
+
components={
|
|
904
|
+
c.address
|
|
905
|
+
for c in concepts_to_grain_concepts(concepts, environment=environment)
|
|
906
|
+
},
|
|
907
|
+
where_clause=where_clause,
|
|
959
908
|
)
|
|
960
909
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
910
|
+
def with_namespace(self, namespace: str) -> "Grain":
|
|
911
|
+
return Grain(
|
|
912
|
+
components={address_with_namespace(c, namespace) for c in self.components},
|
|
913
|
+
where_clause=(
|
|
914
|
+
self.where_clause.with_namespace(namespace)
|
|
915
|
+
if self.where_clause
|
|
916
|
+
else None
|
|
917
|
+
),
|
|
918
|
+
)
|
|
964
919
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
920
|
+
@field_validator("components", mode="before")
|
|
921
|
+
def component_validator(cls, v, info: ValidationInfo):
|
|
922
|
+
output = set()
|
|
923
|
+
if isinstance(v, list):
|
|
924
|
+
for vc in v:
|
|
925
|
+
if isinstance(vc, Concept):
|
|
926
|
+
output.add(vc.address)
|
|
927
|
+
elif isinstance(vc, ConceptRef):
|
|
928
|
+
output.add(vc.address)
|
|
929
|
+
else:
|
|
930
|
+
output.add(vc)
|
|
968
931
|
else:
|
|
969
|
-
|
|
970
|
-
if
|
|
971
|
-
|
|
972
|
-
|
|
932
|
+
output = v
|
|
933
|
+
if not isinstance(output, set):
|
|
934
|
+
raise ValueError(f"Invalid grain component {output}, is not set")
|
|
935
|
+
if not all(isinstance(x, str) for x in output):
|
|
936
|
+
raise ValueError(f"Invalid component {output}")
|
|
937
|
+
return output
|
|
973
938
|
|
|
974
|
-
def
|
|
939
|
+
def __add__(self, other: "Grain") -> "Grain":
|
|
940
|
+
where = self.where_clause
|
|
941
|
+
if other.where_clause:
|
|
942
|
+
if not self.where_clause:
|
|
943
|
+
where = other.where_clause
|
|
944
|
+
elif not other.where_clause == self.where_clause:
|
|
945
|
+
raise NotImplementedError(
|
|
946
|
+
f"Cannot merge grains with where clauses, self {self.where_clause} other {other.where_clause}"
|
|
947
|
+
)
|
|
975
948
|
return Grain(
|
|
976
|
-
components=
|
|
977
|
-
nested=self.nested,
|
|
949
|
+
components=self.components.union(other.components), where_clause=where
|
|
978
950
|
)
|
|
979
951
|
|
|
980
|
-
def
|
|
981
|
-
self, source: Concept, target: Concept, modifiers: List[Modifier]
|
|
982
|
-
) -> "Grain":
|
|
952
|
+
def __sub__(self, other: "Grain") -> "Grain":
|
|
983
953
|
return Grain(
|
|
984
|
-
components=
|
|
985
|
-
|
|
986
|
-
],
|
|
987
|
-
nested=self.nested,
|
|
954
|
+
components=self.components.difference(other.components),
|
|
955
|
+
where_clause=self.where_clause,
|
|
988
956
|
)
|
|
989
957
|
|
|
990
958
|
@property
|
|
991
959
|
def abstract(self):
|
|
992
960
|
return not self.components or all(
|
|
993
|
-
[c.
|
|
961
|
+
[c.endswith(ALL_ROWS_CONCEPT) for c in self.components]
|
|
994
962
|
)
|
|
995
963
|
|
|
996
|
-
@property
|
|
997
|
-
def synonym_set(self) -> set[str]:
|
|
998
|
-
base = []
|
|
999
|
-
for x in self.components_copy:
|
|
1000
|
-
if isinstance(x.lineage, RowsetItem):
|
|
1001
|
-
base.append(x.lineage.content.address)
|
|
1002
|
-
for c in x.lineage.content.pseudonyms:
|
|
1003
|
-
base.append(c)
|
|
1004
|
-
else:
|
|
1005
|
-
base.append(x.address)
|
|
1006
|
-
for c in x.pseudonyms:
|
|
1007
|
-
base.append(c)
|
|
1008
|
-
return set(base)
|
|
1009
|
-
|
|
1010
|
-
@property
|
|
1011
|
-
def set(self) -> set[str]:
|
|
1012
|
-
base = []
|
|
1013
|
-
for x in self.components_copy:
|
|
1014
|
-
if isinstance(x.lineage, RowsetItem):
|
|
1015
|
-
base.append(x.lineage.content.address)
|
|
1016
|
-
else:
|
|
1017
|
-
base.append(x.address)
|
|
1018
|
-
return set(base)
|
|
1019
|
-
|
|
1020
964
|
def __eq__(self, other: object):
|
|
1021
965
|
if isinstance(other, list):
|
|
1022
|
-
|
|
966
|
+
if not all([isinstance(c, Concept) for c in other]):
|
|
967
|
+
return False
|
|
968
|
+
return self.components == set([c.address for c in other])
|
|
1023
969
|
if not isinstance(other, Grain):
|
|
1024
970
|
return False
|
|
1025
|
-
if self.
|
|
1026
|
-
return True
|
|
1027
|
-
elif self.synonym_set == other.synonym_set:
|
|
971
|
+
if self.components == other.components:
|
|
1028
972
|
return True
|
|
1029
973
|
return False
|
|
1030
974
|
|
|
1031
975
|
def issubset(self, other: "Grain"):
|
|
1032
|
-
return self.
|
|
976
|
+
return self.components.issubset(other.components)
|
|
1033
977
|
|
|
1034
978
|
def union(self, other: "Grain"):
|
|
1035
|
-
addresses = self.
|
|
1036
|
-
|
|
1037
|
-
return Grain(
|
|
1038
|
-
components=[c for c in self.components if c.address in addresses]
|
|
1039
|
-
+ [c for c in other.components if c.address in addresses]
|
|
1040
|
-
)
|
|
979
|
+
addresses = self.components.union(other.components)
|
|
980
|
+
return Grain(components=addresses, where_clause=self.where_clause)
|
|
1041
981
|
|
|
1042
982
|
def isdisjoint(self, other: "Grain"):
|
|
1043
|
-
return self.
|
|
983
|
+
return self.components.isdisjoint(other.components)
|
|
1044
984
|
|
|
1045
985
|
def intersection(self, other: "Grain") -> "Grain":
|
|
1046
|
-
intersection = self.
|
|
1047
|
-
components
|
|
1048
|
-
return Grain(components=components)
|
|
986
|
+
intersection = self.components.intersection(other.components)
|
|
987
|
+
return Grain(components=intersection)
|
|
1049
988
|
|
|
1050
|
-
def
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
for c in components:
|
|
1059
|
-
if c.purpose == Purpose.PROPERTY and not any(
|
|
1060
|
-
[key in base_components for key in (c.keys or [])]
|
|
1061
|
-
):
|
|
1062
|
-
base_components.append(c)
|
|
1063
|
-
elif (
|
|
1064
|
-
c.purpose == Purpose.CONSTANT
|
|
1065
|
-
and not c.derivation == PurposeLineage.CONSTANT
|
|
1066
|
-
):
|
|
1067
|
-
base_components.append(c)
|
|
1068
|
-
return Grain(components=base_components)
|
|
989
|
+
def __str__(self):
|
|
990
|
+
if self.abstract:
|
|
991
|
+
base = "Grain<Abstract>"
|
|
992
|
+
else:
|
|
993
|
+
base = "Grain<" + ",".join([c for c in sorted(list(self.components))]) + ">"
|
|
994
|
+
if self.where_clause:
|
|
995
|
+
base += f"|{str(self.where_clause)}"
|
|
996
|
+
return base
|
|
1069
997
|
|
|
1070
998
|
def __radd__(self, other) -> "Grain":
|
|
1071
999
|
if other == 0:
|
|
@@ -1755,6 +1683,7 @@ class SelectStatement(HasUUID, Mergeable, Namespaced, SelectTypeMixin, BaseModel
|
|
|
1755
1683
|
local_concepts: Annotated[
|
|
1756
1684
|
EnvironmentConceptDict, PlainValidator(validate_concepts)
|
|
1757
1685
|
] = Field(default_factory=EnvironmentConceptDict)
|
|
1686
|
+
grain: Grain = Field(default_factory=Grain)
|
|
1758
1687
|
|
|
1759
1688
|
def validate_syntax(self, environment: Environment):
|
|
1760
1689
|
if self.where_clause:
|
|
@@ -1806,15 +1735,6 @@ class SelectStatement(HasUUID, Mergeable, Namespaced, SelectTypeMixin, BaseModel
|
|
|
1806
1735
|
|
|
1807
1736
|
return render_query(self)
|
|
1808
1737
|
|
|
1809
|
-
def __init__(self, *args, **kwargs) -> None:
|
|
1810
|
-
super().__init__(*args, **kwargs)
|
|
1811
|
-
for nitem in self.selection:
|
|
1812
|
-
if not isinstance(nitem.content, Concept):
|
|
1813
|
-
continue
|
|
1814
|
-
if nitem.content.grain == Grain():
|
|
1815
|
-
if nitem.content.derivation == PurposeLineage.AGGREGATE:
|
|
1816
|
-
nitem.content = nitem.content.with_grain(self.grain)
|
|
1817
|
-
|
|
1818
1738
|
@field_validator("selection", mode="before")
|
|
1819
1739
|
@classmethod
|
|
1820
1740
|
def selection_validation(cls, v):
|
|
@@ -1886,9 +1806,7 @@ class SelectStatement(HasUUID, Mergeable, Namespaced, SelectTypeMixin, BaseModel
|
|
|
1886
1806
|
|
|
1887
1807
|
@property
|
|
1888
1808
|
def all_components(self) -> List[Concept]:
|
|
1889
|
-
return
|
|
1890
|
-
self.input_components + self.output_components + self.grain.components_copy
|
|
1891
|
-
)
|
|
1809
|
+
return self.input_components + self.output_components
|
|
1892
1810
|
|
|
1893
1811
|
def to_datasource(
|
|
1894
1812
|
self,
|
|
@@ -1938,55 +1856,6 @@ class SelectStatement(HasUUID, Mergeable, Namespaced, SelectTypeMixin, BaseModel
|
|
|
1938
1856
|
column.concept = column.concept.with_grain(new_datasource.grain)
|
|
1939
1857
|
return new_datasource
|
|
1940
1858
|
|
|
1941
|
-
@property
|
|
1942
|
-
def grain(self) -> "Grain":
|
|
1943
|
-
output = []
|
|
1944
|
-
for item in self.output_components:
|
|
1945
|
-
if item.purpose == Purpose.KEY:
|
|
1946
|
-
output.append(item)
|
|
1947
|
-
# if self.where_clause:
|
|
1948
|
-
# for item in self.where_clause.concept_arguments:
|
|
1949
|
-
# if item.purpose == Purpose.KEY:
|
|
1950
|
-
# output.append(item)
|
|
1951
|
-
# elif item.purpose == Purpose.PROPERTY and item.grain:
|
|
1952
|
-
# output += item.grain.components
|
|
1953
|
-
# TODO: handle other grain cases
|
|
1954
|
-
# new if block by design
|
|
1955
|
-
# add back any purpose that is not at the grain
|
|
1956
|
-
# if a query already has the key of the property in the grain
|
|
1957
|
-
# we want to group to that grain and ignore the property, which is a derivation
|
|
1958
|
-
# otherwise, we need to include property as the group by
|
|
1959
|
-
for item in self.output_components:
|
|
1960
|
-
if (
|
|
1961
|
-
item.purpose == Purpose.PROPERTY
|
|
1962
|
-
and item.grain
|
|
1963
|
-
and (
|
|
1964
|
-
not item.grain.components
|
|
1965
|
-
or not item.grain.issubset(
|
|
1966
|
-
Grain(components=unique(output, "address"))
|
|
1967
|
-
)
|
|
1968
|
-
)
|
|
1969
|
-
):
|
|
1970
|
-
output.append(item)
|
|
1971
|
-
if (
|
|
1972
|
-
item.purpose == Purpose.CONSTANT
|
|
1973
|
-
and item.derivation != PurposeLineage.CONSTANT
|
|
1974
|
-
and item.grain
|
|
1975
|
-
and (
|
|
1976
|
-
not item.grain.components
|
|
1977
|
-
or not item.grain.issubset(
|
|
1978
|
-
Grain(components=unique(output, "address"))
|
|
1979
|
-
)
|
|
1980
|
-
)
|
|
1981
|
-
):
|
|
1982
|
-
output.append(item)
|
|
1983
|
-
# TODO: explore implicit filtering more
|
|
1984
|
-
# if self.where_clause.conditional and self.where_clause_category == SelectFiltering.IMPLICIT:
|
|
1985
|
-
# output =[x.with_filter(self.where_clause.conditional) for x in output]
|
|
1986
|
-
return Grain(
|
|
1987
|
-
components=unique(output, "address"), where_clause=self.where_clause
|
|
1988
|
-
)
|
|
1989
|
-
|
|
1990
1859
|
def with_namespace(self, namespace: str) -> "SelectStatement":
|
|
1991
1860
|
return SelectStatement(
|
|
1992
1861
|
selection=[c.with_namespace(namespace) for c in self.selection],
|
|
@@ -2203,7 +2072,7 @@ def safe_grain(v) -> Grain:
|
|
|
2203
2072
|
elif isinstance(v, Grain):
|
|
2204
2073
|
return v
|
|
2205
2074
|
elif not v:
|
|
2206
|
-
return Grain(components=
|
|
2075
|
+
return Grain(components=set())
|
|
2207
2076
|
else:
|
|
2208
2077
|
raise ValueError(f"Invalid input type to safe_grain {type(v)}")
|
|
2209
2078
|
|
|
@@ -2235,7 +2104,7 @@ class Datasource(HasUUID, Namespaced, BaseModel):
|
|
|
2235
2104
|
columns: List[ColumnAssignment]
|
|
2236
2105
|
address: Union[Address, str]
|
|
2237
2106
|
grain: Grain = Field(
|
|
2238
|
-
default_factory=lambda: Grain(components=
|
|
2107
|
+
default_factory=lambda: Grain(components=set()), validate_default=True
|
|
2239
2108
|
)
|
|
2240
2109
|
namespace: Optional[str] = Field(default=DEFAULT_NAMESPACE, validate_default=True)
|
|
2241
2110
|
metadata: DatasourceMetadata = Field(
|
|
@@ -2323,8 +2192,8 @@ class Datasource(HasUUID, Namespaced, BaseModel):
|
|
|
2323
2192
|
grain: Grain = safe_grain(v)
|
|
2324
2193
|
if not grain.components:
|
|
2325
2194
|
columns: List[ColumnAssignment] = values.get("columns", [])
|
|
2326
|
-
grain = Grain(
|
|
2327
|
-
|
|
2195
|
+
grain = Grain.from_concepts(
|
|
2196
|
+
[
|
|
2328
2197
|
c.concept.with_grain(Grain())
|
|
2329
2198
|
for c in columns
|
|
2330
2199
|
if c.concept.purpose == Purpose.KEY
|
|
@@ -2758,15 +2627,11 @@ class QueryDatasource(BaseModel):
|
|
|
2758
2627
|
@property
|
|
2759
2628
|
def identifier(self) -> str:
|
|
2760
2629
|
filters = abs(hash(str(self.condition))) if self.condition else ""
|
|
2761
|
-
grain = "_".join(
|
|
2762
|
-
[str(c.address).replace(".", "_") for c in self.grain.components]
|
|
2763
|
-
)
|
|
2764
|
-
# partial = "_".join([str(c.address).replace(".", "_") for c in self.partial_concepts])
|
|
2630
|
+
grain = "_".join([str(c).replace(".", "_") for c in self.grain.components])
|
|
2765
2631
|
return (
|
|
2766
2632
|
"_join_".join([d.identifier for d in self.datasources])
|
|
2767
2633
|
+ (f"_at_{grain}" if grain else "_at_abstract")
|
|
2768
2634
|
+ (f"_filtered_by_{filters}" if filters else "")
|
|
2769
|
-
# + (f"_partial_{partial}" if partial else "")
|
|
2770
2635
|
)
|
|
2771
2636
|
|
|
2772
2637
|
def get_alias(
|
|
@@ -3105,12 +2970,16 @@ class CTE(BaseModel):
|
|
|
3105
2970
|
for cte in self.parent_ctes:
|
|
3106
2971
|
if address in cte.output_columns:
|
|
3107
2972
|
match = [x for x in cte.output_columns if x.address == address].pop()
|
|
3108
|
-
|
|
2973
|
+
if match:
|
|
2974
|
+
return match
|
|
3109
2975
|
|
|
3110
2976
|
for array in [self.source.input_concepts, self.source.output_concepts]:
|
|
3111
2977
|
match_list = [x for x in array if x.address == address]
|
|
3112
2978
|
if match_list:
|
|
3113
2979
|
return match_list.pop()
|
|
2980
|
+
match_list = [x for x in self.output_columns if x.address == address]
|
|
2981
|
+
if match_list:
|
|
2982
|
+
return match_list.pop()
|
|
3114
2983
|
return None
|
|
3115
2984
|
|
|
3116
2985
|
def get_alias(self, concept: Concept, source: str | None = None) -> str:
|
|
@@ -3119,8 +2988,10 @@ class CTE(BaseModel):
|
|
|
3119
2988
|
if source and source != cte.name:
|
|
3120
2989
|
continue
|
|
3121
2990
|
return concept.safe_address
|
|
2991
|
+
|
|
3122
2992
|
try:
|
|
3123
2993
|
source = self.source.get_alias(concept, source=source)
|
|
2994
|
+
|
|
3124
2995
|
if not source:
|
|
3125
2996
|
raise ValueError("No source found")
|
|
3126
2997
|
return source
|
|
@@ -3133,7 +3004,8 @@ class CTE(BaseModel):
|
|
|
3133
3004
|
if len(self.source_map.get(c.address, [])) > 0:
|
|
3134
3005
|
return False
|
|
3135
3006
|
if c.derivation == PurposeLineage.ROWSET:
|
|
3136
|
-
|
|
3007
|
+
assert isinstance(c.lineage, RowsetItem)
|
|
3008
|
+
return check_is_not_in_group(c.lineage.content)
|
|
3137
3009
|
if c.derivation == PurposeLineage.CONSTANT:
|
|
3138
3010
|
return False
|
|
3139
3011
|
if c.purpose == Purpose.METRIC:
|
|
@@ -3330,7 +3202,6 @@ class UndefinedConcept(Concept, Mergeable, Namespaced):
|
|
|
3330
3202
|
if self.address in local_concepts:
|
|
3331
3203
|
rval = local_concepts[self.address]
|
|
3332
3204
|
rval = rval.with_select_context(local_concepts, grain, environment)
|
|
3333
|
-
|
|
3334
3205
|
return rval
|
|
3335
3206
|
environment.concepts.raise_undefined(self.address, line_no=self.line_no)
|
|
3336
3207
|
|
|
@@ -4462,7 +4333,7 @@ class AggregateWrapper(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
4462
4333
|
self, local_concepts: dict[str, Concept], grain: Grain, environment: Environment
|
|
4463
4334
|
) -> AggregateWrapper:
|
|
4464
4335
|
if not self.by:
|
|
4465
|
-
by = grain.
|
|
4336
|
+
by = [environment.concepts[c] for c in grain.components]
|
|
4466
4337
|
else:
|
|
4467
4338
|
by = [
|
|
4468
4339
|
x.with_select_context(local_concepts, grain, environment)
|
|
@@ -4511,16 +4382,6 @@ class WhereClause(Mergeable, ConceptArgs, Namespaced, SelectContext, BaseModel):
|
|
|
4511
4382
|
)
|
|
4512
4383
|
)
|
|
4513
4384
|
|
|
4514
|
-
@property
|
|
4515
|
-
def grain(self) -> Grain:
|
|
4516
|
-
output = []
|
|
4517
|
-
for item in self.input:
|
|
4518
|
-
if item.purpose == Purpose.KEY:
|
|
4519
|
-
output.append(item)
|
|
4520
|
-
elif item.purpose == Purpose.PROPERTY:
|
|
4521
|
-
output += item.grain.components if item.grain else []
|
|
4522
|
-
return Grain(components=list(set(output)))
|
|
4523
|
-
|
|
4524
4385
|
@property
|
|
4525
4386
|
def components(self):
|
|
4526
4387
|
from trilogy.core.processing.utility import decompose_condition
|
|
@@ -4656,7 +4517,7 @@ class RowsetDerivationStatement(HasUUID, Namespaced, BaseModel):
|
|
|
4656
4517
|
)
|
|
4657
4518
|
orig[orig_concept.address] = new_concept
|
|
4658
4519
|
output.append(new_concept)
|
|
4659
|
-
default_grain = Grain(
|
|
4520
|
+
default_grain = Grain.from_concepts([*output])
|
|
4660
4521
|
# remap everything to the properties of the rowset
|
|
4661
4522
|
for x in output:
|
|
4662
4523
|
if x.keys:
|
|
@@ -4668,9 +4529,9 @@ class RowsetDerivationStatement(HasUUID, Namespaced, BaseModel):
|
|
|
4668
4529
|
# TODO: fix this up
|
|
4669
4530
|
x.keys = tuple()
|
|
4670
4531
|
for x in output:
|
|
4671
|
-
if all([c
|
|
4532
|
+
if all([c in orig for c in x.grain.components]):
|
|
4672
4533
|
x.grain = Grain(
|
|
4673
|
-
components=
|
|
4534
|
+
components={orig[c].address for c in x.grain.components}
|
|
4674
4535
|
)
|
|
4675
4536
|
else:
|
|
4676
4537
|
x.grain = default_grain
|