semantic-link-labs 0.8.6__py3-none-any.whl → 0.8.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of semantic-link-labs might be problematic. Click here for more details.

Files changed (34) hide show
  1. {semantic_link_labs-0.8.6.dist-info → semantic_link_labs-0.8.7.dist-info}/METADATA +14 -6
  2. {semantic_link_labs-0.8.6.dist-info → semantic_link_labs-0.8.7.dist-info}/RECORD +34 -28
  3. {semantic_link_labs-0.8.6.dist-info → semantic_link_labs-0.8.7.dist-info}/WHEEL +1 -1
  4. sempy_labs/__init__.py +37 -6
  5. sempy_labs/_authentication.py +108 -0
  6. sempy_labs/_connections.py +355 -176
  7. sempy_labs/_dataflows.py +0 -1
  8. sempy_labs/_gateways.py +439 -0
  9. sempy_labs/_generate_semantic_model.py +51 -30
  10. sempy_labs/_git.py +13 -5
  11. sempy_labs/_helper_functions.py +14 -3
  12. sempy_labs/_list_functions.py +1 -1
  13. sempy_labs/_model_auto_build.py +4 -2
  14. sempy_labs/_model_bpa.py +2 -15
  15. sempy_labs/_model_bpa_bulk.py +4 -2
  16. sempy_labs/_refresh_semantic_model.py +6 -0
  17. sempy_labs/admin/__init__.py +19 -9
  18. sempy_labs/admin/_basic_functions.py +475 -548
  19. sempy_labs/admin/_external_data_share.py +97 -0
  20. sempy_labs/admin/_git.py +69 -0
  21. sempy_labs/admin/_items.py +264 -0
  22. sempy_labs/admin/_scanner.py +104 -0
  23. sempy_labs/directlake/_dl_helper.py +6 -2
  24. sempy_labs/directlake/_get_shared_expression.py +5 -35
  25. sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +3 -2
  26. sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +4 -2
  27. sempy_labs/report/_generate_report.py +10 -4
  28. sempy_labs/report/_report_bpa.py +1 -0
  29. sempy_labs/report/_report_helper.py +58 -0
  30. sempy_labs/report/_report_list_functions.py +2 -0
  31. sempy_labs/report/_reportwrapper.py +358 -175
  32. sempy_labs/tom/_model.py +1 -0
  33. {semantic_link_labs-0.8.6.dist-info → semantic_link_labs-0.8.7.dist-info}/LICENSE +0 -0
  34. {semantic_link_labs-0.8.6.dist-info → semantic_link_labs-0.8.7.dist-info}/top_level.txt +0 -0
@@ -7,11 +7,10 @@ from sempy_labs._helper_functions import (
7
7
  _extract_json,
8
8
  _add_part,
9
9
  lro,
10
- # _make_clickable,
10
+ _decode_b64,
11
11
  )
12
12
  from typing import Optional, List
13
13
  import pandas as pd
14
- import re
15
14
  import json
16
15
  import base64
17
16
  from uuid import UUID
@@ -24,6 +23,25 @@ import warnings
24
23
 
25
24
 
26
25
  class ReportWrapper:
26
+ """
27
+ Connects to a Power BI report and retrieves its definition.
28
+
29
+ The ReportWrapper and all functions which depend on it require the report to be in the `PBIR <https://powerbi.microsoft.com/blog/power-bi-enhanced-report-format-pbir-in-power-bi-desktop-developer-mode-preview>`_ format.
30
+
31
+ Parameters
32
+ ----------
33
+ report : str
34
+ The name of the report.
35
+ workspace : str
36
+ The name of the workspace in which the report resides.
37
+ Defaults to None which resolves to the workspace of the attached lakehouse
38
+ or if no lakehouse attached, resolves to the workspace of the notebook.
39
+
40
+ Returns
41
+ -------
42
+ pandas.DataFrame
43
+ A pandas dataframe containing the report metadata definition files.
44
+ """
27
45
 
28
46
  _report: str
29
47
  _workspace: str
