From bee62791335599ab2306fb0e70b6955c129f5c64 Mon Sep 17 00:00:00 2001 From: MariusWirtz Date: Mon, 30 May 2022 18:26:09 +0200 Subject: [PATCH 1/3] `get_ancestory`, `get_descendants` on Hierarchy Introduce utility functions on `Hierarchy` class --- TM1py/Objects/Hierarchy.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/TM1py/Objects/Hierarchy.py b/TM1py/Objects/Hierarchy.py index f948eac5..4288b17d 100644 --- a/TM1py/Objects/Hierarchy.py +++ b/TM1py/Objects/Hierarchy.py @@ -2,7 +2,7 @@ import collections import json -from typing import List, Dict, Iterable, Optional, Tuple, Union +from typing import List, Dict, Iterable, Optional, Tuple, Union, Set from TM1py.Objects.Element import Element from TM1py.Objects.ElementAttribute import ElementAttribute @@ -139,6 +139,38 @@ def get_element(self, element_name: str) -> Element: else: raise ValueError("Element: {} not found in Hierarchy: {}".format(element_name, self.name)) + def get_ancestors(self, element_name: str, recursive: bool = False): + ancestors = set() + + for (parent, component) in self._edges: + if not case_and_space_insensitive_equals(component, element_name): + continue + + ancestor: Element = self.elements[parent] + ancestors.add(ancestor) + + if recursive: + ancestors = ancestors.union(self.get_ancestors(ancestor.name, True)) + return ancestors + + def get_descendants(self, element_name: str, recursive: bool = False, leaves_only=False) -> Set[Element]: + descendants = set() + + for (parent, component) in self._edges: + if not case_and_space_insensitive_equals(parent, element_name): + continue + + descendant: Element = self.elements[component] + if not leaves_only: + descendants.add(descendant) + else: + if descendant.element_type == Element.Types.NUMERIC: + descendants.add(descendant) + + if recursive and descendant.element_type == Element.Types.CONSOLIDATED: + descendants = descendants.union(self.get_descendants(descendant.name, True)) + return descendants + def add_element(self, element_name: str, element_type: Union[str, Element.Types]): if element_name in self._elements: raise ValueError("Element name must be unique") From 50a8bd99b2847fa9b4c8d0874dc1572be491028c Mon Sep 17 00:00:00 2001 From: MariusWirtz Date: Tue, 31 May 2022 12:03:58 +0200 Subject: [PATCH 2/3] Add tests for new hierarchy functions --- TM1py/Objects/Hierarchy.py | 8 +- TM1py/Services/HierarchyService.py | 4 +- Tests/Hierarchy_test.py | 138 ++++++++++++++++++++++++++++- 3 files changed, 143 insertions(+), 7 deletions(-) diff --git a/TM1py/Objects/Hierarchy.py b/TM1py/Objects/Hierarchy.py index 4288b17d..d32f001b 100644 --- a/TM1py/Objects/Hierarchy.py +++ b/TM1py/Objects/Hierarchy.py @@ -48,7 +48,7 @@ def __init__( self._name = name self._dimension_name = None self.dimension_name = dimension_name - self._elements = CaseAndSpaceInsensitiveDict() + self._elements: Dict[str, Element] = CaseAndSpaceInsensitiveDict() if elements: for elem in elements: self._elements[elem.name] = elem @@ -99,7 +99,7 @@ def dimension_name(self, dimension_name: str): self._dimension_name = dimension_name @property - def elements(self) -> CaseAndSpaceInsensitiveDict: + def elements(self) -> Dict[str, Element]: return self._elements @property @@ -107,7 +107,7 @@ def element_attributes(self) -> List[ElementAttribute]: return self._element_attributes @property - def edges(self) -> 'CaseAndSpaceInsensitiveTuplesDict': + def edges(self) -> Dict[Tuple[str], Element]: return self._edges @property @@ -139,7 +139,7 @@ def get_element(self, element_name: str) -> Element: else: raise ValueError("Element: {} not found in Hierarchy: {}".format(element_name, self.name)) - def get_ancestors(self, element_name: str, recursive: bool = False): + def get_ancestors(self, element_name: str, recursive: bool = False) -> Set[Element]: ancestors = set() for (parent, component) in self._edges: diff --git a/TM1py/Services/HierarchyService.py b/TM1py/Services/HierarchyService.py index fe69ea2a..112b2ed5 100644 --- a/TM1py/Services/HierarchyService.py +++ b/TM1py/Services/HierarchyService.py @@ -37,7 +37,7 @@ def create(self, hierarchy: Hierarchy, **kwargs): response = self._rest.POST(url, hierarchy.body, **kwargs) return response - def get(self, dimension_name: str, hierarchy_name: str, **kwargs): + def get(self, dimension_name: str, hierarchy_name: str, **kwargs) -> Hierarchy: """ get hierarchy :param dimension_name: name of the dimension @@ -51,7 +51,7 @@ def get(self, dimension_name: str, hierarchy_name: str, **kwargs): response = self._rest.GET(url, **kwargs) return Hierarchy.from_dict(response.json()) - def get_all_names(self, dimension_name: str, **kwargs): + def get_all_names(self, dimension_name: str, **kwargs) -> List[str]: """ get all names of existing Hierarchies in a dimension :param dimension_name: diff --git a/Tests/Hierarchy_test.py b/Tests/Hierarchy_test.py index 42283f44..1ed63c72 100644 --- a/Tests/Hierarchy_test.py +++ b/Tests/Hierarchy_test.py @@ -1,6 +1,6 @@ import unittest -from TM1py import Hierarchy +from TM1py import Hierarchy, Element class TestHierarchy(unittest.TestCase): @@ -39,6 +39,142 @@ def test_add_component_parent_is_string(self): hierarchy.add_component(parent_name="c1", component_name="e1", weight=1) print(str(error)) + def test_get_ancestors(self): + hierarchy = Hierarchy( + name="NotRelevant", + dimension_name="NotRelevant", + elements=[ + Element("Total", "Consolidated"), + Element("Europe", "Consolidated"), + Element("DACH", "Consolidated"), + Element("Germany", "Numeric"), + Element("Switzerland", "Numeric"), + Element("Austria", "Numeric"), + Element("France", "Numeric"), + Element("Other", "Numeric")], + edges={ + ("Total", "Europe"): 1, + ("Europe", "DACH"): 1, + ("DACH", "Germany"): 1, + ("DACH", "Switzerland"): 1, + ("DACH", "Austria"): 1, + ("Europe", "France"): 1, + }) + + elements = hierarchy.get_ancestors("Germany", recursive=False) + self.assertEqual( + {Element("DACH", "Consolidated")}, + elements) + + def test_get_ancestors_recursive(self): + hierarchy = Hierarchy( + name="NotRelevant", + dimension_name="NotRelevant", + elements=[ + Element("Total", "Consolidated"), + Element("Europe", "Consolidated"), + Element("DACH", "Consolidated"), + Element("Germany", "Numeric"), + Element("Switzerland", "Numeric"), + Element("Austria", "Numeric"), + Element("France", "Numeric"), + Element("Other", "Numeric")], + edges={ + ("Total", "Europe"): 1, + ("Europe", "DACH"): 1, + ("DACH", "Germany"): 1, + ("DACH", "Switzerland"): 1, + ("DACH", "Austria"): 1, + ("Europe", "France"): 1, + }) + + elements = hierarchy.get_ancestors("Germany", recursive=True) + self.assertEqual( + {Element("DACH", "Consolidated"), Element("Europe", "Consolidated"), Element("Total", "Consolidated")}, + elements) + + def test_get_descendants(self): + hierarchy = Hierarchy( + name="NotRelevant", + dimension_name="NotRelevant", + elements=[ + Element("Total", "Consolidated"), + Element("Europe", "Consolidated"), + Element("DACH", "Consolidated"), + Element("Germany", "Numeric"), + Element("Switzerland", "Numeric"), + Element("Austria", "Numeric"), + Element("France", "Numeric"), + Element("Other", "Numeric")], + edges={ + ("Total", "Europe"): 1, + ("Europe", "DACH"): 1, + ("DACH", "Germany"): 1, + ("DACH", "Switzerland"): 1, + ("DACH", "Austria"): 1, + ("Europe", "France"): 1, + }) + + elements = hierarchy.get_descendants("DACH") + self.assertEqual( + {Element("Germany", "Numeric"), Element("Austria", "Numeric"), Element("Switzerland", "Numeric")}, + elements) + + def test_get_descendants_recursive(self): + hierarchy = Hierarchy( + name="NotRelevant", + dimension_name="NotRelevant", + elements=[ + Element("Total", "Consolidated"), + Element("Europe", "Consolidated"), + Element("DACH", "Consolidated"), + Element("Germany", "Numeric"), + Element("Switzerland", "Numeric"), + Element("Austria", "Numeric"), + Element("France", "Numeric"), + Element("Other", "Numeric")], + edges={ + ("Total", "Europe"): 1, + ("Europe", "DACH"): 1, + ("DACH", "Germany"): 1, + ("DACH", "Switzerland"): 1, + ("DACH", "Austria"): 1, + ("Europe", "France"): 1, + }) + + elements = hierarchy.get_descendants("Europe", recursive=True) + self.assertEqual( + {Element("DACH", "Consolidated"), Element("Germany", "Numeric"), Element("Austria", "Numeric"), + Element("Switzerland", "Numeric"), Element("France", "Numeric")}, + elements) + + def test_get_descendants_recursive_leaves_only(self): + hierarchy = Hierarchy( + name="NotRelevant", + dimension_name="NotRelevant", + elements=[ + Element("Total", "Consolidated"), + Element("Europe", "Consolidated"), + Element("DACH", "Consolidated"), + Element("Germany", "Numeric"), + Element("Switzerland", "Numeric"), + Element("Austria", "Numeric"), + Element("France", "Numeric"), + Element("Other", "Numeric")], + edges={ + ("Total", "Europe"): 1, + ("Europe", "DACH"): 1, + ("DACH", "Germany"): 1, + ("DACH", "Switzerland"): 1, + ("DACH", "Austria"): 1, + ("Europe", "France"): 1, + }) + + elements = hierarchy.get_descendants("Europe", recursive=True, leaves_only=True) + self.assertEqual( + {Element("Germany", "Numeric"), Element("Austria", "Numeric"), + Element("Switzerland", "Numeric"), Element("France", "Numeric")}, + elements) if __name__ == '__main__': unittest.main() From f43fff9b9510a16a261a935d5a5b056cd5b69b67 Mon Sep 17 00:00:00 2001 From: MariusWirtz Date: Wed, 1 Jun 2022 22:48:08 +0200 Subject: [PATCH 3/3] Fix native view test --- Tests/NativeView_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/NativeView_test.py b/Tests/NativeView_test.py index 9a407bab..18914627 100644 --- a/Tests/NativeView_test.py +++ b/Tests/NativeView_test.py @@ -19,7 +19,7 @@ def test_as_mdx_happy_case(self): "SELECT\r\n" "NON EMPTY {[d1].[e1]} ON 0,\r\n" "{[d2].[e2]} ON 1\r\n" - "FROM [C1]\r\n" + "FROM [c1]\r\n" "WHERE ([d3].[d3].[e3])", native_view.mdx)