예제 #1
0
class DimensionService(ObjectService):
    """ Service to handle Object Updates for TM1 Dimensions
    
    """
    def __init__(self, rest):
        super().__init__(rest)
        self.hierarchies = HierarchyService(rest)
        self.subsets = SubsetService(rest)

    def create(self, dimension):
        """ Create a dimension

        :param dimension: instance of TM1py.Dimension
        :return: response
        """
        # If Dimension exists. throw Exception
        if self.exists(dimension.name):
            raise Exception("Dimension already exists")
        # If not all subsequent calls successfull -> undo everything that has been done in this function
        try:
            # Create Dimension, Hierarchies, Elements, Edges.
            request = "/api/v1/Dimensions"
            response = self._rest.POST(request, dimension.body)
            for hierarchy in dimension:
                if len(hierarchy.element_attributes) > 0:
                    self.hierarchies.update(hierarchy)
        except TM1pyException as e:
            # undo everything if problem in step 1 or 2
            if self.exists(dimension.name):
                self.delete(dimension.name)
            raise e
        return response

    def get(self, dimension_name):
        """ Get a Dimension

        :param dimension_name:
        :return:
        """
        request = "/api/v1/Dimensions('{}')?$expand=Hierarchies($expand=*)".format(
            dimension_name)
        response = self._rest.GET(request)
        return Dimension.from_json(response.text)

    def update(self, dimension):
        """ Update an existing dimension

        :param dimension: instance of TM1py.Dimension
        :return: None
        """
        # delete hierarchies that have been removed from the dimension object
        hierarchies_to_be_removed = set(
            self.hierarchies.get_all_names(dimension.name)) - set(
                dimension.hierarchy_names)
        for hierarchy_name in hierarchies_to_be_removed:
            self.hierarchies.delete(dimension_name=dimension.name,
                                    hierarchy_name=hierarchy_name)

        # update all Hierarchies except for the implicitly maintained 'Leaves' Hierarchy
        for hierarchy in dimension:
            if not case_and_space_insensitive_equals(hierarchy.name, "Leaves"):
                if self.hierarchies.exists(
                        dimension_name=hierarchy.dimension_name,
                        hierarchy_name=hierarchy.name):
                    self.hierarchies.update(hierarchy)
                else:
                    self.hierarchies.create(hierarchy)

    def delete(self, dimension_name):
        """ Delete a dimension

        :param dimension_name: Name of the dimension
        :return:
        """
        request = '/api/v1/Dimensions(\'{}\')'.format(dimension_name)
        return self._rest.DELETE(request)

    def exists(self, dimension_name):
        """ Check if dimension exists
        
        :return: 
        """
        request = "/api/v1/Dimensions('{}')".format(dimension_name)
        return self._exists(request)

    def get_all_names(self):
        """Ask TM1 Server for list with all dimension names

        :Returns:
            List of Strings
        """
        response = self._rest.GET('/api/v1/Dimensions?$select=Name', '')
        dimension_names = list(entry['Name']
                               for entry in response.json()['value'])
        return dimension_names

    def execute_mdx(self, dimension_name, mdx):
        """ Execute MDX against Dimension. 
        Requires }ElementAttributes_ Cube of the dimension to exist !
 
        :param dimension_name: Name of the Dimension
        :param mdx: valid Dimension-MDX Statement 
        :return: List of Element names
        """

        warnings.warn(
            "execute_mdx() will be deprecated; use ElementService execute_set_mdx.",
            DeprecationWarning,
            stacklevel=2)

        mdx_skeleton = "SELECT " \
                       "{} ON ROWS, " \
                       "{{ [}}ElementAttributes_{}].DefaultMember }} ON COLUMNS  " \
                       "FROM [}}ElementAttributes_{}]"
        mdx_full = mdx_skeleton.format(mdx, dimension_name, dimension_name)
        request = '/api/v1/ExecuteMDX?$expand=Axes(' \
                  '$filter=Ordinal eq 1;' \
                  '$expand=Tuples($expand=Members($select=Ordinal;$expand=Element($select=Name))))'
        payload = {"MDX": mdx_full}
        response = self._rest.POST(request,
                                   json.dumps(payload, ensure_ascii=False))
        raw_dict = response.json()
        return [
            row_tuple['Members'][0]['Element']['Name']
            for row_tuple in raw_dict['Axes'][0]['Tuples']
        ]

    def create_element_attributes_through_ti(self, dimension):
        """ 
        
        :param dimension. Instance of TM1py.Objects.Dimension class
        :return: 
        """
        process_service = ProcessService(self._rest)
        for h in dimension:
            statements = [
                "AttrInsert('{}', '', '{}', '{}');".format(
                    dimension.name, ea.name, ea.attribute_type[0])
                for ea in h.element_attributes
            ]
            process_service.execute_ti_code(lines_prolog=statements)
