semantic-link-labs 0.10.0__py3-none-any.whl → 0.11.0__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 (95) hide show
  1. {semantic_link_labs-0.10.0.dist-info → semantic_link_labs-0.11.0.dist-info}/METADATA +9 -6
  2. {semantic_link_labs-0.10.0.dist-info → semantic_link_labs-0.11.0.dist-info}/RECORD +95 -87
  3. sempy_labs/__init__.py +11 -1
  4. sempy_labs/_a_lib_info.py +2 -0
  5. sempy_labs/_capacities.py +2 -0
  6. sempy_labs/_connections.py +11 -0
  7. sempy_labs/_dashboards.py +9 -4
  8. sempy_labs/_data_pipelines.py +5 -0
  9. sempy_labs/_dataflows.py +284 -17
  10. sempy_labs/_daxformatter.py +80 -0
  11. sempy_labs/_delta_analyzer_history.py +4 -1
  12. sempy_labs/_deployment_pipelines.py +4 -0
  13. sempy_labs/_documentation.py +3 -0
  14. sempy_labs/_environments.py +10 -1
  15. sempy_labs/_eventhouses.py +12 -5
  16. sempy_labs/_eventstreams.py +11 -3
  17. sempy_labs/_external_data_shares.py +8 -2
  18. sempy_labs/_gateways.py +26 -5
  19. sempy_labs/_git.py +11 -0
  20. sempy_labs/_graphQL.py +10 -3
  21. sempy_labs/_helper_functions.py +62 -10
  22. sempy_labs/_job_scheduler.py +54 -7
  23. sempy_labs/_kql_databases.py +11 -2
  24. sempy_labs/_kql_querysets.py +11 -3
  25. sempy_labs/_list_functions.py +17 -45
  26. sempy_labs/_managed_private_endpoints.py +11 -2
  27. sempy_labs/_mirrored_databases.py +17 -3
  28. sempy_labs/_mirrored_warehouses.py +9 -3
  29. sempy_labs/_ml_experiments.py +11 -3
  30. sempy_labs/_ml_models.py +11 -3
  31. sempy_labs/_model_bpa_rules.py +2 -0
  32. sempy_labs/_mounted_data_factories.py +12 -8
  33. sempy_labs/_notebooks.py +6 -3
  34. sempy_labs/_refresh_semantic_model.py +1 -0
  35. sempy_labs/_semantic_models.py +107 -0
  36. sempy_labs/_spark.py +7 -0
  37. sempy_labs/_sql_endpoints.py +208 -0
  38. sempy_labs/_sqldatabase.py +13 -4
  39. sempy_labs/_tags.py +5 -1
  40. sempy_labs/_user_delegation_key.py +2 -0
  41. sempy_labs/_variable_libraries.py +3 -1
  42. sempy_labs/_warehouses.py +13 -3
  43. sempy_labs/_workloads.py +3 -0
  44. sempy_labs/_workspace_identity.py +3 -0
  45. sempy_labs/_workspaces.py +14 -1
  46. sempy_labs/admin/__init__.py +2 -0
  47. sempy_labs/admin/_activities.py +6 -5
  48. sempy_labs/admin/_apps.py +31 -31
  49. sempy_labs/admin/_artifacts.py +8 -3
  50. sempy_labs/admin/_basic_functions.py +5 -0
  51. sempy_labs/admin/_capacities.py +39 -28
  52. sempy_labs/admin/_datasets.py +51 -51
  53. sempy_labs/admin/_domains.py +17 -1
  54. sempy_labs/admin/_external_data_share.py +8 -2
  55. sempy_labs/admin/_git.py +14 -9
  56. sempy_labs/admin/_items.py +15 -2
  57. sempy_labs/admin/_reports.py +64 -65
  58. sempy_labs/admin/_shared.py +7 -1
  59. sempy_labs/admin/_tags.py +5 -0
  60. sempy_labs/admin/_tenant.py +5 -2
  61. sempy_labs/admin/_users.py +9 -3
  62. sempy_labs/admin/_workspaces.py +88 -0
  63. sempy_labs/directlake/_dl_helper.py +2 -0
  64. sempy_labs/directlake/_generate_shared_expression.py +2 -0
  65. sempy_labs/directlake/_get_directlake_lakehouse.py +2 -4
  66. sempy_labs/directlake/_get_shared_expression.py +2 -0
  67. sempy_labs/directlake/_guardrails.py +2 -0
  68. sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +5 -3
  69. sempy_labs/directlake/_warm_cache.py +1 -0
  70. sempy_labs/graph/_groups.py +22 -7
  71. sempy_labs/graph/_teams.py +7 -2
  72. sempy_labs/graph/_users.py +1 -0
  73. sempy_labs/lakehouse/_blobs.py +1 -0
  74. sempy_labs/lakehouse/_get_lakehouse_tables.py +88 -27
  75. sempy_labs/lakehouse/_helper.py +2 -0
  76. sempy_labs/lakehouse/_lakehouse.py +38 -5
  77. sempy_labs/lakehouse/_livy_sessions.py +2 -1
  78. sempy_labs/lakehouse/_shortcuts.py +7 -1
  79. sempy_labs/migration/_direct_lake_to_import.py +2 -0
  80. sempy_labs/mirrored_azure_databricks_catalog/__init__.py +15 -0
  81. sempy_labs/mirrored_azure_databricks_catalog/_discover.py +213 -0
  82. sempy_labs/mirrored_azure_databricks_catalog/_refresh_catalog_metadata.py +45 -0
  83. sempy_labs/report/_download_report.py +2 -1
  84. sempy_labs/report/_generate_report.py +2 -0
  85. sempy_labs/report/_paginated.py +2 -0
  86. sempy_labs/report/_report_bpa.py +110 -122
  87. sempy_labs/report/_report_bpa_rules.py +2 -0
  88. sempy_labs/report/_report_functions.py +7 -0
  89. sempy_labs/report/_reportwrapper.py +86 -48
  90. sempy_labs/theme/__init__.py +12 -0
  91. sempy_labs/theme/_org_themes.py +96 -0
  92. sempy_labs/tom/_model.py +702 -35
  93. {semantic_link_labs-0.10.0.dist-info → semantic_link_labs-0.11.0.dist-info}/WHEEL +0 -0
  94. {semantic_link_labs-0.10.0.dist-info → semantic_link_labs-0.11.0.dist-info}/licenses/LICENSE +0 -0
  95. {semantic_link_labs-0.10.0.dist-info → semantic_link_labs-0.11.0.dist-info}/top_level.txt +0 -0
