Skip to content

Commit 0f56230

Browse files
committed
Implement skip_invalid_edges argument
Fixes #1336
1 parent 975b67b commit 0f56230

2 files changed

Lines changed: 163 additions & 14 deletions

File tree

TM1py/Services/ElementService.py

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -136,32 +136,74 @@ def delete_edges(
136136
use_ti: bool = False,
137137
use_blob: bool = False,
138138
remove_blob: bool = True,
139+
skip_invalid_edges: bool = True,
139140
**kwargs,
140141
):
142+
"""
143+
Remove edges in TM1.
144+
145+
:param dimension_name: The name of the dimension.
146+
:param hierarchy_name: The name of the hierarchy.
147+
:param edges: A list of tuples representing the edges to remove, where each tuple contains a parent and a child.
148+
:param use_ti: A boolean indicating whether to use a TI process to delete edges (default: False).
149+
:param use_blob: A boolean indicating whether to use a blob file to delete edges (default: False).
150+
:param remove_blob: A boolean indicating whether to remove the parent-child file after use (default: True).
151+
:param skip_invalid_edges: A boolean indicating whether to skip invalid edges (default: True).
152+
"""
141153
if use_ti:
142-
return self.delete_edges_use_ti(dimension_name, hierarchy_name, edges, **kwargs)
154+
return self.delete_edges_use_ti(dimension_name, hierarchy_name, edges, skip_invalid_edges, **kwargs)
143155

144156
if use_blob:
145-
return self.delete_edges_use_blob(dimension_name, hierarchy_name, edges, remove_blob, **kwargs)
157+
return self.delete_edges_use_blob(
158+
dimension_name, hierarchy_name, edges, remove_blob, skip_invalid_edges, **kwargs
159+
)
146160

147161
h_service = self._get_hierarchy_service()
148162
h = h_service.get(dimension_name, hierarchy_name, **kwargs)
149163
for edge in edges:
164+
if edge not in h.edges and not skip_invalid_edges:
165+
raise TM1pyException(f"Edge {edge} does not exist in hierarchy [{dimension_name}].[{hierarchy_name}]")
150166
h.remove_edge(parent=edge[0], component=edge[1])
151167
h_service.update(h, **kwargs)
152168

153-
def delete_edges_use_ti(self, dimension_name: str, hierarchy_name: str, edges: List[str] = None, **kwargs):
169+
def delete_edges_use_ti(
170+
self,
171+
dimension_name: str,
172+
hierarchy_name: str,
173+
edges: List[str] = None,
174+
skip_invalid_edges: bool = True,
175+
**kwargs,
176+
):
177+
"""
178+
Remove edges in TM1 via an unbound TI process.
179+
:param dimension_name: The name of the dimension.
180+
:param hierarchy_name: The name of the hierarchy.
181+
:param edges: A list of tuples representing the edges to remove, where each tuple contains a parent and a child.
182+
:param skip_invalid_edges: A boolean indicating whether to skip invalid edges (default: True).
183+
"""
154184
if not edges:
155185
return
156186

157187
def escape_single_quote(text):
158188
return text.replace("'", "''")
159189

160-
statements = [
161-
f"HierarchyElementComponentDelete('{dimension_name}', '{hierarchy_name}', "
162-
f"'{escape_single_quote(parent)}', '{escape_single_quote(child)}');"
163-
for (parent, child) in edges
164-
]
190+
statements = []
191+
for parent, child in edges:
192+
parent = escape_single_quote(parent)
193+
child = escape_single_quote(child)
194+
if skip_invalid_edges:
195+
statements.extend(
196+
[
197+
f"IF(ElementIsParent('{dimension_name}','{hierarchy_name}','{parent}','{child}')=1);",
198+
f"HierarchyElementComponentDelete('{dimension_name}','{hierarchy_name}','{parent}','{child}');",
199+
f"ENDIF;",
200+
]
201+
)
202+
203+
else:
204+
statements.append(
205+
f"HierarchyElementComponentDelete('{dimension_name}', '{hierarchy_name}', '{parent}', '{child}');"
206+
)
165207

166208
unbound_process_name = self.suggest_unique_object_name()
167209