@@ -34,6 +52,25 @@ class ReportWrapper:
34
52
  report: str,
35
53
  workspace: Optional[str] = None,
36
54
  ):
55
+ """
56
+ Connects to a Power BI report and retrieves its definition.
57
+
58
+ The ReportWrapper and all functions which depend on it require the report to be in the `PBIR <https://powerbi.microsoft.com/blog/power-bi-enhanced-report-format-pbir-in-power-bi-desktop-developer-mode-preview>`_ format.
59
+
60
+ Parameters
61
+ ----------
62
+ report : str
63
+ The name of the report.
64
+ workspace : str
65
+ The name of the workspace in which the report resides.
66
+ Defaults to None which resolves to the workspace of the attached lakehouse
67
+ or if no lakehouse attached, resolves to the workspace of the notebook.
68
+
69
+ Returns
70
+ -------
71
+ pandas.DataFrame
72
+ A pandas dataframe containing the report metadata definition files.
73
+ """
37
74
 
38
75
  from sempy_labs.report import get_report_definition
39
76
 
@@ -85,6 +122,22 @@ class ReportWrapper:
85
122
  )
86
123
  return dataframe
87
124
 
125
+ def _update_single_file(self, file_name: str, new_payload):
126
+ """
127
+ Updates a single file within the PBIR structure
128
+ """
129
+
130
+ request_body = {"definition": {"parts": []}}
131
+ for _, r in self.rdef.iterrows():
132
+ path = r["path"]
133
+ payload = r["payload"]
134
+ if path == file_name:
135
+ _add_part(request_body, path=path, payload=new_payload)
136
+ else:
137
+ _add_part(request_body, path=path, payload=payload)
138
+
139
+ self.update_report(request_body)
140
+
88
141
  def update_report(self, request_body: dict):
89
142
 
90
143
  client = fabric.FabricRestClient()
@@ -692,6 +745,7 @@ class ReportWrapper:
692
745
  custom_visuals = rptJson.get("publicCustomVisuals", [])
693
746
  page_mapping, visual_mapping = helper.visual_page_mapping(self)
694
747
  helper.populate_custom_visual_display_names()
748
+ agg_type_map = helper._get_agg_type_mapping()
695
749
 
696
750
  def contains_key(data, keys_to_check):
697
751
  matches = parse("$..*").find(data)
@@ -738,21 +792,36 @@ class ReportWrapper:
738
792
 
739
793
  # Title
740
794
  matches = parse(
741
- "$.visual.visualContainerObjects.title[0].properties.text.expr.Literal.Value"
795
+ "$.visual.visualContainerObjects.title[0].properties.text.expr"
742
796
  ).find(visual_json)
743
- title = matches[0].value[1:-1] if matches else ""
797
+ # title = matches[0].value[1:-1] if matches else ""
798
+ title = (
799
+ helper._get_expression(matches[0].value, agg_type_map)
800
+ if matches
801
+ else ""
802
+ )
744
803
 
745
804
  # SubTitle
746
805
  matches = parse(
747
- "$.visual.visualContainerObjects.subTitle[0].properties.text.expr.Literal.Value"
806
+ "$.visual.visualContainerObjects.subTitle[0].properties.text.expr"
748
807
  ).find(visual_json)
749
- sub_title = matches[0].value[1:-1] if matches else ""
808
+ # sub_title = matches[0].value[1:-1] if matches else ""
809
+ sub_title = (
810
+ helper._get_expression(matches[0].value, agg_type_map)
811
+ if matches
812
+ else ""
813
+ )
750
814
 
751
815
  # Alt Text
752
816
  matches = parse(
753
- "$.visual.visualContainerObjects.general[0].properties.altText.expr.Literal.Value"
817
+ "$.visual.visualContainerObjects.general[0].properties.altText.expr"
754
818
  ).find(visual_json)
755
- alt_text = matches[0].value[1:-1] if matches else ""
819
+ # alt_text = matches[0].value[1:-1] if matches else ""
820
+ alt_text = (
821
+ helper._get_expression(matches[0].value, agg_type_map)
822
+ if matches
823
+ else ""
824
+ )
756
825
 