@@ -2,7 +2,7 @@ from typing import Optional
2
2
  import pandas as pd
3
3
  import datetime
4
4
  from sempy._utils._log import log
5
- from sempy_labs.report import ReportWrapper, report_bpa_rules
5
+ from sempy_labs.report import connect_report, report_bpa_rules
6
6
  from sempy_labs._helper_functions import (
7
7
  format_dax_object_name,
8
8
  save_as_delta_table,
@@ -14,7 +14,6 @@ from sempy_labs._helper_functions import (
14
14
  from sempy_labs.lakehouse import get_lakehouse_tables, lakehouse_attached
15
15
  import sempy_labs._icons as icons
16
16
  from IPython.display import display, HTML
17
- import sempy_labs.report._report_helper as helper
18
17
  from uuid import UUID
19
18
 
20
19
 
@@ -52,124 +51,114 @@ def run_report_bpa(
52
51
  A pandas dataframe in HTML format showing report objects which violated the best practice analyzer rules.
53
52
  """
54
53
 
55
- rpt = ReportWrapper(report=report, workspace=workspace)
54
+ with connect_report(
55
+ report=report, workspace=workspace, readonly=True, show_diffs=False
56
+ ) as rpt:
56
57
 
57
- dfCV = rpt.list_custom_visuals()
58
- dfP = rpt.list_pages()
59
- dfRF = rpt.list_report_filters()
60
- dfRF["Filter Object"] = format_dax_object_name(
61
- dfRF["Table Name"], dfRF["Object Name"]
62
- )
63
- dfPF = rpt.list_page_filters()
64
- # Convert back to dataframe
65
- # if isinstance(dfPF, pd.io.formats.style.Styler):
66
- # dfPF = dfPF.data
67
- # if isinstance(dfP, pd.io.formats.style.Styler):
68
- # dfP = dfP.data
69
-
70
- dfPF["Filter Object"] = (
71
- dfPF["Page Display Name"]
72
- + " : "
73
- + format_dax_object_name(dfPF["Table Name"], dfPF["Object Name"])
74
- )
75
- dfVF = rpt.list_visual_filters()
76
- dfVF["Filter Object"] = (
77
- format_dax_object_name(dfVF["Page Display Name"], dfVF["Visual Name"])
78
- + " : "
79
- + format_dax_object_name(dfVF["Table Name"], dfVF["Object Name"])
80
- )
81
- dfRLM = rpt.list_report_level_measures()
82
- dfV = rpt.list_visuals()
83
- dfV["Visual Full Name"] = format_dax_object_name(
84
- dfV["Page Display Name"], dfV["Visual Name"]
85
- )
86
- dfSMO = rpt.list_semantic_model_objects(extended=True)
87
-
88
- # Translations
89
- if rules is None:
90
- rules = report_bpa_rules()
91
-
92
- scope_to_dataframe = {
93
- "Custom Visual": (dfCV, ["Custom Visual Display Name"]),
94
- "Page": (dfP, ["Page Display Name"]),
95
- "Visual": (dfV, ["Visual Full Name"]),
96
- "Report Filter": (dfRF, ["Filter Object"]),
97
- "Page Filter": (dfPF, ["Filter Object"]),
98
- "Visual Filter": (dfVF, ["Filter Object"]),
99
- "Report Level Measure": (dfRLM, ["Measure Name"]),
100
- "Semantic Model": (dfSMO, ["Report Source Object"]),
101
- }
102
-
103
- def execute_rule(row):
104
- scopes = row["Scope"]
105
-
106
- # support both str and list as scope type
107
- if isinstance(scopes, str):
108
- scopes = [scopes]
109
-
110
- # collect output dataframes
111
- df_outputs = []
112
-
113
- for scope in scopes:
114
- # common fields for each scope
115
- (df, violation_cols_or_func) = scope_to_dataframe[scope]
116
-
117
- # execute rule and subset df
118
- df_violations = df[row["Expression"](df)]
119
-
120
- # subset the right output columns (e.g. Table Name & Column Name)
121
- if isinstance(violation_cols_or_func, list):
122
- violation_func = lambda violations: violations[violation_cols_or_func]
123
- else:
124
- violation_func = violation_cols_or_func
125
-
126
- # build output data frame
127
- df_output = violation_func(df_violations).copy()
128
-
129
- df_output.columns = ["Object Name"]
130
- df_output["Rule Name"] = row["Rule Name"]
131
- df_output["Category"] = row["Category"]
132
-
133
- # Handle 'Object Type' based on scope
134
- if scope == "Semantic Model":
135
- # Ensure 'Report Source Object' is unique in dfSMO
136
- dfSMO_unique = dfSMO.drop_duplicates(subset="Report Source Object")
137
- # Map 'Object Name' to the 'Report Source' column in dfSMO
138
- df_output["Object Type"] = df_output["Object Name"].map(
139
- dfSMO_unique.set_index("Report Source Object")["Report Source"]
140
- )
141
- else:
142
- df_output["Object Type"] = scope
143
-
144
- df_output["Severity"] = row["Severity"]
145
- df_output["Description"] = row["Description"]
146
- df_output["URL"] = row["URL"]
147
- df_output["Report URL"] = helper.get_web_url(
148
- report=report, workspace=workspace
149
- )
150
-
151
- page_mapping_dict = dfP.set_index("Page Display Name")["Page URL"].to_dict()
152
-
153
- if scope == "Page":
154
- df_output["Report URL"] = df_output["Object Name"].map(
155
- page_mapping_dict
156
- )
157
- elif scope == "Page Filter":
158
- df_output["Page Name"] = df_output["Object Name"].str.extract(
159
- r"(.*?) : '"
160
- )
161
- df_output["Report URL"] = df_output["Page Name"].map(page_mapping_dict)
162
- df_output.drop(columns=["Page Name"], inplace=True)
163
- elif scope in ["Visual", "Visual Filter"]:
164
- df_output["Page Name"] = df_output["Object Name"].str.extract(
165
- r"'(.*?)'\[.*?\]"
166
- )
167
- df_output["Report URL"] = df_output["Page Name"].map(page_mapping_dict)
168
- df_output.drop(columns=["Page Name"], inplace=True)
58
+ dfCV = rpt.list_custom_visuals()
59
+ dfP = rpt.list_pages()
60
+ dfRF = rpt.list_report_filters()
61
+ dfRF["Filter Object"] = format_dax_object_name(
62
+ dfRF["Table Name"], dfRF["Object Name"]
63
+ )
64
+ dfPF = rpt.list_page_filters()
169
65
 
170
- df_outputs.append(df_output)
66
+ dfPF["Filter Object"] = (
67
+ dfPF["Page Display Name"]
68
+ + " : "
69
+ + format_dax_object_name(dfPF["Table Name"], dfPF["Object Name"])
70
+ )
71
+ dfVF = rpt.list_visual_filters()
72
+ dfVF["Filter Object"] = (
73
+ format_dax_object_name(dfVF["Page Display Name"], dfVF["Visual Name"])
74
+ + " : "
75
+ + format_dax_object_name(dfVF["Table Name"], dfVF["Object Name"])
76
+ )
77
+ dfRLM = rpt.list_report_level_measures()
78
+ dfV = rpt.list_visuals()
79
+ dfV["Visual Full Name"] = format_dax_object_name(
80
+ dfV["Page Display Name"], dfV["Visual Name"]
81
+ )
82
+ dfSMO = rpt.list_semantic_model_objects(extended=True)
83
+
84
+ # Translations
85
+ if rules is None:
86
+ rules = report_bpa_rules()
87
+
88
+ scope_to_dataframe = {
89
+ "Custom Visual": (dfCV, ["Custom Visual Display Name"]),
90
+ "Page": (dfP, ["Page Display Name"]),
91
+ "Visual": (dfV, ["Visual Full Name"]),
92
+ "Report Filter": (dfRF, ["Filter Object"]),
93
+ "Page Filter": (dfPF, ["Filter Object"]),
94
+ "Visual Filter": (dfVF, ["Filter Object"]),
95
+ "Report Level Measure": (dfRLM, ["Measure Name"]),
96
+ "Semantic Model": (dfSMO, ["Report Source Object"]),
97
+ }
171
98
 
172
- return df_outputs
99
+ def execute_rule(row):
100
+ scopes = row["Scope"]
101
+
102
+ # support both str and list as scope type
103
+ if isinstance(scopes, str):
104
+ scopes = [scopes]
105
+
106
+ # collect output dataframes
107
+ df_outputs = []
108
+
109
+ for scope in scopes:
110
+ # common fields for each scope
111
+ (df, violation_cols_or_func) = scope_to_dataframe[scope]
112
+
113
+ # execute rule and subset df
114
+ df_violations = df[row["Expression"](df)]
115
+
116
+ # subset the right output columns (e.g. Table Name & Column Name)
117
+ if isinstance(violation_cols_or_func, list):
118
+ violation_func = lambda violations: violations[
119
+ violation_cols_or_func
120
+ ]
121
+ else:
122
+ violation_func = violation_cols_or_func
123
+
124
+ # build output data frame
125
+ df_output = violation_func(df_violations).copy()
126
+
127
+ df_output.columns = ["Object Name"]
128
+ df_output["Rule Name"] = row["Rule Name"]
129
+ df_output["Category"] = row["Category"]
130
+
131
+ # Handle 'Object Type' based on scope
132
+ if scope == "Semantic Model":
133
+ # Ensure 'Report Source Object' is unique in dfSMO
134
+ dfSMO_unique = dfSMO.drop_duplicates(subset="Report Source Object")
135
+ # Map 'Object Name' to the 'Report Source' column in dfSMO
136
+ df_output["Object Type"] = df_output["Object Name"].map(
137
+ dfSMO_unique.set_index("Report Source Object")["Report Source"]
138
+ )
139
+ else:
140
+ df_output["Object Type"] = scope
141
+
142
+ df_output["Severity"] = row["Severity"]
143
+ df_output["Description"] = row["Description"]
144
+ if scope in ["Page", "Page Filter"]:
145
+ if not df_output.empty:
146
+ for idx, r in df_output.iterrows():
147
+ df_output.loc[idx, "URL"] = rpt._get_url(
148
+ page_name=r["Object Name"]
149
+ )
150
+ elif scope in ["Visual", "Visual Filter"]:
151
+ if not df_output.empty:
152
+ for idx, r in df_output.iterrows():
153
+ df_output.loc[idx, "URL"] = rpt._get_url(
154
+ page_name=r["Object Name"].split("[")[0][1:-1],
155
+ visual_name=r["Object Name"].split("[")[1].rstrip("]"),
156
+ )
157
+ else:
158
+ df_output["URL"] = rpt._get_url()
159
+ df_outputs.append(df_output)
160
+
161
+ return df_outputs
173
162
 
174
163
  # flatten list of lists
175
164
  flatten_dfs = [
@@ -186,7 +175,7 @@ def run_report_bpa(
186
175
  "Severity",
187
176
  "Description",
188
177
  "URL",
189
- "Report URL",
178
+ # "Report URL",
190
179
  ]
191
180
  ]
192
181
 
@@ -271,7 +260,7 @@ def run_report_bpa(
271
260
  "Severity",
272
261
  "Description",
273
262
  "URL",
274
- "Report URL",
263
+ # "Report URL",
275
264
  ]
276
265
  ]
277
266
  .sort_values(["Category", "Rule Name", "Object Type", "Object Name"])
@@ -347,13 +336,12 @@ def run_report_bpa(
347
336
  else:
348
337
  content_html += f'<td>{row["Rule Name"]}</td>'
349
338
  content_html += f'<td>{row["Object Type"]}</td>'
350
- if pd.notnull(row["Report URL"]):
339
+ if pd.notnull(row["URL"]):
351
340
  content_html += (
352
- f'<td><a href="{row["Report URL"]}">{row["Object Name"]}</a></td>'
341
+ f'<td><a href="{row["URL"]}">{row["Object Name"]}</a></td>'
353
342
  )
354
343
  else:
355
344
  content_html += f'<td>{row["Object Name"]}</td>'
356
- # content_html += f'<td>{row["Object Name"]}</td>'
357
345
  content_html += f'<td style="text-align: center;">{row["Severity"]}</td>'
358
346
  content_html += "</tr>"
359
347
  content_html += "</table>"
@@ -1,6 +1,8 @@
1
1
  import pandas as pd
2
+ from sempy._utils._log import log
2
3
 
3
4
 
5
+ @log
4
6
  def report_bpa_rules() -> pd.DataFrame:
5
7
  """
6
8
  Shows the default rules for the report BPA used by the run_report_bpa function.
@@ -26,6 +26,7 @@ import sempy_labs._icons as icons
26
26
  from uuid import UUID
27
27
 
28
28
 
29
+ @log
29
30
  def get_report_json(
30
31
  report: str,
31
32
  workspace: Optional[str | UUID] = None,
@@ -90,6 +91,7 @@ def get_report_json(
90
91
  return report_json
91
92
 
92
93
 
94
+ @log
93
95
  def report_dependency_tree(workspace: Optional[str | UUID] = None):
94
96
  """
95
97
  Prints a dependency between reports and semantic models.
@@ -142,6 +144,7 @@ def report_dependency_tree(workspace: Optional[str | UUID] = None):
142
144
  print(f"{pre}{node.custom_property}'{node.name}'")
143
145
 
144
146
 
147
+ @log
145
148
  def clone_report(
146
149
  report: str,
147
150
  cloned_report: str,
@@ -221,6 +224,7 @@ def clone_report(
221
224
  )
222
225
 
223
226
 
227
+ @log
224
228
  def launch_report(report: str, workspace: Optional[str | UUID] = None):
225
229
  """
226
230
  Shows a Power BI report within a Fabric notebook.
@@ -249,6 +253,7 @@ def launch_report(report: str, workspace: Optional[str | UUID] = None):
249
253
  return report
250
254
 
251
255
 
256
+ @log
252
257
  def list_report_pages(report: str, workspace: Optional[str | UUID] = None):
253
258
  """
254
259
  Shows the properties of all pages within a Power BI report.
@@ -316,6 +321,7 @@ def list_report_pages(report: str, workspace: Optional[str | UUID] = None):
316
321
  return df
317
322
 
318
323
 
324
+ @log
319
325
  def list_report_visuals(report: str, workspace: Optional[str | UUID] = None):
320
326
  """
321
327
  Shows the properties of all visuals within a Power BI report.
@@ -369,6 +375,7 @@ def list_report_visuals(report: str, workspace: Optional[str | UUID] = None):
369
375
  return df
370
376
 
371
377
 
378
+ @log
372
379
  def list_report_bookmarks(report: str, workspace: Optional[str | UUID] = None):
373
380
  """
374
381
  Shows the properties of all bookmarks within a Power BI report.
@@ -17,6 +17,7 @@ from sempy_labs._helper_functions import (
17
17
  get_jsonpath_value,
18
18
  set_json_value,
19
19
  remove_json_value,
20
+ get_tenant_id,
20
21
  )
21
22
  from sempy_labs._dictionary_diffs import (
22
23
  diff_parts,
@@ -425,7 +426,9 @@ class ReportWrapper:
425
426
  ]
426
427
  )
427
428
 
428
- def _get_url(self, page_name: Optional[str] = None) -> str:
429
+ def _get_url(
430
+ self, page_name: Optional[str] = None, visual_name: Optional[str] = None
431
+ ) -> str:
429
432
  """
430
433
  Gets the URL of the report. If specified, gets the URL of the specified page.
431
434
 
@@ -444,8 +447,16 @@ class ReportWrapper:
444
447
  url = f"https://app.powerbi.com/groups/{self._workspace_id}/reports/{self._report_id}"
445
448
 
446
449
  if page_name:
450
+ if page_name in [page["payload"]["name"] for page in self.__all_pages()]:
451
+ pass
452
+ else:
453
+ page_name = self.resolve_page_name(page_name)
447
454
  url += f"/{page_name}"
448
455
 
456
+ if visual_name:
457
+ tenant_id = get_tenant_id()
458
+ url += f"?ctid={tenant_id}&pbi_source=shareVisual&visual={visual_name}"
459
+
449
460
  return url
450
461
 
451
462
  def __resolve_page_name_and_display_name_file_path(
@@ -1120,6 +1131,7 @@ class ReportWrapper:
1120
1131
  "Has Sparkline": "bool",
1121
1132
  "Visual Filter Count": "int",
1122
1133
  "Data Limit": "int",
1134
+ "URL": "str",
1123
1135
  }
1124
1136
  df = _create_dataframe(columns=columns)
1125
1137
 
@@ -1247,12 +1259,13 @@ class ReportWrapper:
1247
1259
 
1248
1260
  # Sparkline
1249
1261
  has_sparkline = contains_key(payload, ["SparklineData"])
1262
+ visual_name = payload.get("name")
1250
1263
 
1251
1264
  new_data = {
1252
1265
  "File Path": path,
1253
1266
  "Page Name": page_id,
1254
1267
  "Page Display Name": page_display,
1255
- "Visual Name": payload.get("name"),
1268
+ "Visual Name": visual_name,
1256
1269
  "X": pos.get("x"),
1257
1270
  "Y": pos.get("y"),
1258
1271
  "Z": pos.get("z"),
@@ -1275,6 +1288,7 @@ class ReportWrapper:
1275
1288
  "Has Sparkline": has_sparkline,
1276
1289
  "Visual Filter Count": visual_filter_count,
1277
1290
  "Data Limit": data_limit,
1291
+ "URL": self._get_url(page_name=page_id, visual_name=visual_name),
1278
1292
  }
1279
1293
  dfs.append(pd.DataFrame(new_data, index=[0]))
1280
1294
 
@@ -1361,6 +1375,7 @@ class ReportWrapper:
1361
1375
  if isinstance(expression, dict)
1362
1376
  else {}
1363
1377
  )
1378
+
1364
1379
  if (
1365
1380
  isinstance(source_ref, dict)
1366
1381
  and "Entity" in source_ref
@@ -1374,9 +1389,15 @@ class ReportWrapper:
1374
1389
  if keys_path
1375
1390
  else "Unknown"
1376
1391
  )
1377
- is_agg = keys_path[-3] == "Aggregation"
1378
- is_viz_calc = keys_path[-3] == "NativeVisualCalculation"
1379
- is_sparkline = keys_path[-3] == "SparklineData"
1392
+ is_agg = len(keys_path) > 2 and keys_path[-3] == "Aggregation"
1393
+ is_viz_calc = (
1394
+ len(keys_path) > 2
1395
+ and keys_path[-3] == "NativeVisualCalculation"
1396
+ )
1397
+ is_sparkline = (
1398
+ len(keys_path) > 2 and keys_path[-3] == "SparklineData"
1399
+ )
1400
+
1380
1401
  result[property_value] = (
1381
1402
  entity,
1382
1403
  object_type,
@@ -1384,13 +1405,10 @@ class ReportWrapper:
1384
1405
  is_viz_calc,
1385
1406
  is_sparkline,
1386
1407
  )
1387
- if keys_path:
1388
- keys_path.pop()
1389
1408
 
1390
1409
  # Recursively search the rest of the dictionary
1391
1410
  for key, value in data.items():
1392
- keys_path.append(key)
1393
- find_entity_property_pairs(value, result, keys_path)
1411
+ find_entity_property_pairs(value, result, keys_path + [key])
1394
1412
 
1395
1413
  elif isinstance(data, list):
1396
1414
  for item in data:
@@ -1712,31 +1730,36 @@ class ReportWrapper:
1712
1730
  "Expression": "str",
1713
1731
  "Data Type": "str",
1714
1732
  "Format String": "str",
1733
+ "Data Category": "str",
1715
1734
  }
1716
1735
 
1717
1736
  df = _create_dataframe(columns=columns)
1718
1737
 
1738
+ # If no report extensions path, return empty DataFrame
1739
+ if self._report_extensions_path not in self.list_paths()["Path"].values:
1740
+ return df
1741
+
1719
1742
  report_file = self.get(file_path=self._report_extensions_path)
1720
1743
 
1721
1744
  dfs = []
1722
- if report_file:
1723
- payload = report_file.get("payload")
1724
- for e in payload.get("entities", []):
1725
- table_name = e.get("name")
1726
- for m in e.get("measures", []):
1727
- measure_name = m.get("name")
1728
- expr = m.get("expression")
1729
- data_type = m.get("dataType")
1730
- format_string = m.get("formatString")
1745
+ for e in report_file.get("entities", []):
1746
+ table_name = e.get("name")
1747
+ for m in e.get("measures", []):
1748
+ measure_name = m.get("name")
1749
+ expr = m.get("expression")
1750
+ data_type = m.get("dataType")
1751
+ format_string = m.get("formatString")
1752
+ data_category = m.get("dataCategory")
1731
1753
 
1732
- new_data = {
1733
- "Measure Name": measure_name,
1734
- "Table Name": table_name,
1735
- "Expression": expr,
1736
- "Data Type": data_type,
1737
- "Format String": format_string,
1738
- }
1739
- dfs.append(pd.DataFrame(new_data, index=[0]))
1754
+ new_data = {
1755
+ "Measure Name": measure_name,
1756
+ "Table Name": table_name,
1757
+ "Expression": expr,
1758
+ "Data Type": data_type,
1759
+ "Format String": format_string,
1760
+ "Data Category": data_category,
1761
+ }
1762
+ dfs.append(pd.DataFrame(new_data, index=[0]))
1740
1763
 
1741
1764
  if dfs:
1742
1765
  df = pd.concat(dfs, ignore_index=True)
@@ -1792,46 +1815,61 @@ class ReportWrapper:
1792
1815
  return self.get(file_path=theme_file_path)
1793
1816
 
1794
1817
  # Action functions
1795
- def set_theme(self, theme_file_path: str):
1818
+ def set_theme(
1819
+ self, theme_file_path: Optional[str] = None, theme_json: Optional[dict] = None
1820
+ ):
1796
1821
  """
1797
1822
  Sets a custom theme for a report based on a theme .json file.
1798
1823
 
1799
1824
  Parameters
1800
1825
  ----------
1801
- theme_file_path : str
1826
+ theme_file_path : str, default=None
1802
1827
  The file path of the theme.json file. This can either be from a Fabric lakehouse or from the web.
1803
1828
  Example for lakehouse: file_path = '/lakehouse/default/Files/CY23SU09.json'
1804
1829
  Example for web url: file_path = 'https://raw.githubusercontent.com/PowerBiDevCamp/FabricUserApiDemo/main/FabricUserApiDemo/DefinitionTemplates/Shared/Reports/StaticResources/SharedResources/BaseThemes/CY23SU08.json'
1830
+ theme_json : dict, default=None
1831
+ The theme file in .json format. Must specify either the theme_file_path or the theme_json.
1805
1832
  """
1806
1833
 
1807
- self._ensure_pbir()
1808
- theme_version = "5.6.4"
1809
-
1810
- # Open file
1811
- if not theme_file_path.endswith(".json"):
1834
+ if theme_file_path and theme_json:
1812
1835
  raise ValueError(
1813
- f"{icons.red_dot} The '{theme_file_path}' theme file path must be a .json file."
1836
+ f"{icons.red_dot} Please specify either the 'theme_file_path' or the 'theme_json' parameter, not both."
1814
1837
  )
1815
- elif theme_file_path.startswith("https://"):
1816
- response = requests.get(theme_file_path)
1817
- theme_file = response.json()
1818
- elif theme_file_path.startswith("/lakehouse") or theme_file_path.startswith(
1819
- "/synfs/"
1820
- ):
1821
- with open(theme_file_path, "r", encoding="utf-8-sig") as file:
1822
- theme_file = json.load(file)
1823
- else:
1824
- ValueError(
1825
- f"{icons.red_dot} Incorrect theme file path value '{theme_file_path}'."
1838
+ if not theme_file_path and not theme_json:
1839
+ raise ValueError(
1840
+ f"{icons.red_dot} Please specify either the 'theme_file_path' or the 'theme_json' parameter."
1826
1841
  )
1827
1842
 
1828
- theme_name = theme_file.get("name")
1843
+ self._ensure_pbir()
1844
+ theme_version = "5.6.4"
1845
+
1846
+ # Extract theme_json from theme_file_path
1847
+ if theme_file_path:
1848
+ # Open file
1849
+ if not theme_file_path.endswith(".json"):
1850
+ raise ValueError(
1851
+ f"{icons.red_dot} The '{theme_file_path}' theme file path must be a .json file."
1852
+ )
1853
+ elif theme_file_path.startswith("https://"):
1854
+ response = requests.get(theme_file_path)
1855
+ theme_json = response.json()
1856
+ elif theme_file_path.startswith("/lakehouse") or theme_file_path.startswith(
1857
+ "/synfs/"
1858
+ ):
1859
+ with open(theme_file_path, "r", encoding="utf-8-sig") as file:
1860
+ theme_json = json.load(file)
1861
+ else:
1862
+ ValueError(
1863
+ f"{icons.red_dot} Incorrect theme file path value '{theme_file_path}'."
1864
+ )
1865
+
1866
+ theme_name = theme_json.get("name")
1829
1867
  theme_name_full = f"{theme_name}.json"
1830
1868
 
1831
1869
  # Add theme.json file
1832
1870
  self.add(
1833
1871
  file_path=f"StaticResources/RegisteredResources/{theme_name_full}",
1834
- payload=theme_file,
1872
+ payload=theme_json,
1835
1873
  )
1836
1874
 
1837
1875
  custom_theme = {
@@ -2102,7 +2140,7 @@ class ReportWrapper:
2102
2140
  format_string = r["Format String"]
2103
2141
  # Add measures to the model
2104
2142
  if (
2105
- measure_name in measures or measures is None
2143
+ measures is None or measure_name in measures
2106
2144
  ) and measure_name not in existing_measures:
2107
2145
  tom.add_measure(
2108
2146
  table_name=table_name,
@@ -0,0 +1,12 @@
1
+ from ._org_themes import (
2
+ list_org_themes,
3
+ get_org_theme_json,
4
+ resolve_theme_id,
5
+ )
6
+
7
+
8
+ __all__ = [
9
+ "list_org_themes",
10
+ "get_org_theme_json",
11
+ "resolve_theme_id",
12
+ ]