@@ -174,7 +216,13 @@ def escape_single_quote(text):
174216
@require_data_admin
175217
@require_ops_admin
176218
def delete_edges_use_blob(
177-
self, dimension_name: str, hierarchy_name: str, edges: List[str] = None, remove_blob: bool = True, **kwargs
219+
self,
220+
dimension_name: str,
221+
hierarchy_name: str,
222+
edges: List[str] = None,
223+
remove_blob: bool = True,
224+
skip_invalid_edges: bool = True,
225+
**kwargs,
178226
):
179227
"""
180228
Remove edges in TM1 via an unbound TI process having an uploaded CSV as the data source.
@@ -183,6 +231,7 @@ def delete_edges_use_blob(
183231
:param hierarchy_name: The name of the hierarchy.
184232
:param edges: A list of tuples representing the edges to remove, where each tuple contains a parent and a child.
185233
:param remove_blob: A boolean indicating whether to remove the parent-child file after use (default: True).
234+
:param skip_invalid_edges: A boolean indicating whether to skip invalid edges (default: True).
186235
:param kwargs: Additional arguments for the process execution.
187236
:return: None
188237
"""
@@ -209,6 +258,7 @@ def delete_edges_use_blob(
209258
hierarchy_name=hierarchy_name,
210259
process_name=unique_name,
211260
blob_filename=file_name,
261+
skip_invalid_edges=skip_invalid_edges,
212262
)
213263