예제 #2
0
class DimensionService(ObjectService):
    """ Service to handle Object Updates for TM1 Dimensions
    
    """
    def __init__(self, rest: RestService):
        super().__init__(rest)
        self.hierarchies = HierarchyService(rest)
        self.subsets = SubsetService(rest)

    def create(self, dimension: Dimension, **kwargs) -> Response:
        """ Create a dimension

        :param dimension: instance of TM1py.Dimension
        :return: response
        """
        # If Dimension exists. throw Exception
        if self.exists(dimension.name):
            raise RuntimeError("Dimension '{}' already exists".format(
                dimension.name))
        # If not all subsequent calls successful -> undo everything that has been done in this function
        try:
            # Create Dimension, Hierarchies, Elements, Edges.
            url = "/api/v1/Dimensions"
            response = self._rest.POST(url, dimension.body, **kwargs)
            # Create ElementAttributes
            for hierarchy in dimension:
                if not case_and_space_insensitive_equals(
                        hierarchy.name, "Leaves"):
                    self.hierarchies.update_element_attributes(
                        hierarchy, **kwargs)
        except TM1pyException as e:
            # undo everything if problem in step 1 or 2
            if self.exists(dimension.name, **kwargs):
                self.delete(dimension.name)
            raise e
        return response

    def get(self, dimension_name: str, **kwargs) -> Dimension:
        """ Get a Dimension

        :param dimension_name:
        :return:
        """
        url = format_url(
            "/api/v1/Dimensions('{}')?$expand=Hierarchies($expand=*)",
            dimension_name)
        response = self._rest.GET(url, **kwargs)
        return Dimension.from_json(response.text)

    def update(self, dimension: Dimension, **kwargs):
        """ Update an existing dimension

        :param dimension: instance of TM1py.Dimension
        :return: None
        """
        # delete hierarchies that have been removed from the dimension object
        hierarchies_to_be_removed = CaseAndSpaceInsensitiveSet(
            *self.hierarchies.get_all_names(dimension.name, **kwargs))
        for hierarchy in dimension.hierarchy_names:
            hierarchies_to_be_removed.discard(hierarchy)

        # update all Hierarchies except for the implicitly maintained 'Leaves' Hierarchy
        for hierarchy in dimension:
            if not case_and_space_insensitive_equals(hierarchy.name, "Leaves"):
                if self.hierarchies.exists(hierarchy.dimension_name,
                                           hierarchy.name, **kwargs):
                    self.hierarchies.update(hierarchy, **kwargs)
                else:
                    self.hierarchies.create(hierarchy, **kwargs)

        # Edge case: elements in leaves hierarchy that do not exist in other hierarchies
        if "Leaves" in dimension:
            existing_leaves = CaseAndSpaceInsensitiveSet(
                self.hierarchies.elements.get_leaf_element_names(
                    dimension.name, "Leaves"))

            leaves_to_create = list()
            for leaf in dimension.get_hierarchy("Leaves"):
                if leaf.name not in existing_leaves:
                    leaves_to_create.append(leaf)

            if leaves_to_create:
                self.hierarchies.elements.add_elements(
                    dimension_name=dimension.name,
                    hierarchy_name="Leaves",
                    elements=leaves_to_create)

        for hierarchy_name in hierarchies_to_be_removed:
            if not case_and_space_insensitive_equals(hierarchy_name, "Leaves"):
                self.hierarchies.delete(dimension_name=dimension.name,
                                        hierarchy_name=hierarchy_name,
                                        **kwargs)

    def update_or_create(self, dimension: Dimension, **kwargs):
        """ update if exists else create

        :param dimension:
        :return:
        """
        if self.exists(dimension_name=dimension.name, **kwargs):
            self.update(dimension=dimension, **kwargs)
        else:
            self.create(dimension=dimension, **kwargs)

    def delete(self, dimension_name: str, **kwargs) -> Response:
        """ Delete a dimension

        :param dimension_name: Name of the dimension
        :return:
        """
        url = format_url("/api/v1/Dimensions('{}')", dimension_name)
        return self._rest.DELETE(url, **kwargs)

    def exists(self, dimension_name: str, **kwargs) -> bool:
        """ Check if dimension exists
        
        :return: 
        """
        url = format_url("/api/v1/Dimensions('{}')", dimension_name)
        return self._exists(url, **kwargs)

    def get_all_names(self,
                      skip_control_dims: bool = False,
                      **kwargs) -> List[str]:
        """Ask TM1 Server for list of all dimension names

        :skip_control_dims: bool, True to skip control dims
        :Returns:
            List of Strings
        """
        url = format_url(
            "/api/v1/{}?$select=Name",
            'ModelDimensions()' if skip_control_dims else 'Dimensions')

        response = self._rest.GET(url, **kwargs)

        dimension_names = list(entry['Name']
                               for entry in response.json()['value'])
        return dimension_names

    def get_number_of_dimensions(self,
                                 skip_control_dims: bool = False,
                                 **kwargs) -> int:
        """Ask TM1 Server for number of dimensions

        :skip_control_dims: bool, True to exclude control dims from count
        :return: Number of dimensions
        """

        if skip_control_dims:
            response = self._rest.GET(
                "/api/v1/ModelDimensions()?$select=Name&$top=0&$count",
                **kwargs)
            return response.json()['@odata.count']

        return int(self._rest.GET("/api/v1/Dimensions/$count", **kwargs).text)

    def execute_mdx(self, dimension_name: str, mdx: str, **kwargs) -> List:
        """ Execute MDX against Dimension. 
        Requires }ElementAttributes_ Cube of the dimension to exist !
 
        :param dimension_name: Name of the Dimension
        :param mdx: valid Dimension-MDX Statement 
        :return: List of Element names
        """

        warnings.warn(
            "execute_mdx() will be deprecated; use ElementService execute_set_mdx.",
            DeprecationWarning,
            stacklevel=2)

        mdx_skeleton = "SELECT " \
                       "{} ON ROWS, " \
                       "{{ [}}ElementAttributes_{}].DefaultMember }} ON COLUMNS  " \
                       "FROM [}}ElementAttributes_{}]"
        mdx_full = mdx_skeleton.format(mdx, dimension_name, dimension_name)
        request = '/api/v1/ExecuteMDX?$expand=Axes(' \
                  '$filter=Ordinal eq 1;' \
                  '$expand=Tuples($expand=Members($select=Ordinal;$expand=Element($select=Name))))'
        payload = {"MDX": mdx_full}
        response = self._rest.POST(request,
                                   json.dumps(payload, ensure_ascii=False),
                                   **kwargs)
        raw_dict = response.json()
        return [
            row_tuple['Members'][0]['Element']['Name']
            for row_tuple in raw_dict['Axes'][0]['Tuples']
        ]

    def create_element_attributes_through_ti(self, dimension: Dimension,
                                             **kwargs):
        """ 
        
        :param dimension. Instance of TM1py.Objects.Dimension class
        :return: 
        """
        process_service = ProcessService(self._rest)
        for h in dimension:
            statements = [
                "AttrInsert('{}', '', '{}', '{}');".format(
                    dimension.name, ea.name, ea.attribute_type[0])
                for ea in h.element_attributes
            ]
            process_service.execute_ti_code(lines_prolog=statements, **kwargs)