def __init__( self, name: str, dimension_name: str, elements: Optional[Iterable['Element']] = None, element_attributes: Optional[Iterable['ElementAttribute']] = None, edges: Optional['Dict'] = None, subsets: Optional[Iterable[str]] = None, structure: Optional[int] = None, default_member: Optional[str] = None): self._name = name self._dimension_name = None self.dimension_name = dimension_name self._elements = CaseAndSpaceInsensitiveDict() if elements: for elem in elements: self._elements[elem.name] = elem self._element_attributes = list( element_attributes) if element_attributes else [] self._edges = CaseAndSpaceInsensitiveTuplesDict( edges) if edges else CaseAndSpaceInsensitiveTuplesDict() self._subsets = list(subsets) if subsets else [] # balanced is true, false or None (in versions < TM1 11) self._balanced = False if not structure else structure == 0 self._default_member = default_member
def test_eq(self): _map = CaseAndSpaceInsensitiveTuplesDict() _map[("Elem1", "Elem1")] = "Value1" _map[("Elem1", "Elem2")] = 2 _map[("Elem1", "Elem3")] = 3 self.assertEqual(_map, self.map) _map = CaseAndSpaceInsensitiveTuplesDict() _map[("Elem 1", "Elem1")] = "Value1" _map[("ELEM 1", "E L E M 2")] = 2 _map[(" Elem1 ", "Elem 3")] = 3 self.assertEqual(_map, self.map)
def __init__(self, name, dimension_name, elements=None, element_attributes=None, edges=None, subsets=None, structure=None, default_member=None): self._name = name self._dimension_name = dimension_name self._elements = CaseAndSpaceInsensitiveDict() if elements: for elem in elements: self._elements[elem.name] = elem self._element_attributes = element_attributes if element_attributes else [] self._edges = CaseAndSpaceInsensitiveTuplesDict(edges) if edges else CaseAndSpaceInsensitiveTuplesDict() self._subsets = subsets if subsets else [] # balanced is true, false or None (in versions < TM1 11) self._balanced = structure if not structure else structure == 0 self._default_member = default_member
def extract_cellset_rows_and_values(self, cellset_id, element_unique_names=True, **kwargs): request = "/api/v1/Cellsets('{}')?$expand=" \ "Axes($filter=Ordinal eq 1;$expand=Tuples(" \ "$expand=Members($select=Element;$expand=Element($select={}))))," \ "Cells($select=Value)".format(cellset_id, "UniqueName" if element_unique_names else "Name") response = self._rest.GET(request=request, data='') response_json = response.json() rows = response_json["Axes"][0]["Tuples"] cell_values = [cell["Value"] for cell in response_json["Cells"]] result = CaseAndSpaceInsensitiveTuplesDict() number_rows = len(rows) # avoid division by zero if not number_rows: return result number_cells = len(cell_values) number_columns = int(number_cells / number_rows) cell_values_by_row = [ cell_values[cell_counter:cell_counter + number_columns] for cell_counter in range(0, number_cells, number_columns) ] element_names_by_row = [ tuple(member["Element"] ["UniqueName" if element_unique_names else "Name"] for member in tupl["Members"]) for tupl in rows ] for element_tuple, cells in zip(element_names_by_row, cell_values_by_row): result[element_tuple] = cells return result
def from_dict(cls, hierarchy_as_dict: Dict) -> 'Hierarchy': # Build the Dictionary for the edges edges = CaseAndSpaceInsensitiveTuplesDict({ (edge['ParentName'], edge['ComponentName']): edge['Weight'] for edge in hierarchy_as_dict['Edges'] }) return cls(name=hierarchy_as_dict['Name'], dimension_name=hierarchy_as_dict['UniqueName'] [1:hierarchy_as_dict['UniqueName'].find("].[")], elements=[ Element.from_dict(elem) for elem in hierarchy_as_dict['Elements'] ], element_attributes=[ ElementAttribute(ea['Name'], ea['Type']) for ea in hierarchy_as_dict['ElementAttributes'] ], edges=edges, subsets=[ subset['Name'] for subset in hierarchy_as_dict['Subsets'] ], structure=hierarchy_as_dict['Structure'] if 'Structure' in hierarchy_as_dict else None, default_member=hierarchy_as_dict['DefaultMember']['Name'] if hierarchy_as_dict['DefaultMember'] else None)
def test_ne(self): map = CaseAndSpaceInsensitiveTuplesDict() map[("Elem1", "Elem1")] = "Value1" map[("Elem1", "Elem2")] = 0 map[("Elem1", "Elem3")] = 3 self.assertNotEqual(map, self.map) map = CaseAndSpaceInsensitiveTuplesDict() map[("Elem 1", "Elem1")] = "Value1" map[("ELEM 1", "E L E M 2")] = "wrong" map[(" Elem1 ", "Elem 3")] = 3 self.assertNotEqual(map, self.map) map = CaseAndSpaceInsensitiveTuplesDict() map[("wrong", "Elem1")] = "Value1" map[("Elem1", "Elem2")] = 2 map[("Elem1", "Elem3")] = 3 self.assertNotEqual(map, self.map)
def __init__(self, name, dimension_name, elements=None, element_attributes=None, edges=None, subsets=None, default_member=None): self._name = name self._dimension_name = dimension_name self._elements = CaseAndSpaceInsensitiveDict() if elements: for elem in elements: self._elements[elem.name] = elem self._element_attributes = element_attributes if element_attributes else [] self._edges = edges if edges else CaseAndSpaceInsensitiveTuplesDict() self._subsets = subsets if subsets else [] self._default_member = default_member
def extract_cellset_rows_and_cells(self, cellset_id, **kwargs): request = "/api/v1/Cellsets('{}')?$expand=" \ "Axes($filter=Ordinal eq 1;$expand=Tuples($expand=Members($select=Element;$expand=Element($select=UniqueName))))," \ "Cells($select=Value)".format(cellset_id) response = self._rest.GET(request=request, data='') response_json = response.json() rows = response_json["Axes"][0]["Tuples"] cell_values = [cell["Value"] for cell in response_json["Cells"]] number_rows = len(rows) number_cells = len(cell_values) number_cells_in_row = int(number_cells / number_rows) cells_by_row = [cell_values[start:end] for start, end in zip(range(0, number_rows), range(number_cells_in_row, number_cells))] unique_element_names_by_row = [tuple(member["Element"]["UniqueName"] for member in tupl["Members"]) for tupl in rows] result = CaseAndSpaceInsensitiveTuplesDict() for unique_element_names, cells in zip(unique_element_names_by_row, cells_by_row): result[unique_element_names] = cells return result
def remove_all_edges(self): self._edges = CaseAndSpaceInsensitiveTuplesDict()
class Hierarchy(TM1Object): """ Abstraction of TM1 Hierarchy Requires reference to a Dimension Elements modeled as a Dictionary where key is the element name and value an instance of TM1py.Element { 'US': instance of TM1py.Element, 'CN': instance of TM1py.Element, 'AU': instance of TM1py.Element } ElementAttributes of type TM1py.Objects.ElementAttribute Edges are represented as a TM1py.Utils.CaseAndSpaceInsensitiveTupleDict: { (parent1, component1) : 10, (parent1, component2) : 30 } Subsets is list of type TM1py.Subset """ def __init__( self, name: str, dimension_name: str, elements: Optional[Iterable['Element']] = None, element_attributes: Optional[Iterable['ElementAttribute']] = None, edges: Optional['Dict'] = None, subsets: Optional[Iterable[str]] = None, structure: Optional[int] = None, default_member: Optional[str] = None): self._name = name self._dimension_name = None self.dimension_name = dimension_name self._elements = CaseAndSpaceInsensitiveDict() if elements: for elem in elements: self._elements[elem.name] = elem self._element_attributes = list( element_attributes) if element_attributes else [] self._edges = CaseAndSpaceInsensitiveTuplesDict( edges) if edges else CaseAndSpaceInsensitiveTuplesDict() self._subsets = list(subsets) if subsets else [] # balanced is true, false or None (in versions < TM1 11) self._balanced = False if not structure else structure == 0 self._default_member = default_member @classmethod def from_dict(cls, hierarchy_as_dict: Dict) -> 'Hierarchy': # Build the Dictionary for the edges edges = CaseAndSpaceInsensitiveTuplesDict({ (edge['ParentName'], edge['ComponentName']): edge['Weight'] for edge in hierarchy_as_dict['Edges'] }) return cls(name=hierarchy_as_dict['Name'], dimension_name=hierarchy_as_dict['UniqueName'] [1:hierarchy_as_dict['UniqueName'].find("].[")], elements=[ Element.from_dict(elem) for elem in hierarchy_as_dict['Elements'] ], element_attributes=[ ElementAttribute(ea['Name'], ea['Type']) for ea in hierarchy_as_dict['ElementAttributes'] ], edges=edges, subsets=[ subset['Name'] for subset in hierarchy_as_dict['Subsets'] ], structure=hierarchy_as_dict['Structure'] if 'Structure' in hierarchy_as_dict else None, default_member=hierarchy_as_dict['DefaultMember']['Name'] if hierarchy_as_dict['DefaultMember'] else None) @property def name(self) -> str: return self._name @name.setter def name(self, value: str): self._name = value @property def dimension_name(self) -> str: return self._dimension_name @dimension_name.setter def dimension_name(self, dimension_name: str): self._dimension_name = dimension_name @property def elements(self) -> CaseAndSpaceInsensitiveDict: return self._elements @property def element_attributes(self) -> List[ElementAttribute]: return self._element_attributes @property def edges(self) -> 'CaseAndSpaceInsensitiveTuplesDict': return self._edges @property def subsets(self) -> List[str]: return self._subsets @property def balanced(self) -> bool: return self._balanced @property def default_member(self) -> str: return self._default_member @property def body(self) -> str: return json.dumps(self._construct_body()) @property def body_as_dict(self) -> Dict: return self._construct_body() def contains_element(self, element_name: str) -> bool: return element_name in self._elements def get_element(self, element_name: str) -> Element: if element_name in self._elements: return self._elements[element_name] else: raise ValueError("Element: {} not found in Hierarchy: {}".format( element_name, self.name)) 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") self._elements[element_name] = Element(name=element_name, element_type=element_type) def update_element(self, element_name: str, element_type: Union[str, Element.Types]): self._elements[element_name].element_type = element_type def remove_element(self, element_name: str): if element_name not in self._elements: return del self._elements[element_name] self.remove_edges_related_to_element(element_name=element_name) def remove_all_elements(self): self._elements = CaseAndSpaceInsensitiveDict() self.remove_all_edges() def add_edge(self, parent: str, component: str, weight: int): self._edges[(parent, component)] = weight def update_edge(self, parent: str, component: str, weight: int): self._edges[(parent, component)] = weight def remove_edge(self, parent: str, component: str): if (parent, component) in self.edges: del self.edges[(parent, component)] def remove_edges(self, edges: Iterable[Tuple[str, str]]): for edge in edges: self.remove_edge(*edge) def remove_all_edges(self): self._edges = CaseAndSpaceInsensitiveTuplesDict() def remove_edges_related_to_element(self, element_name: str): element_name_adjusted = lower_and_drop_spaces(element_name) edges_to_remove = set() for edge in self._edges.adjusted_keys(): if element_name_adjusted in edge: edges_to_remove.add(edge) self.remove_edges(edges=edges_to_remove) def add_element_attribute(self, name: str, attribute_type: str): attribute = ElementAttribute(name, attribute_type) if attribute not in self.element_attributes: self.element_attributes.append(attribute) def remove_element_attribute(self, name: str): self._element_attributes = [ element_attribute for element_attribute in self.element_attributes if not case_and_space_insensitive_equals(element_attribute.name, name) ] def _construct_body(self, element_attributes: Optional[bool] = False) -> Dict: """ With TM1 10.2.2 Hierarchy and Element Attributes can't be created in one batch -> https://www.ibm.com/developerworks/community/forums/html/threadTopic?id=d91f3e0e-d305-44db-ac02-2fdcbee00393 Thus, no need to have the ElementAttribute included in the JSON :param element_attributes: Only include element_attributes in body if explicitly asked for :return: """ body_as_dict = collections.OrderedDict() body_as_dict['Name'] = self._name body_as_dict['Elements'] = [] body_as_dict['Edges'] = [] for element in self._elements.values(): body_as_dict['Elements'].append(element.body_as_dict) for edge in self._edges: edge_as_dict = collections.OrderedDict() edge_as_dict['ParentName'] = edge[0] edge_as_dict['ComponentName'] = edge[1] edge_as_dict['Weight'] = self._edges[edge] body_as_dict['Edges'].append(edge_as_dict) if element_attributes: body_as_dict['ElementAttributes'] = [ element_attribute.body_as_dict for element_attribute in self._element_attributes ] return body_as_dict def __iter__(self): return iter(self._elements.values()) def __len__(self): return len(self._elements) def __contains__(self, item): return self.contains_element(item) def __getitem__(self, item): return self.get_element(item)
def build_date_dimension(tm1, dimension_name, first_date, last_date, overwrite): date_span = last_date - first_date dates = [ str(first_date + timedelta(day)) for day in range(date_span.days + 1) ] # Build Leaves leaves = [Element(name=date, element_type='Numeric') for date in dates] # Build Consolidations years = [str(year) for year in range(first_date.year, last_date.year + 1)] consolidations = [ Element(name=year, element_type='Consolidated') for year in years ] for year in years: for month in range(1, 13): year_month = year + "-" + str(month).zfill(2) consolidations.append( Element(name=year_month, element_type="Consolidated")) # Build Elements elements = leaves + consolidations # Build Edges edges = CaseAndSpaceInsensitiveTuplesDict() for year in years: for month in range(1, 13): year_month = year + "-" + str(month).zfill(2) edges[(year, year_month)] = 1 for date in filter(lambda d: d[0:4] == year, dates): year_month = date[0:7] edges[(year_month, date)] = 1 # Build Attribute attributes = [ ElementAttribute('Alias', 'Alias'), ElementAttribute('Year', 'String'), ElementAttribute('Month', 'String'), ElementAttribute('Day', 'String'), ElementAttribute('Weekday', 'String') ] # write Aliases attribute_values = {} for date in dates: # Year, Month, Day Attributes attribute_values[(date, 'Year')] = date.split('-')[0] attribute_values[(date, 'Month')] = date.split('-')[1] attribute_values[(date, 'Day')] = date.split('-')[2] attribute_values[( date, 'Weekday')] = dateutil.parser.parse(date).weekday() + 1 # US Notation as Alias year, month, day = [str(int(ymd)) for ymd in date.split('-')] attribute_values[(date, 'Alias')] = month + "/" + day + "/" + year # Build Hierarchy, Dimension hier = Hierarchy(name=dimension_name, dimension_name=dimension_name, elements=elements, edges=edges, element_attributes=attributes) dim = Dimension(name=dimension_name, hierarchies=[hier]) # Interaction with TM1 exists = tm1.dimensions.exists(dimension_name) if not exists: tm1.dimensions.create(dim) elif exists and overwrite: tm1.dimensions.update(dim) if not exists or overwrite: tm1.cubes.cells.write_values(cube_name="}ElementAttributes_" + dimension_name, cellset_as_dict=attribute_values) # Year Subsets for year in years: expr = "{ FILTER ( {TM1SubsetAll([Date])}, [Date].[Year] = '" + year + "' ) }" subset = Subset(year, dimension_name=dimension_name, hierarchy_name=dimension_name, expression=expr) if not tm1.dimensions.hierarchies.subsets.exists( year, dimension_name, dimension_name, private=False): tm1.dimensions.hierarchies.subsets.create(subset, private=False) else: tm1.dimensions.hierarchies.subsets.update(subset, private=False)
def setUp(self): self.map = CaseAndSpaceInsensitiveTuplesDict() self.map[("Elem1", "Elem1")] = "Value1" self.map[("Elem1", "Elem2")] = 2 self.map[("Elem1", "Elem3")] = 3
class TestCaseAndSpaceInsensitiveTuplesDict(unittest.TestCase): tm1: TM1Service map: CaseAndSpaceInsensitiveTuplesDict @classmethod def setUpClass(cls): """ Establishes a connection to TM1 and creates TM! objects to use across all tests """ # Connection to TM1 config = configparser.ConfigParser() config.read(Path(__file__).parent.joinpath('config.ini')) cls.tm1 = TM1Service(**config['tm1srv01']) def setUp(self): self.map = CaseAndSpaceInsensitiveTuplesDict() self.map[("Elem1", "Elem1")] = "Value1" self.map[("Elem1", "Elem2")] = 2 self.map[("Elem1", "Elem3")] = 3 def tearDown(self): del self.map def test_del(self): self.assertIn(("Elem1", "Elem1"), self.map) self.assertIn(("Elem1", "Elem2"), self.map) self.assertIn(("Elem1", "Elem3"), self.map) del self.map[("El em1", "ELEM1")] del self.map[("El em1", "E L E M 2")] del self.map[("El em1", " eLEM3")] self.assertNotIn(("Elem1", "Elem1"), self.map) self.assertNotIn(("Elem1", "Elem2"), self.map) self.assertNotIn(("Elem1", "Elem3"), self.map) def test_eq(self): _map = CaseAndSpaceInsensitiveTuplesDict() _map[("Elem1", "Elem1")] = "Value1" _map[("Elem1", "Elem2")] = 2 _map[("Elem1", "Elem3")] = 3 self.assertEqual(_map, self.map) _map = CaseAndSpaceInsensitiveTuplesDict() _map[("Elem 1", "Elem1")] = "Value1" _map[("ELEM 1", "E L E M 2")] = 2 _map[(" Elem1 ", "Elem 3")] = 3 self.assertEqual(_map, self.map) def test_ne(self): _map = CaseAndSpaceInsensitiveTuplesDict() _map[("Elem1", "Elem1")] = "Value1" _map[("Elem1", "Elem2")] = 0 _map[("Elem1", "Elem3")] = 3 self.assertNotEqual(_map, self.map) _map = CaseAndSpaceInsensitiveTuplesDict() _map[("Elem 1", "Elem1")] = "Value1" _map[("ELEM 1", "E L E M 2")] = "wrong" _map[(" Elem1 ", "Elem 3")] = 3 self.assertNotEqual(_map, self.map) _map = CaseAndSpaceInsensitiveTuplesDict() _map[("wrong", "Elem1")] = "Value1" _map[("Elem1", "Elem2")] = 2 _map[("Elem1", "Elem3")] = 3 self.assertNotEqual(_map, self.map) def test_get(self): self.assertEqual(self.map[("ELEM1", "ELEM1")], "Value1") self.assertEqual(self.map[("elem1", "e l e m 2")], 2) self.assertEqual(self.map[("e l e M 1", "elem3")], 3) self.assertNotEqual(self.map[("e l e M 1", "elem3")], 2) def test_iter(self): for tuple1, tuple2 in zip(self.map, [("Elem1", "Elem1"), ("Elem1", "Elem2"), ("Elem1", "Elem3")]): self.assertEqual(tuple1, tuple2) def test_len(self): self.assertEqual(len(self.map), 3) def test_set(self): self.map[("E L E M 1", "E L E M 2")] = 3 self.assertEqual(self.map[("Elem1", "Elem2")], 3) def test_copy(self): c = self.map.copy() self.assertIsNot(c, self.map) self.assertEqual(c, self.map) def test_full(self): mdx_rows = '[}Clients].Members' mdx_columns = '[}Groups].Members' cube_name = '[}ClientGroups]' mdx = 'SELECT {} ON ROWS, {} ON COLUMNS FROM {}'.format( mdx_rows, mdx_columns, cube_name) data = self.tm1.cubes.cells.execute_mdx(mdx) # Get if self.tm1.version[0:2] == '10': coordinates = ('[}Clients].[ad min]', '[}Groups].[ADM IN]') else: coordinates = ('[}Clients].[}Clients].[ad min]', '[}Groups].[}Groups].[ADM IN]') self.assertIsNotNone(data[coordinates]) # Delete if self.tm1.version[0:2] == '10': coordinates = ('[}clients].[}clients].[admin]', '[}groups].[}groups].[admin]') else: coordinates = ('[}clients].[}clients].[admin]', '[}groups].[}groups].[admin]') self.assertTrue(coordinates in data) del data[coordinates] self.assertFalse(coordinates in data) # Copy data_cloned = data.copy() self.assertTrue(data_cloned == data) self.assertFalse(data_cloned is data) @classmethod def tearDownClass(cls): cls.tm1.logout()
class TestCaseAndSpaceInsensitiveTuplesDict(unittest.TestCase): @classmethod def setUpClass(cls): cls.tm1 = TM1Service(**config['tm1srv01']) def setUp(self): self.map = CaseAndSpaceInsensitiveTuplesDict() self.map[("Elem1", "Elem1")] = "Value1" self.map[("Elem1", "Elem2")] = 2 self.map[("Elem1", "Elem3")] = 3 def tearDown(self): del self.map def test_del(self): self.assertIn(("Elem1", "Elem1"), self.map) self.assertIn(("Elem1", "Elem2"), self.map) self.assertIn(("Elem1", "Elem3"), self.map) del self.map[("El em1", "ELEM1")] del self.map[("El em1", "E L E M 2")] del self.map[("El em1", " eLEM3")] self.assertNotIn(("Elem1", "Elem1"), self.map) self.assertNotIn(("Elem1", "Elem2"), self.map) self.assertNotIn(("Elem1", "Elem3"), self.map) def test_eq(self): map = CaseAndSpaceInsensitiveTuplesDict() map[("Elem1", "Elem1")] = "Value1" map[("Elem1", "Elem2")] = 2 map[("Elem1", "Elem3")] = 3 self.assertEqual(map, self.map) map = CaseAndSpaceInsensitiveTuplesDict() map[("Elem 1", "Elem1")] = "Value1" map[("ELEM 1", "E L E M 2")] = 2 map[(" Elem1 ", "Elem 3")] = 3 self.assertEqual(map, self.map) def test_ne(self): map = CaseAndSpaceInsensitiveTuplesDict() map[("Elem1", "Elem1")] = "Value1" map[("Elem1", "Elem2")] = 0 map[("Elem1", "Elem3")] = 3 self.assertNotEqual(map, self.map) map = CaseAndSpaceInsensitiveTuplesDict() map[("Elem 1", "Elem1")] = "Value1" map[("ELEM 1", "E L E M 2")] = "wrong" map[(" Elem1 ", "Elem 3")] = 3 self.assertNotEqual(map, self.map) map = CaseAndSpaceInsensitiveTuplesDict() map[("wrong", "Elem1")] = "Value1" map[("Elem1", "Elem2")] = 2 map[("Elem1", "Elem3")] = 3 self.assertNotEqual(map, self.map) def test_get(self): self.assertEqual(self.map[("ELEM1", "ELEM1")], "Value1") self.assertEqual(self.map[("elem1", "e l e m 2")], 2) self.assertEqual(self.map[("e l e M 1", "elem3")], 3) self.assertNotEqual(self.map[("e l e M 1", "elem3")], 2) def test_iter(self): for tuple1, tuple2 in zip(self.map, [("Elem1", "Elem1"), ("Elem1", "Elem2"), ("Elem1", "Elem3")]): self.assertEqual(tuple1, tuple2) def test_len(self): self.assertEqual(len(self.map), 3) def test_set(self): self.map[("E L E M 1", "E L E M 2")] = 3 self.assertEqual(self.map[("Elem1", "Elem2")], 3) def test_copy(self): c = self.map.copy() self.assertIsNot(c, self.map) self.assertEqual(c, self.map) def test_full(self): mdx_rows = '[}Clients].Members' mdx_columns = '[}Groups].Members' cube_name = '[}ClientGroups]' mdx = 'SELECT {} ON ROWS, {} ON COLUMNS FROM {}'.format(mdx_rows, mdx_columns, cube_name) data = self.tm1.cubes.cells.execute_mdx(mdx) # Get if self.tm1.version[0:2] == '10': coordinates = ('[}Clients].[ad min]', '[}Groups].[ADM IN]') else: coordinates = ('[}Clients].[}Clients].[ad min]', '[}Groups].[}Groups].[ADM IN]') self.assertIsNotNone(data[coordinates]) # Delete if self.tm1.version[0:2] == '10': coordinates = ('[}clients].[}clients].[admin]', '[}groups].[}groups].[admin]') else: coordinates = ('[}clients].[}clients].[admin]', '[}groups].[}groups].[admin]') self.assertTrue(coordinates in data) del data[coordinates] self.assertFalse(coordinates in data) # Copy data_cloned = data.copy() self.assertTrue(data_cloned == data) self.assertFalse(data_cloned is data) @classmethod def tearDownClass(cls): cls.tm1.logout()