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)
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, 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 = 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 @classmethod def from_dict(cls, hierarchy_as_dict): # 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): return self._name @name.setter def name(self, value): self._name = value @property def dimension_name(self): return self._dimension_name @property def elements(self): return self._elements @property def element_attributes(self): return self._element_attributes @property def edges(self): return self._edges @property def subsets(self): return self._subsets @property def balanced(self): return self._balanced @property def default_member(self): return self._default_member @property def body(self): return json.dumps(self._construct_body()) @property def body_as_dict(self): return self._construct_body() def add_element(self, element_name, element_type): if element_name in self._elements: raise Exception("Elementname has to be unqiue") e = Element(name=element_name, element_type=element_type) self._elements[element_name] = e def update_element(self, element_name, element_type=None): self._elements[element_name].element_type = element_type def add_edge(self, parent, component, weight): self._edges[(parent, component)] = weight def update_edge(self, parent, component, weight): self._edges[(parent, component)] = weight def remove_edge(self, parent, component): if (parent, component) in self.edges: del self.edges[(parent, component)] def add_element_attribute(self, name, attribute_type): if name not in self.element_attributes: self.element_attributes.append( ElementAttribute(name, attribute_type)) def remove_element_attribute(self, name): if name in self.element_attributes: self.element_attributes.remove(name) def _construct_body(self, element_attributes=False): """ 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.values())