757
826
  # Show items with no data
758
827
  def find_show_all_with_jsonpath(obj):
@@ -1296,6 +1365,71 @@ class ReportWrapper:
1296
1365
 
1297
1366
  return df
1298
1367
 
1368
+ def _list_annotations(self) -> pd.DataFrame:
1369
+ """
1370
+ Shows a list of annotations in the report.
1371
+
1372
+ Returns
1373
+ -------
1374
+ pandas.DataFrame
1375
+ A pandas dataframe showing a list of report, page and visual annotations in the report.
1376
+ """
1377
+
1378
+ df = pd.DataFrame(
1379
+ columns=["Type", "Object Name", "Annotation Name", "Annotation Value"]
1380
+ )
1381
+
1382
+ page_mapping, visual_mapping = helper.visual_page_mapping(self)
1383
+ for _, r in self.rdef.iterrows():
1384
+ payload = r["payload"]
1385
+ path = r["path"]
1386
+ if path == "definition/report.json":
1387
+ file = _decode_b64(payload)
1388
+ json_file = json.loads(file)
1389
+ if "annotations" in json_file:
1390
+ for ann in json_file["annotations"]:
1391
+ new_data = {
1392
+ "Type": "Report",
1393
+ "Object Name": self._report,
1394
+ "Annotation Name": ann.get("name"),
1395
+ "Annotation Value": ann.get("value"),
1396
+ }
1397
+ df = pd.concat(
1398
+ [df, pd.DataFrame(new_data, index=[0])], ignore_index=True
1399
+ )
1400
+ elif path.endswith("/page.json"):
1401
+ file = _decode_b64(payload)
1402
+ json_file = json.loads(file)
1403
+ if "annotations" in json_file:
1404
+ for ann in json_file["annotations"]:
1405
+ new_data = {
1406
+ "Type": "Page",
1407
+ "Object Name": json_file.get("displayName"),
1408
+ "Annotation Name": ann.get("name"),
1409
+ "Annotation Value": ann.get("value"),
1410
+ }
1411
+ df = pd.concat(
1412
+ [df, pd.DataFrame(new_data, index=[0])], ignore_index=True
1413
+ )
1414
+ elif path.endswith("/visual.json"):
1415
+ file = _decode_b64(payload)
1416
+ json_file = json.loads(file)
1417
+ page_display = visual_mapping.get(path)[1]
1418
+ visual_name = json_file.get("name")
1419
+ if "annotations" in json_file:
1420
+ for ann in json_file["annotations"]:
1421
+ new_data = {
1422
+ "Type": "Visual",
1423
+ "Object Name": f"'{page_display}'[{visual_name}]",
1424
+ "Annotation Name": ann.get("name"),
1425
+ "Annotation Value": ann.get("value"),
1426
+ }
1427
+ df = pd.concat(
1428
+ [df, pd.DataFrame(new_data, index=[0])], ignore_index=True
1429
+ )
1430
+
1431
+ return df
1432
+
1299
1433
  # Automation functions
1300
1434
  def set_theme(self, theme_file_path: str):
1301
1435
  """
@@ -1414,41 +1548,40 @@ class ReportWrapper:
1414
1548
  """
1415
1549
 
1416
1550
  pages_file = "definition/pages/pages.json"