214264
success, status, log_file = process_service.execute_process_with_return(process=process, **kwargs)
@@ -223,7 +273,12 @@ def delete_edges_use_blob(
223273
file_service.delete(file_name=file_name)
224274

225275
def _build_unwind_hierarchy_edges_from_blob_process(
226-
self, dimension_name: str, hierarchy_name: str, process_name: str, blob_filename: str
276+
self,
277+
dimension_name: str,
278+
hierarchy_name: str,
279+
process_name: str,
280+
blob_filename: str,
281+
skip_invalid_edges: bool = True,
227282
) -> Process:
228283

229284
# v11 automatically adds blb file extensions to documents created via the contents api
@@ -251,7 +306,15 @@ def _build_unwind_hierarchy_edges_from_blob_process(
251306
hierarchyupdate_process.add_variable(name=child_variable, variable_type="String")
252307

253308
# Write the statement for delete component in hierarchy
254-
delete_component = f"\rHierarchyElementComponentDelete('{dimension_name}', '{hierarchy_name}', {parent_variable}, {child_variable});"
309+
if skip_invalid_edges:
310+
delete_component = (
311+
f"\r"
312+
f"IF(ElementIsParent('{dimension_name}','{hierarchy_name}',{parent_variable},{child_variable})=1);"
313+
f"HierarchyElementComponentDelete('{dimension_name}','{hierarchy_name}',{parent_variable},{child_variable});"
314+
f"ENDIF;"
315+
)
316+
else:
317+
delete_component = f"HierarchyElementComponentDelete('{dimension_name}','{hierarchy_name}',{parent_variable},{child_variable});"
255318

256319
# Define Metadata section
257320
metadata_statement = delete_component

Tests/ElementService_test.py

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55

66
from mdxpy import MdxBuilder
77

8+
from TM1py.Exceptions import TM1pyException, TM1pyRestException, TM1pyWritePartialFailureException
9+
from TM1py.Objects import Dimension, Element, ElementAttribute, Hierarchy
10+
from TM1py.Services import TM1Service
811
from Tests.Utils import (
912
generate_test_uuid,
1013
skip_if_no_pandas,
1114
skip_if_version_lower_than,
1215
)
13-
from TM1py.Exceptions import TM1pyException, TM1pyRestException
14-
from TM1py.Objects import Dimension, Element, ElementAttribute, Hierarchy
15-
from TM1py.Services import TM1Service
1616

1717

1818
class TestElementService(unittest.TestCase):
@@ -1287,6 +1287,92 @@ def test_delete_edges(self):
12871287
self.assertNotIn(("Total Years", "1989"), edges)
12881288
self.assertNotIn(("Total Years", "1990"), edges)
12891289

1290+
@skip_if_version_lower_than(version="11.4")
1291+
def test_delete_edges_use_ti_skip_invalid_edges_true(self):
1292+
self.tm1.elements.delete_edges(
1293+
dimension_name=self.dimension_name,
1294+
hierarchy_name=self.hierarchy_name,
1295+
edges=[("Every Year", "1989"), ("Total Years", "1989"), ("Total Years", "1990")],
1296+
skip_invalid_edges=True,
1297+
)
1298+
1299+
edges = self.tm1.elements.get_edges(self.dimension_name, self.dimension_name)
1300+
self.assertNotIn(("Total Years", "1989"), edges)
1301+
self.assertNotIn(("Total Years", "1990"), edges)
1302+
1303+
@skip_if_version_lower_than(version="11.4")
1304+
def test_delete_edges_skip_invalid_edges_false(self):
1305+
with self.assertRaises(TM1pyException):
1306+
self.tm1.elements.delete_edges(
1307+
dimension_name=self.dimension_name,
1308+
hierarchy_name=self.hierarchy_name,
1309+
edges=[("Every Year", "1989")],
1310+
skip_invalid_edges=False,
1311+
)
1312+
1313+
@skip_if_version_lower_than(version="11.4")
1314+
def test_delete_edges_use_blob(self):
1315+
self.tm1.elements.delete_edges(
1316+
dimension_name=self.dimension_name,
1317+
hierarchy_name=self.hierarchy_name,
1318+
edges=[("Total Years", "1989"), ("Total Years", "1990")],
1319+
use_blob=True,
1320+
)
1321+
1322+
edges = self.tm1.elements.get_edges(self.dimension_name, self.dimension_name)
1323+
self.assertNotIn(("Total Years", "1989"), edges)
1324+
self.assertNotIn(("Total Years", "1990"), edges)
1325+
1326+
@skip_if_version_lower_than(version="11.4")
1327+
def test_delete_edges_use_blob_skip_invalid_edges_true(self):
1328+
self.tm1.elements.delete_edges(
1329+
dimension_name=self.dimension_name,
1330+
hierarchy_name=self.hierarchy_name,
1331+
edges=[("Every Year", "1989"), ("Total Years", "1989"), ("Total Years", "1990")],
1332+
use_blob=True,
1333+
skip_invalid_edges=True,
1334+
)
1335+
1336+
edges = self.tm1.elements.get_edges(self.dimension_name, self.dimension_name)
1337+
self.assertNotIn(("Total Years", "1989"), edges)
1338+
self.assertNotIn(("Total Years", "1990"), edges)
1339+
1340+
@skip_if_version_lower_than(version="11.4")
1341+
def test_delete_edges_use_blob_skip_invalid_edges_false(self):
1342+
with self.assertRaises(TM1pyWritePartialFailureException):
1343+
self.tm1.elements.delete_edges(
1344+
dimension_name=self.dimension_name,
1345+
hierarchy_name=self.hierarchy_name,
1346+
edges=[("Every Year", "1989")],
1347+
use_blob=True,
1348+
skip_invalid_edges=False,
1349+
)
1350+
1351+
@skip_if_version_lower_than(version="11.4")
1352+
def test_delete_edges_use_ti_skip_invalid_edges_true(self):
1353+
self.tm1.elements.delete_edges(
1354+
dimension_name=self.dimension_name,
1355+
hierarchy_name=self.hierarchy_name,
1356+
edges=[("Every Year", "1989"), ("Total Years", "1989"), ("Total Years", "1990")],
1357+
use_ti=True,
1358+
skip_invalid_edges=True,
1359+
)
1360+
1361+
edges = self.tm1.elements.get_edges(self.dimension_name, self.dimension_name)
1362+
self.assertNotIn(("Total Years", "1989"), edges)
1363+
self.assertNotIn(("Total Years", "1990"), edges)
1364+
1365+
@skip_if_version_lower_than(version="11.4")
1366+
def test_delete_edges_use_ti_skip_invalid_edges_false(self):
1367+
with self.assertRaises(TM1pyException):
1368+
self.tm1.elements.delete_edges(
1369+
dimension_name=self.dimension_name,
1370+
hierarchy_name=self.hierarchy_name,
1371+
edges=[("Every Year", "1989")],
1372+
use_ti=True,
1373+
skip_invalid_edges=False,
1374+
)
1375+
12901376
def test_remove_edge_parent_not_existing(self):
12911377
with self.assertRaises(TM1pyRestException):
12921378
self.tm1.elements.remove_edge(

0 commit comments

Comments
 (0)