Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename to clear_with_dataframe #1032

Merged
merged 2 commits into from
Jan 23, 2024
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
81 changes: 52 additions & 29 deletions TM1py/Services/CellService.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ def clear(self, cube: str, **kwargs):
@require_data_admin
@require_ops_admin
@require_version(version="11.7")
def clear_from_df(self, cube: str, df: pd.DataFrame, dimension_mapping: Dict = None, **kwargs):
def clear_with_dataframe(self, cube: str, df: 'pd.DataFrame', dimension_mapping: Dict = None, **kwargs):
"""Clears data from a TM1 cube based on the distinct values in a DataFrame over cube dimensions.
Note:
This function is similar to `tm1.cells.clear`, but it is designed specifically for clearing data
Expand All @@ -585,7 +585,7 @@ def clear_from_df(self, cube: str, df: pd.DataFrame, dimension_mapping: Dict = N
The DataFrame containing distinct values over cube dimensions.
Columns in the DataFrame should correspond to cube dimensions.
:param dimension_mapping: Dict, optional
A dictionary mapping DataFrame columns to specific hierarchies within TM1 dimensions.
A dictionary mapping the DataFrame columns to one or many hierarchies within the given dimension.
If not provided, assumes that the dimensions have just one hierarchy.

:return: None
Expand All @@ -610,54 +610,77 @@ def clear_from_df(self, cube: str, df: pd.DataFrame, dimension_mapping: Dict = N
"Organisation": "hierarchy_1",
"Location": ["hierarchy_2", "hierarchy_3", "hierarchy_4"]
}

dataframe = pd.DataFrame(data)

with TM1Service(**tm1params) as tm1:
tm1.cells.clear_with_dataframe(cube="Sales", df=dataframe)

```
"""

if not dimension_mapping:
dimension_mapping = {}

if len(CaseAndSpaceInsensitiveSet(df.columns)) != len(df.columns):
raise ValueError(f"Column names in DataFrame are not unique identifiers for TM1: {list(df.columns)}")

cube_service = self.get_cube_service()
dimension_names = CaseAndSpaceInsensitiveSet(*cube_service.get_dimension_names(cube_name=cube))

df = df.astype(str)

unique_entries = [{col_name: df[col_name].unique()} for col_name in df.columns]
elements_by_column = {col_name: df[col_name].unique() for col_name in df.columns}
MariusWirtz marked this conversation as resolved.
Show resolved Hide resolved

cell_coordinates = {}
mdx_selections = {}
unmatched_dimension_names = []
for entry in unique_entries:
coordinates_list = []
for key, value in entry.items():
if key not in dimension_names:
unmatched_dimension_names.append(key)
for i in value:
if key in dimension_mapping:
element_definition = Member.of(key, dimension_mapping[key], i)
coordinates_list.append(element_definition.unique_name)
else:
element_definition = Member.of(key, key, i)
coordinates_list.append(element_definition.unique_name)
cell_coordinates[key] = "{" + ",".join(coordinates_list) + "}"
for column, elements in elements_by_column.items():
members = []

if column not in dimension_names:
unmatched_dimension_names.append(column)

for element in elements:
if column in dimension_mapping:
hierarchy = dimension_mapping.get(column)
if not isinstance(hierarchy, str):
raise ValueError(f"Value for key '{dimension}' in dimension_mapping must be of type str")
members.append(Member.of(column, hierarchy, element))

else:
members.append(Member.of(column, column, element))
mdx_selections[column] = MdxHierarchySet.members(members)

if dimension_mapping:
for key, value in dimension_mapping.items():
if key not in dimension_names:
unmatched_dimension_names.append(key)
cell_coordinates[key] = MdxHierarchySet.tm1_subset_all(dimension=key,
hierarchy=value).filter_by_level(0).to_mdx()
for dimension, hierarchies in dimension_mapping.items():
if dimension not in dimension_names:
unmatched_dimension_names.append(dimension)

elif isinstance(hierarchies, str):
hierarchy = hierarchies
mdx_selections[dimension] = MdxHierarchySet.tm1_subset_all(
dimension=dimension,
hierarchy=hierarchy).filter_by_level(0)

elif isinstance(hierarchies, Iterable):
for hierarchy in hierarchies:
mdx_selections[dimension] = MdxHierarchySet.tm1_subset_all(
dimension=dimension,
hierarchy=hierarchy).filter_by_level(0)

else:
raise ValueError(f"Unexpected value type for key '{dimension}' in dimension_mapping")

