Tm1py: tm1.cells.write error when writing to string attribute values

Created on 18 Dec 2020  路  5Comments  路  Source: cubewise-code/tm1py

Describe the bug
Writing string attributes throws an exception when using parameter use_ti=True in the function tm1.cells.write

TM1py exception
File "C:\ProgramData\Anaconda3\lib\site-packages\TM1py\Services\CellService.py", line 453, in write_through_unbound_process raise TM1pyException(f"{len(successes) - sum(successes)} out of {len(successes)} unbound processes failed") TM1py.Exceptions.Exceptions.TM1pyException: 1 out of 1 unbound processes failed

The TI error log "Cell type is string"

To Reproduce
tm1.cells.write(cube_name=cube_name, cellset_as_dict=cellset_attributes, use_ti=True)

Version
TM1py 1.5.0

bug

Most helpful comment

The previous approach has a bug. This version seems to work faster.

  1. Checking the element type and using the respective TM1 function
  2. skipping any consolidated elements
    @require_admin
    @manage_transaction_log
    def write_through_unbound_process(self, cube_name: str, cellset_as_dict: Dict, increment: bool = False,
                                      sandbox_name: str = None, **kwargs):
        if sandbox_name:
            raise NotImplementedError("Function does not support writing to sandboxes yet")

        successes = list()
        statements = list()

        from TM1py import CubeService
        from TM1py import ElementService
        cube_service = CubeService(self._rest)
        element_service = ElementService(self._rest)
        measure_dimension = cube_service.get(cube_name).dimensions[-1]
        measure_dimension_elements = element_service.get_element_types(dimension_name=measure_dimension,
                                                                       hierarchy_name=measure_dimension,
                                                                       skip_consolidations=True)

        for coordinates, value in cellset_as_dict.items():
            element_type = measure_dimension_elements.get(coordinates[-1])
            if element_type == "Numeric":
                function_str = f"{'CellIncrementN(' if increment else 'CellPutN('}"
                value_str = f"{value}"
            elif element_type == "String":
                function_str = 'CellPutS('
                value_str = f"'{value}'"
            else:
                continue
            statement = "".join([
                function_str,
                value_str,
                f",'{cube_name}',",
                ",".join(f"'{element}'" for element in coordinates),
                ");\n"])
            statements.append(statement)

        chunk = list()
        for n, statement in enumerate(statements):
            chunk.append(statement)
            if n > 0 and n % Process.MAX_STATEMENTS == 0:
                process = Process(name="", prolog_procedure="\r".join(chunk))
                success, _, _ = self.execute_unbound_process(process, **kwargs)
                successes.append(success)
                chunk = list()

        process = Process(name="", prolog_procedure="\r".join(chunk))
        success, _, _ = self.execute_unbound_process(process, **kwargs)
        successes.append(success)

        if not all(successes):
            raise TM1pyException(f"{len(successes) - sum(successes)} out of {len(successes)} unbound processes failed")

All 5 comments

@MariusWirtz ,

