Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 74 additions & 3 deletions TM1py/Services/CubeService.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import json
import random
from typing import Dict, Iterable, List, Union
from typing import Dict, Iterable, List, Optional, Union

from requests import Response

Expand Down Expand Up @@ -101,10 +101,81 @@ def get_number_of_cubes(self, skip_control_cubes: bool = False, **kwargs) -> int

return int(self._rest.GET(url=format_url("/Cubes/$count"), **kwargs).text)

@require_version(version="11.8.018")
def get_measure_dimension(self, cube_name: str, **kwargs) -> str:
url = format_url("/Cubes('{}')/Dimensions?$select=Name", cube_name)
"""Get the measures dimension of a cube.

Reads the cube's MeasuresDimension navigation property. This property is
available from v11.8.018 onwards (and on v12); earlier versions do not expose
it. When no measures dimension is explicitly assigned, TM1 treats the last
dimension of the cube as the measures dimension, so that is returned as a
fallback.

:param cube_name:
:return: name of the measures dimension
"""
url = format_url("/Cubes('{}')/MeasuresDimension?$select=Name", cube_name)
response = self._rest.GET(url, **kwargs)
if response.status_code == 204 or not response.text:
return self.get_dimension_names(cube_name=cube_name, **kwargs)[-1]
return response.json()["Name"]

@require_version(version="11.8.018")
def get_time_dimension(self, cube_name: str, **kwargs) -> Optional[str]:
"""Get the time dimension of a cube.

Reads the cube's TimeDimension navigation property. This property is available
from v11.8.018 onwards (and on v12); earlier versions do not expose it. Returns
None when no time dimension is assigned to the cube.

:param cube_name:
:return: name of the time dimension, or None if not assigned
"""
url = format_url("/Cubes('{}')/TimeDimension?$select=Name", cube_name)
response = self._rest.GET(url, **kwargs)
return response.json()["value"][-1]["Name"]
if response.status_code == 204 or not response.text:
return None
return response.json()["Name"]

@require_data_admin
@require_version(version="11.8.018")
def set_measure_dimension(self, cube_name: str, dimension_name: str, **kwargs) -> Response:
"""Set the measures dimension of a cube.

Binds the cube's MeasuresDimension navigation property to the given dimension.
This property is available from v11.8.018 onwards (and on v12).

:param cube_name:
:param dimension_name: must be one of the cube's dimensions
:return: response
"""
self._validate_dimension_in_cube(cube_name=cube_name, dimension_name=dimension_name, **kwargs)
url = format_url("/Cubes('{}')", cube_name)
payload = {"MeasuresDimension@odata.bind": format_url("Dimensions('{}')", dimension_name)}
return self._rest.PATCH(url=url, data=json.dumps(payload), **kwargs)

@require_data_admin
@require_version(version="11.8.018")
def set_time_dimension(self, cube_name: str, dimension_name: str, **kwargs) -> Response:
"""Set the time dimension of a cube.

Binds the cube's TimeDimension navigation property to the given dimension.
This property is available from v11.8.018 onwards (and on v12).

:param cube_name:
:param dimension_name: must be one of the cube's dimensions
:return: response
"""
self._validate_dimension_in_cube(cube_name=cube_name, dimension_name=dimension_name, **kwargs)
url = format_url("/Cubes('{}')", cube_name)
payload = {"TimeDimension@odata.bind": format_url("Dimensions('{}')", dimension_name)}
return self._rest.PATCH(url=url, data=json.dumps(payload), **kwargs)

def _validate_dimension_in_cube(self, cube_name: str, dimension_name: str, **kwargs) -> None:
"""Raise ValueError if dimension_name is not a dimension of the cube"""
dimension_names = self.get_dimension_names(cube_name=cube_name, **kwargs)
if not any(case_and_space_insensitive_equals(dimension_name, dim) for dim in dimension_names):
raise ValueError(f"'{dimension_name}' is not a dimension of cube '{cube_name}'")

def update(self, cube: Cube, **kwargs) -> Response:
"""Update existing cube on TM1 Server
Expand Down
25 changes: 25 additions & 0 deletions Tests/CubeService_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,31 @@ def test_get_measure_dimension(self):

self.assertEqual(self.dimension_names[-1], measure_dimension)

def test_set_and_get_measure_dimension(self):
# assign a non-last dimension as the measures dimension
self.tm1.cubes.set_measure_dimension(self.cube_name, self.dimension_names[0])

measure_dimension = self.tm1.cubes.get_measure_dimension(self.cube_name)
self.assertEqual(self.dimension_names[0], measure_dimension)

def test_set_measure_dimension_invalid_dimension(self):
with self.assertRaises(ValueError):
self.tm1.cubes.set_measure_dimension(self.cube_name, "Not_A_Dimension_Of_The_Cube")

def test_get_time_dimension_not_set(self):
time_dimension = self.tm1.cubes.get_time_dimension(self.cube_name)
self.assertIsNone(time_dimension)

def test_set_and_get_time_dimension(self):
self.tm1.cubes.set_time_dimension(self.cube_name, self.dimension_names[1])

time_dimension = self.tm1.cubes.get_time_dimension(self.cube_name)
self.assertEqual(self.dimension_names[1], time_dimension)

def test_set_time_dimension_invalid_dimension(self):
with self.assertRaises(ValueError):
self.tm1.cubes.set_time_dimension(self.cube_name, "Not_A_Dimension_Of_The_Cube")

def tearDown(self):
self.tm1.cubes.delete(self.cube_name)
if self.tm1.cubes.exists(self.cube_name_to_delete):
Expand Down
Loading