1417
- request_body = {"definition": {"parts": []}}
1418
-
1419
- rd = self.rdef
1420
- page_id, page_display, file_path = helper.resolve_page_name(
1551
+ page_id, page_display_name, file_path = helper.resolve_page_name(
1421
1552
  self, page_name=page_name
1422
1553
  )
1423
- for _, r in rd.iterrows():
1424
- path = r["path"]
1425
- file_payload = r["payload"]
1426
- if path != pages_file:
1427
- _add_part(request_body, path, file_payload)
1428
1554
 
1429
- pagePath = rd[rd["path"] == pages_file]
1555
+ pagePath = self.rdef[self.rdef["path"] == pages_file]
1430
1556
  payload = pagePath["payload"].iloc[0]
1431
- pageFile = base64.b64decode(payload).decode("utf-8")
1432
- pageJson = json.loads(pageFile)
1433
- pageJson["activePageName"] = page_id
1434
- file_payload = _conv_b64(pageJson)
1557
+ page_file = _decode_b64(payload)
1558
+ json_file = json.loads(page_file)
1559
+ json_file["activePageName"] = page_id
1560
+ file_payload = _conv_b64(json_file)
1435
1561
 
1436
- _add_part(request_body, pages_file, file_payload)
1562
+ self._update_single_file(file_name=pages_file, new_payload=file_payload)
1437
1563
 
1438
- self.update_report(request_body=request_body)
1439
1564
  print(
1440
- f"{icons.green_dot} The '{page_name}' page has been set as the active page in the '{self._report}' report within the '{self._workspace}' workspace."
1565
+ f"{icons.green_dot} The '{page_display_name}' page has been set as the active page in the '{self._report}' report within the '{self._workspace}' workspace."
1441
1566
  )
1442
1567
 
1443
1568
  def set_page_type(self, page_name: str, page_type: str):
1569
+ """
1570
+ Changes the page type of a report page.
1571
+
1572
+ Parameters
1573
+ ----------
1574
+ page_name : str
1575
+ Name or display name of the report page.
1576
+ page_type : str
1577
+ The page type. Valid page types: 'Tooltip', 'Letter', '4:3', '16:9'.
1578
+ """
1444
1579
 
1445
1580
  if page_type not in helper.page_types:
1446
1581
  raise ValueError(
1447
1582
  f"{icons.red_dot} Invalid page type. Valid options: {helper.page_types}."
1448
1583
  )
1449
1584
 
1450
- request_body = {"definition": {"parts": []}}
1451
-
1452
1585
  letter_key = next(
1453
1586
  (
1454
1587
  key
@@ -1461,30 +1594,24 @@ class ReportWrapper:
1461
1594
  width, height = letter_key
1462
1595
  else:
1463
1596
  raise ValueError(
1464
- "Invalid page_type parameter. Valid options: ['Tooltip', 'Letter', '4:3', '16:9']."
1597
+ f"{icons.red_dot} Invalid page_type parameter. Valid options: ['Tooltip', 'Letter', '4:3', '16:9']."
1465
1598
  )
1466
1599
 
1467
- rd = self.rdef
1468
- page_id, page_display, file_path = helper.resolve_page_name(
1600
+ page_id, page_display_name, file_path = helper.resolve_page_name(
1469
1601
  self, page_name=page_name
1470
1602
  )
1471
- rd_filt = rd[rd["path"] == file_path]
1603
+ rd_filt = self.rdef[self.rdef["path"] == file_path]
1472
1604
  payload = rd_filt["payload"].iloc[0]
1473
- pageFile = base64.b64decode(payload).decode("utf-8")
1474
- pageJson = json.loads(pageFile)
1475
- pageJson["width"] = width
1476
- pageJson["height"] = height
1477
-
1478
- file_payload = _conv_b64(pageJson)
1479
- _add_part(request_body, file_path, file_payload)
1605
+ page_file = _decode_b64(payload)
1606
+ json_file = json.loads(page_file)
1607
+ json_file["width"] = width
1608
+ json_file["height"] = height
1609
+ file_payload = _conv_b64(json_file)
1480
1610
 
1481
- for _, r in rd.iterrows():
1482
- if r["path"] != file_path:
1483
- _add_part(request_body, r["path"], r["payload"])
1611
+ self._update_single_file(file_name=file_path, new_payload=file_payload)
1484
1612
 
1485
- self.update_report(request_body=request_body)
1486
1613
  print(
1487
- f"The '{page_display}' page has been updated to the '{page_type}' page type."
1614
+ f"{icons.green_dot} The '{page_display_name}' page has been updated to the '{page_type}' page type."
1488
1615
  )
1489
1616
 
1490
1617
  def remove_unnecessary_custom_visuals(self):
@@ -1632,31 +1759,27 @@ class ReportWrapper:
1632
1759
  If set to False, makes the report page visible.
1633
1760
  """
1634
1761
 
1635
- rd = self.rdef
1636
- page_id, page_display, file_path = helper.resolve_page_name(
1762
+ page_id, page_display_name, file_path = helper.resolve_page_name(
1637
1763
  self, page_name=page_name
1638
1764
  )
1639
1765
  visibility = "visible" if hidden is False else "hidden"
1640
1766
 
1641
- request_body = {"definition": {"parts": []}}
1767
+ rd_filt = self.rdef[self.rdef["path"] == file_path]
1768
+ payload = rd_filt["payload"].iloc[0]
1769
+ obj_file = _decode_b64(payload)
1770
+ obj_json = json.loads(obj_file)
1771
+ if hidden:
1772
+ obj_json["visibility"] = "HiddenInViewMode"
1773
+ else:
1774
+ if "visibility" in obj_json:
1775
+ del obj_json["visibility"]
1776
+ new_payload = _conv_b64(obj_json)
1642
1777
 
1643
- for _, r in rd.iterrows():
1644
- path = r["path"]
1645
- payload = r["payload"]
1646
- if path == file_path:
1647
- obj_file = base64.b64decode(payload).decode("utf-8")
1648
- obj_json = json.loads(obj_file)
1649
- if hidden:
1650
- obj_json["visibility"] = "HiddenInViewMode"
1651
- else:
1652
- if "visibility" in obj_json:
1653
- del obj_json["visibility"]
1654
- _add_part(request_body, path, _conv_b64(obj_json))
1655
- else:
1656
- _add_part(request_body, path, payload)
1778
+ self._update_single_file(file_name=file_path, new_payload=new_payload)
1657
1779
 
1658
- self.update_report(request_body=request_body)
1659
- print(f"{icons.green_dot} The '{page_name}' page has been set to {visibility}.")
1780
+ print(
1781
+ f"{icons.green_dot} The '{page_display_name}' page has been set to {visibility}."
1782
+ )
1660
1783
 
1661
1784
  def hide_tooltip_drillthrough_pages(self):
1662
1785
  """
@@ -1712,142 +1835,202 @@ class ReportWrapper:
1712
1835
  f"{icons.green_dot} Show items with data has been disabled for all visuals in the '{self._report}' report within the '{self._workspace}' workspace."
1713
1836
  )
1714
1837
 
1715
- def __get_annotation_value(self, object_name: str, object_type: str, name: str):
1838
+ # Set Annotations
1839
+ def __set_annotation(self, json_file: dict, name: str, value: str) -> dict:
1716
1840
 
1717
- object_types = ["Visual", "Page", "Report"]
1718
- object_type = object_type.capitalize()
1719
- if object_type not in object_types:
1720
- raise ValueError(
1721
- f"{icons.red_dot} Invalid object type. Valid options: {object_types}."
1722
- )
1841
+ if "annotations" in json_file:
1842
+ if any(
1843
+ annotation["name"] == name for annotation in json_file["annotations"]
1844
+ ):
1845
+ for annotation in json_file["annotations"]:
1846
+ if annotation["name"] == name:
1847
+ annotation["value"] = value
1848
+ break
1849
+ else:
1850
+ json_file["annotations"].append({"name": name, "value": value})
1851
+ else:
1852
+ json_file["annotations"] = []
1853
+ json_file["annotations"].append({"name": name, "value": value})
1723
1854
 
1724
- rd = self.rdef
1855
+ return json_file
1856
+
1857
+ def _set_annotation(
1858
+ self,
1859
+ annotation_name: str,
1860
+ annotation_value: str,
1861
+ page_name: Optional[str] = None,
1862
+ visual_name: Optional[str] = None,
1863
+ ):
1864
+ """
1865
+ Sets an annotation on the report/page/visual. If the annotation already exists, the annotation value is updated.
1866
+ In order to set a report annotation, leave page_name=None, visual_name=None.
1867
+ In order to set a page annotation, leave visual_annotation=None.
1868
+ In order to set a visual annotation, set all parameters.
1725
1869
 
1726
- if object_type == "Report":
1727
- rd_filt = rd[rd["path"] == "definition/report.json"]
1728
- obj_json = _extract_json(rd_filt)
1729
- elif object_type == "Page":
1730
- page_id, page_display, page_file = helper.resolve_page_name(
1731
- self, page_name=object_name
1870
+ Parameters
1871
+ ----------
1872
+ annotation_name : str
1873
+ Name of the annotation.
1874
+ annotation_value : str
1875
+ Value of the annotation.
1876
+ page_name : str, default=None
1877
+ The page name or page display name.
1878
+ Set this annotation when setting an annotation on a page or visual.
1879
+ visual_name : str, default=None
1880
+ The visual name.
1881
+ Set this property when setting an annotation on a visual.
1882
+ """
1883
+
1884
+ if page_name is None and visual_name is None:
1885
+ file_path = "definition/report.json"
1886
+ elif page_name is not None and visual_name is None:
1887
+ page_id, page_display, file_path = helper.resolve_page_name(
1888
+ self, page_name=page_name
1732
1889
  )
1733
- rd_filt = rd[rd["path"] == page_file]
1734
- payload = rd_filt["payload"].iloc[0]
1735
- obj_file = base64.b64decode(payload).decode("utf-8")
1736
- obj_json = json.loads(obj_file)
1737
- elif object_type == "Visual":
1738
- pattern = r"'([^']+)'\[([^]]+)\]"
1739
- match = re.search(pattern, object_name)
1740
- if match:
1741
- p_name = match.group(1)
1742
- v_name = match.group(2)
1743
- else:
1744
- raise ValueError(
1745
- "Invalid page/visual name within the 'object_name' parameter. Valid format: 'Page 1'[f8dvo24PdJ39fp6]"
1890
+ elif page_name is not None and visual_name is not None:
1891
+ page_name, page_display_name, visual_name, file_path = (
1892
+ helper.resolve_visual_name(
1893
+ self, page_name=page_name, visual_name=visual_name
1746
1894
  )
1747
- valid_page_name, valid_display_name, visual_name, file_path = (
1748
- helper.resolve_visual_name(self, page_name=p_name, visual_name=v_name)
1749
1895
  )
1750
- rd_filt = rd[rd["path"] == file_path]
1751
- payload = rd_filt["payload"].iloc[0]
1752
- obj_file = base64.b64decode(payload).decode("utf-8")
1753
- obj_json = json.loads(obj_file)
1896
+ else:
1897
+ raise ValueError(
1898
+ f"{icons.red_dot} Invalid parameters. If specifying a visual_name you must specify the page_name."
1899
+ )
1754
1900
 
1755
- value = obj_json.get("annotations", {}).get(name, "")
1901
+ payload = self.rdef[self.rdef["path"] == file_path]["payload"].iloc[0]
1902
+ file = _decode_b64(payload)
1903
+ json_file = json.loads(file)
1756
1904
 
1757
- return value
1905
+ new_file = self.__set_annotation(
1906
+ json_file, name=annotation_name, value=annotation_value
1907
+ )
1908
+ new_payload = _conv_b64(new_file)
1758
1909
 
1759
- def __remove_annotation(self, object_name: str, object_type: str, name: str):
1910
+ self._update_single_file(file_name=file_path, new_payload=new_payload)
1760
1911
 
1761
- object_types = ["Visual", "Page", "Report"]
1762
- object_type = object_type.capitalize()
1763
- if object_type not in object_types:
1764
- raise ValueError(
1765
- f"{icons.red_dot} Invalid object type. Valid options: {object_types}."
1766
- )
1912
+ # Remove Annotations
1913
+ def __remove_annotation(self, json_file: dict, name: str) -> dict:
1767
1914
 
1768
- def __set_annotation(
1769
- self, object_name: str, object_type: str, name: str, value: str
1770
- ):
1915
+ if "annotations" in json_file:
1916
+ json_file["annotations"] = [
1917
+ annotation
1918
+ for annotation in json_file["annotations"]
1919
+ if annotation["name"] != name
1920
+ ]
1771
1921
 
1772
- object_types = ["Visual", "Page", "Report"]
1773
- object_type = object_type.capitalize()
1774
- if object_type not in object_types:
1775
- raise ValueError(
1776
- f"{icons.red_dot} Invalid object type. Valid options: {object_types}."
1777
- )
1922
+ return json_file
1778
1923
 
1779
- request_body = {"definition": {"parts": []}}
1780
- new_annotation = {"name": name, "value": value}
1781
-
1782
- # Creates the annotation if it does not exist. Updates the annotation value if the annotation already exists
1783
- def update_annotation(payload):
1784
- objFile = base64.b64decode(payload).decode("utf-8")
1785
- objJson = json.loads(objFile)
1786
- if "annotations" not in objJson:
1787
- objJson["annotations"] = [new_annotation]
1788
- else:
1789
- names = []
1790
- for ann in objJson["annotations"]:
1791
- names.append(ann["name"])
1792
- if name not in names:
1793
- objJson["annotations"].append(new_annotation)
1794
- else:
1795
- for ann in objJson["annotations"]:
1796
- if ann["name"] == name:
1797
- ann["value"] = value
1798
- return objJson
1924
+ def _remove_annotation(
1925
+ self,
1926
+ annotation_name: str,
1927
+ page_name: Optional[str] = None,
1928
+ visual_name: Optional[str] = None,
1929
+ ):
1930
+ """
1931
+ Removes an annotation on the report/page/visual.
1932
+ In order to remove a report annotation, leave page_name=None, visual_name=None.
1933
+ In order to remove a page annotation, leave visual_annotation=None.
1934
+ In order to remove a visual annotation, set all parameters.
1799
1935
 
1800
- # Validate page and visual names
1801
- if object_type == "Page":
1802
- page_id, page_display, file_path = helper.resolve_page_name(
1803
- self, page_name=object_name
1804
- )
1805
- elif object_type == "Visual":
1806
- pattern = r"'(.*?)'|\[(.*?)\]"
1807
- matches = re.findall(pattern, object_name)
1808
- page_name = matches[0][0]
1809
- visual_id = matches[1][1]
1936
+ Parameters
1937
+ ----------
1938
+ annotation_name : str
1939
+ Name of the annotation.
1940
+ page_name : str, default=None
1941
+ The page name or page display name.
1942
+ Set this annotation when setting an annotation on a page or visual.
1943
+ visual_name : str, default=None
1944
+ The visual name.
1945
+ Set this property when setting an annotation on a visual.
1946
+ """
1947
+
1948
+ if page_name is None and visual_name is None:
1949
+ file_path = "definition/report.json"
1950
+ elif page_name is not None and visual_name is None:
1810
1951
  page_id, page_display, file_path = helper.resolve_page_name(
1811
1952
  self, page_name=page_name
1812
1953
  )
1954
+ elif page_name is not None and visual_name is not None:
1955
+ page_name, page_display_name, visual_name, file_path = (
1956
+ helper.resolve_visual_name(
1957
+ self, page_name=page_name, visual_name=visual_name
1958
+ )
1959
+ )
1960
+ else:
1961
+ raise ValueError(
1962
+ f"{icons.red_dot} Invalid parameters. If specifying a visual_name you must specify the page_name."
1963
+ )
1813
1964
 
1814
- rd = self.rdef
1815
- for _, r in rd.iterrows():
1816
- path = r["path"]
1817
- payload = r["payload"]
1818
- if object_type == "Report" and path == "definition/report.json":
1819
- a = update_annotation(payload=payload)
1820
- _add_part(request_body, path, _conv_b64(a))
1821
- elif (
1822
- object_type == "Page"
1823
- and path == f"definition/pages/{page_id}/page.json"
1824
- ):
1825
- a = update_annotation(payload=payload)
1826
- _add_part(request_body, path, _conv_b64(a))
1827
- elif (
1828
- object_type == "Visual"
1829
- and path
1830
- == f"definition/pages/{page_id}/visuals/{visual_id}/visual.json"
1831
- ):
1832
- a = update_annotation(payload=payload)
1833
- _add_part(request_body, path, _conv_b64(a))
1834
- else:
1835
- _add_part(request_body, path, payload)
1965
+ payload = self.rdef[self.rdef["path"] == file_path]["payload"].iloc[0]
1966
+ file = _decode_b64(payload)
1967
+ json_file = json.loads(file)
1836
1968
 
1837
- self.update_report(request_body=request_body)
1838
- if object_type == "Report":
1839
- print(
1840
- f"{icons.green_dot} The '{name}' annotation has been set on the report with the '{value}' value."
1969
+ new_file = self.__remove_annotation(json_file, name=annotation_name)
1970
+ new_payload = _conv_b64(new_file)
1971
+
1972
+ self._update_single_file(file_name=file_path, new_payload=new_payload)
1973
+
1974
+ # Get Annotation Value
1975
+ def __get_annotation_value(self, json_file: dict, name: str) -> str:
1976
+
1977
+ if "annotations" in json_file:
1978
+ for ann in json_file["annotations"]:
1979
+ if ann.get("name") == name:
1980
+ return ann.get("value")
1981
+
1982
+ def _get_annotation_value(
1983
+ self,
1984
+ annotation_name: str,
1985
+ page_name: Optional[str] = None,
1986
+ visual_name: Optional[str] = None,
1987
+ ) -> str:
1988
+ """
1989
+ Retrieves the annotation value of an annotation on the report/page/visual.
1990
+ In order to retrieve a report annotation value, leave page_name=None, visual_name=None.
1991
+ In order to retrieve a page annotation value, leave visual_annotation=None.
1992
+ In order to retrieve a visual annotation value, set all parameters.
1993
+
1994
+ Parameters
1995
+ ----------
1996
+ annotation_name : str
1997
+ Name of the annotation.
1998
+ page_name : str, default=None
1999
+ The page name or page display name.
2000
+ Set this annotation when setting an annotation on a page or visual.
2001
+ visual_name : str, default=None
2002
+ The visual name.
2003
+ Set this property when setting an annotation on a visual.
2004
+
2005
+ Returns
2006
+ -------
2007
+ str
2008
+ The annotation value.
2009
+ """
2010
+
2011
+ if page_name is None and visual_name is None:
2012
+ file_path = "definition/report.json"
2013
+ elif page_name is not None and visual_name is None:
2014
+ page_id, page_display, file_path = helper.resolve_page_name(
2015
+ self, page_name=page_name
1841
2016
  )
1842
- elif object_type == "Page":
1843
- print(
1844
- f"{icons.green_dot} The '{name}' annotation has been set on the '{object_name}' page with the '{value}' value."
2017
+ elif page_name is not None and visual_name is not None:
2018
+ page_name, page_display_name, visual_name, file_path = (
2019
+ helper.resolve_visual_name(
2020
+ self, page_name=page_name, visual_name=visual_name
2021
+ )
1845
2022
  )
1846
- elif object_type == "Visual":
1847
- print(
1848
- f"{icons.green_dot} The '{name}' annotation has been set on the '{visual_id}' visual on the '{page_display}' page with the '{value}' value."
2023
+ else:
2024
+ raise ValueError(
2025
+ f"{icons.red_dot} Invalid parameters. If specifying a visual_name you must specify the page_name."
1849
2026
  )
1850
2027
 
2028
+ payload = self.rdef[self.rdef["path"] == file_path]["payload"].iloc[0]
2029
+ file = _decode_b64(payload)
2030
+ json_file = json.loads(file)
2031
+
2032
+ return self.__get_annotation_value(json_file, name=annotation_name)
2033
+
1851
2034
  def __adjust_settings(
1852
2035
  self, setting_type: str, setting_name: str, setting_value: bool
1853
2036
  ): # Meta function