Here is a solution but not sure if this is the most efficient way to do this. I'm using a DTYPE check here in the TI for every cellset item.

    @require_admin
    @manage_transaction_log
    def write_through_unbound_process(self, cube_name: str, cellset_as_dict: Dict, increment: bool = False,
                                      sandbox_name: str = None, **kwargs):
        if sandbox_name:
            raise NotImplementedError("Function does not support writing to sandboxes yet")

        successes = list()
        statements = list()

        from TM1py import CubeService
        cube_service = CubeService(self._rest)
        measure_dimension = cube_service.get(cube_name).dimensions[-1]
        function_str = f"{'CellIncrementN(' if increment else 'CellPutN('}"
        for coordinates, value in cellset_as_dict.items():
            value_str = f'{value}' if isinstance(value, str) else str(value)

            statement = f"IF(DTYPE('{measure_dimension}', '{coordinates[-1]}') @= 'N');\n"
            try:
                float(value)
                statement = "".join([
                    statement,
                    function_str,
                    value_str,
                    f",'{cube_name}',",
                    ",".join(f"'{element}'" for element in coordinates),
                    ");\n"])
            except ValueError:
                statement = "".join([statement, "'This is a temp line.';\n"])
            statement=  "".join([statement, "ELSE;\n"])
            statement = "".join([
                statement,
                'CellPutS(',
                f"'{value_str}'",
                f",'{cube_name}',",
                ",".join(f"'{element}'" for element in coordinates),
                ");\n",
                "ENDIF;\n"])
            statements.append(statement)

        chunk = list()
        for n, statement in enumerate(statements):
            chunk.append(statement)
            if n > 0 and n % Process.MAX_STATEMENTS == 0:
                process = Process(name="", prolog_procedure="\r".join(chunk))
                success, _, _ = self.execute_unbound_process(process, **kwargs)
                successes.append(success)
                chunk = list()

        process = Process(name="", prolog_procedure="\r".join(chunk))
        success, _, _ = self.execute_unbound_process(process, **kwargs)
        successes.append(success)

        if not all(successes):
            raise TM1pyException(f"{len(successes) - sum(successes)} out of {len(successes)} unbound processes failed")

I think that is the only way to check for a string within a process.
Alternatively you could add a parameter to write numeric only to avoid the check and gain some performance.

The previous approach has a bug. This version seems to work faster.

  1. Checking the element type and using the respective TM1 function
  2. skipping any consolidated elements
    @require_admin
    @manage_transaction_log
    def write_through_unbound_process(self, cube_name: str, cellset_as_dict: Dict, increment: bool = False,
                                      sandbox_name: str = None, **kwargs):
        if sandbox_name:
            raise NotImplementedError("Function does not support writing to sandboxes yet")

        successes = list()
        statements = list()

        from TM1py import CubeService
        from TM1py import ElementService
        cube_service = CubeService(self._rest)
        element_service = ElementService(self._rest)
        measure_dimension = cube_service.get(cube_name).dimensions[-1]
        measure_dimension_elements = element_service.get_element_types(dimension_name=measure_dimension,
                                                                       hierarchy_name=measure_dimension,
                                                                       skip_consolidations=True)

        for coordinates, value in cellset_as_dict.items():
            element_type = measure_dimension_elements.get(coordinates[-1])
            if element_type == "Numeric":
                function_str = f"{'CellIncrementN(' if increment else 'CellPutN('}"
                value_str = f"{value}"
            elif element_type == "String":
                function_str = 'CellPutS('
                value_str = f"'{value}'"
            else:
                continue
            statement = "".join([
                function_str,
                value_str,
                f",'{cube_name}',",
                ",".join(f"'{element}'" for element in coordinates),
                ");\n"])
            statements.append(statement)

        chunk = list()
        for n, statement in enumerate(statements):
            chunk.append(statement)
            if n > 0 and n % Process.MAX_STATEMENTS == 0:
                process = Process(name="", prolog_procedure="\r".join(chunk))
                success, _, _ = self.execute_unbound_process(process, **kwargs)
                successes.append(success)
                chunk = list()

        process = Process(name="", prolog_procedure="\r".join(chunk))
        success, _, _ = self.execute_unbound_process(process, **kwargs)
        successes.append(success)

        if not all(successes):
            raise TM1pyException(f"{len(successes) - sum(successes)} out of {len(successes)} unbound processes failed")

@rkvinoth thanks for the proposed solution.

I created a PR based on your proposal:
https://github.com/cubewise-code/tm1py/pull/446

Please review and approve my changes :)

Fixed with #446

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hermie64 picture hermie64  路  4Comments

scrambldchannel picture scrambldchannel  路  6Comments

scrumthing picture scrumthing  路  4Comments

pourushgupta picture pourushgupta  路  3Comments

Andy-Hofelmeyer picture Andy-Hofelmeyer  路  3Comments