if unmatched_dimension_names:
raise ValueError(f"Dimension(s) {unmatched_dimension_names} does not exist in cube {cube}."
f"\nCheck the source of the dataframe to fix the problem")

for dimension_name in dimension_names:
if dimension_name not in cell_coordinates:
expression = MdxHierarchySet.tm1_subset_all(dimension_name).filter_by_level(0).to_mdx()
cell_coordinates[dimension_name] = expression
if dimension_name not in mdx_selections:
mdx_selections[dimension_name] = MdxHierarchySet.tm1_subset_all(dimension_name).filter_by_level(0)

mdx_builder = MdxBuilder.from_cube(cube).columns_non_empty()
for dimension, expression in cell_coordinates.items():
hierarchy_set = MdxHierarchySet.from_str(dimension=dimension, hierarchy=dimension, mdx=expression)
mdx_builder.add_hierarchy_set_to_column_axis(hierarchy_set)
for dimension, expression in mdx_selections.items():
mdx_builder.add_hierarchy_set_to_column_axis(expression)

return self.clear_with_mdx(cube=cube, mdx=mdx_builder.to_mdx(), **kwargs)

Expand Down
3 changes: 2 additions & 1 deletion TM1py/Utils/Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from io import StringIO
from typing import Any, Dict, List, Tuple, Iterable, Optional, Generator, Union, Callable
from urllib.parse import unquote

import requests
from mdxpy import MdxBuilder, Member
from requests.adapters import HTTPAdapter
Expand Down Expand Up @@ -1319,4 +1320,4 @@ def __init__(self, *args, **kwargs):
def init_poolmanager(self, *args, **kwargs):
if self.socket_options is not None:
kwargs["socket_options"] = self.socket_options
super(HTTPAdapterWithSocketOptions, self).init_poolmanager(*args, **kwargs)
super(HTTPAdapterWithSocketOptions, self).init_poolmanager(*args, **kwargs)
52 changes: 52 additions & 0 deletions Tests/CellService_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4074,6 +4074,58 @@ def test_clear_with_mdx_unsupported_version(self):

self.tm1._tm1_rest.set_version()

@skip_if_insufficient_version(version="11.7")
def test_clear_with_dataframe_happy_case(self):
cells = {("Element17", "Element21", "Element15"): 1}
self.tm1.cells.write_values(self.cube_name, cells)

data = {
self.dimension_names[0]: ["Element17"],
self.dimension_names[1]: ["Element21"],
self.dimension_names[2]: ["Element15"],
}

self.tm1.cells.clear_with_dataframe(cube=self.cube_name, df=pd.DataFrame(data))

mdx = MdxBuilder.from_cube(self.cube_name) \
.add_hierarchy_set_to_row_axis(MdxHierarchySet.member(Member.of(self.dimension_names[0], "Element17"))) \
.add_hierarchy_set_to_column_axis(MdxHierarchySet.member(Member.of(self.dimension_names[1], "Element21"))) \
.where(Member.of(self.dimension_names[2], "Element15")) \
.to_mdx()

value = self.tm1.cells.execute_mdx_values(mdx=mdx)[0]
self.assertEqual(value, None)

@skip_if_insufficient_version(version="11.7")
def test_clear_with_dataframe_dimension_mapping(self):
cells = {("Element17", "Element21", "Element15"): 1}
self.tm1.cells.write_values(self.cube_name, cells)

data = {
self.dimension_names[0]: ["Element17"],
self.dimension_names[1]: ["Element21"],
self.dimension_names[2]: ["Element15"],
}

self.tm1.cells.clear_with_dataframe(
cube=self.cube_name,
df=pd.DataFrame(data),
dimension_mapping={
self.dimension_names[0]: self.dimension_names[0],
self.dimension_names[1]: self.dimension_names[1],
self.dimension_names[2]: self.dimension_names[2]
}
)

mdx = MdxBuilder.from_cube(self.cube_name) \
.add_hierarchy_set_to_row_axis(MdxHierarchySet.member(Member.of(self.dimension_names[0], "Element17"))) \
.add_hierarchy_set_to_column_axis(MdxHierarchySet.member(Member.of(self.dimension_names[1], "Element21"))) \
.where(Member.of(self.dimension_names[2], "Element15")) \
.to_mdx()

value = self.tm1.cells.execute_mdx_values(mdx=mdx)[0]
self.assertEqual(value, None)

def test_execute_mdx_with_skip(self):
mdx = MdxBuilder.from_cube(self.cube_name) \
.add_hierarchy_set_to_row_axis(MdxHierarchySet.tm1_subset_all(self.dimension_names[0]).head(2)) \
Expand Down