semantic-link-labs 0.6.0__py3-none-any.whl → 0.7.1__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 (104) hide show
  1. semantic_link_labs-0.7.1.dist-info/METADATA +148 -0
  2. semantic_link_labs-0.7.1.dist-info/RECORD +111 -0
  3. {semantic_link_labs-0.6.0.dist-info → semantic_link_labs-0.7.1.dist-info}/WHEEL +1 -1
  4. sempy_labs/__init__.py +26 -2
  5. sempy_labs/_ai.py +3 -65
  6. sempy_labs/_bpa_translation/_translations_am-ET.po +828 -0
  7. sempy_labs/_bpa_translation/_translations_ar-AE.po +860 -0
  8. sempy_labs/_bpa_translation/_translations_cs-CZ.po +894 -0
  9. sempy_labs/_bpa_translation/_translations_da-DK.po +894 -0
  10. sempy_labs/_bpa_translation/_translations_de-DE.po +933 -0
  11. sempy_labs/_bpa_translation/_translations_el-GR.po +936 -0
  12. sempy_labs/_bpa_translation/_translations_es-ES.po +915 -0
  13. sempy_labs/_bpa_translation/_translations_fa-IR.po +883 -0
  14. sempy_labs/_bpa_translation/_translations_fr-FR.po +938 -0
  15. sempy_labs/_bpa_translation/_translations_ga-IE.po +912 -0
  16. sempy_labs/_bpa_translation/_translations_he-IL.po +855 -0
  17. sempy_labs/_bpa_translation/_translations_hi-IN.po +892 -0
  18. sempy_labs/_bpa_translation/_translations_hu-HU.po +910 -0
  19. sempy_labs/_bpa_translation/_translations_is-IS.po +887 -0
  20. sempy_labs/_bpa_translation/_translations_it-IT.po +931 -0
  21. sempy_labs/_bpa_translation/_translations_ja-JP.po +805 -0
  22. sempy_labs/_bpa_translation/_translations_nl-NL.po +924 -0
  23. sempy_labs/_bpa_translation/_translations_pl-PL.po +913 -0
  24. sempy_labs/_bpa_translation/_translations_pt-BR.po +909 -0
  25. sempy_labs/_bpa_translation/_translations_pt-PT.po +904 -0
  26. sempy_labs/_bpa_translation/_translations_ru-RU.po +909 -0
  27. sempy_labs/_bpa_translation/_translations_ta-IN.po +922 -0
  28. sempy_labs/_bpa_translation/_translations_te-IN.po +896 -0
  29. sempy_labs/_bpa_translation/_translations_th-TH.po +873 -0
  30. sempy_labs/_bpa_translation/_translations_zh-CN.po +767 -0
  31. sempy_labs/_bpa_translation/_translations_zu-ZA.po +916 -0
  32. sempy_labs/_clear_cache.py +9 -4
  33. sempy_labs/_generate_semantic_model.py +30 -56
  34. sempy_labs/_helper_functions.py +361 -14
  35. sempy_labs/_icons.py +10 -1
  36. sempy_labs/_list_functions.py +539 -260
  37. sempy_labs/_model_bpa.py +194 -18
  38. sempy_labs/_model_bpa_bulk.py +367 -0
  39. sempy_labs/_model_bpa_rules.py +19 -8
  40. sempy_labs/_model_dependencies.py +12 -10
  41. sempy_labs/_one_lake_integration.py +7 -7
  42. sempy_labs/_query_scale_out.py +61 -96
  43. sempy_labs/_refresh_semantic_model.py +7 -0
  44. sempy_labs/_translations.py +154 -1
  45. sempy_labs/_vertipaq.py +103 -90
  46. sempy_labs/directlake/__init__.py +5 -1
  47. sempy_labs/directlake/_directlake_schema_compare.py +27 -31
  48. sempy_labs/directlake/_directlake_schema_sync.py +55 -66
  49. sempy_labs/directlake/_dl_helper.py +233 -0
  50. sempy_labs/directlake/_get_directlake_lakehouse.py +6 -7
  51. sempy_labs/directlake/_get_shared_expression.py +1 -1
  52. sempy_labs/directlake/_guardrails.py +17 -13
  53. sempy_labs/directlake/_update_directlake_partition_entity.py +54 -30
  54. sempy_labs/directlake/_warm_cache.py +1 -1
  55. sempy_labs/lakehouse/__init__.py +2 -0
  56. sempy_labs/lakehouse/_get_lakehouse_tables.py +61 -69
  57. sempy_labs/lakehouse/_lakehouse.py +66 -9
  58. sempy_labs/lakehouse/_shortcuts.py +1 -1
  59. sempy_labs/migration/_create_pqt_file.py +174 -182
  60. sempy_labs/migration/_migrate_calctables_to_lakehouse.py +236 -268
  61. sempy_labs/migration/_migrate_calctables_to_semantic_model.py +75 -73
  62. sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +442 -426
  63. sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +91 -97
  64. sempy_labs/migration/_refresh_calc_tables.py +92 -101
  65. sempy_labs/report/_BPAReportTemplate.json +232 -0
  66. sempy_labs/report/__init__.py +6 -2
  67. sempy_labs/report/_bpareporttemplate/.pbi/localSettings.json +9 -0
  68. sempy_labs/report/_bpareporttemplate/.platform +11 -0
  69. sempy_labs/report/_bpareporttemplate/StaticResources/SharedResources/BaseThemes/CY24SU06.json +710 -0
  70. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/page.json +11 -0
  71. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/1b08bce3bebabb0a27a8/visual.json +191 -0
  72. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/2f22ddb70c301693c165/visual.json +438 -0
  73. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/3b1182230aa6c600b43a/visual.json +127 -0
  74. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/58577ba6380c69891500/visual.json +576 -0
  75. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/a2a8fa5028b3b776c96c/visual.json +207 -0
  76. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/adfd47ef30652707b987/visual.json +506 -0
  77. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/b6a80ee459e716e170b1/visual.json +127 -0
  78. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/ce3130a721c020cc3d81/visual.json +513 -0
  79. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/page.json +8 -0
  80. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/visuals/66e60dfb526437cd78d1/visual.json +112 -0
  81. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/page.json +11 -0
  82. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/07deb8bce824e1be37d7/visual.json +513 -0
  83. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0b1c68838818b32ad03b/visual.json +352 -0
  84. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0c171de9d2683d10b930/visual.json +37 -0
  85. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0efa01be0510e40a645e/visual.json +542 -0
  86. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/6bf2f0eb830ab53cc668/visual.json +221 -0
  87. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/88d8141cb8500b60030c/visual.json +127 -0
  88. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/a753273590beed656a03/visual.json +576 -0
  89. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/b8fdc82cddd61ac447bc/visual.json +127 -0
  90. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/page.json +9 -0
  91. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/visuals/ce8532a7e25020271077/visual.json +38 -0
  92. sempy_labs/report/_bpareporttemplate/definition/pages/pages.json +10 -0
  93. sempy_labs/report/_bpareporttemplate/definition/report.json +176 -0
  94. sempy_labs/report/_bpareporttemplate/definition/version.json +4 -0
  95. sempy_labs/report/_bpareporttemplate/definition.pbir +14 -0
  96. sempy_labs/report/_generate_report.py +255 -139
  97. sempy_labs/report/_report_functions.py +26 -33
  98. sempy_labs/report/_report_rebind.py +31 -26
  99. sempy_labs/tom/_model.py +75 -58
  100. semantic_link_labs-0.6.0.dist-info/METADATA +0 -22
  101. semantic_link_labs-0.6.0.dist-info/RECORD +0 -54
  102. sempy_labs/directlake/_fallback.py +0 -60
  103. {semantic_link_labs-0.6.0.dist-info → semantic_link_labs-0.7.1.dist-info}/LICENSE +0 -0
  104. {semantic_link_labs-0.6.0.dist-info → semantic_link_labs-0.7.1.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  import sempy.fabric as fabric
2
- from ._helper_functions import resolve_dataset_id
2
+ from ._helper_functions import resolve_dataset_id, is_default_semantic_model
3
3
  from typing import Optional
4
4
  import sempy_labs._icons as icons
5
5
 
@@ -20,13 +20,18 @@ def clear_cache(dataset: str, workspace: Optional[str] = None):
20
20
  """
21
21
 
22
22
  workspace = fabric.resolve_workspace_name(workspace)
23
+ if is_default_semantic_model(dataset=dataset, workspace=workspace):
24
+ raise ValueError(
25
+ f"{icons.red_dot} Cannot run XMLA operations against a default semantic model. Please choose a different semantic model. "
26
+ "See here for more information: https://learn.microsoft.com/fabric/data-warehouse/semantic-models"
27
+ )
23
28
 
24
- datasetID = resolve_dataset_id(dataset=dataset, workspace=workspace)
29
+ dataset_id = resolve_dataset_id(dataset=dataset, workspace=workspace)
25
30
 
26
31
  xmla = f"""
27
32
  <ClearCache xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">
28
- <Object>
29
- <DatabaseID>{datasetID}</DatabaseID>
33
+ <Object>
34
+ <DatabaseID>{dataset_id}</DatabaseID>
30
35
  </Object>
31
36
  </ClearCache>
32
37
  """
@@ -1,17 +1,18 @@
1
1
  import sempy.fabric as fabric
2
2
  import pandas as pd
3
3
  import json
4
- import base64
5
- import time
6
4
  import os
7
5
  from typing import Optional
8
6
  from sempy_labs._helper_functions import (
9
7
  resolve_lakehouse_name,
10
8
  resolve_workspace_name_and_id,
9
+ resolve_dataset_id,
10
+ _conv_b64,
11
+ _decode_b64,
12
+ lro,
11
13
  )
12
14
  from sempy_labs.lakehouse._lakehouse import lakehouse_attached
13
15
  import sempy_labs._icons as icons
14
- from sempy.fabric.exceptions import FabricHTTPException
15
16
 
16
17
 
17
18
  def create_blank_semantic_model(
@@ -34,8 +35,7 @@ def create_blank_semantic_model(
34
35
  or if no lakehouse attached, resolves to the workspace of the notebook.
35
36
  """
36
37
 
37
- if workspace is None:
38
- workspace = fabric.resolve_workspace_name()
38
+ workspace = fabric.resolve_workspace_name(workspace)
39
39
 
40
40
  min_compat = 1500
41
41
 
@@ -89,9 +89,7 @@ def create_semantic_model_from_bim(
89
89
 
90
90
  (workspace, workspace_id) = resolve_workspace_name_and_id(workspace)
91
91
 
92
- objectType = "SemanticModel"
93
-
94
- dfI = fabric.list_items(workspace=workspace, type=objectType)
92
+ dfI = fabric.list_items(workspace=workspace, type="SemanticModel")
95
93
  dfI_filt = dfI[(dfI["Display Name"] == dataset)]
96
94
 
97
95
  if len(dfI_filt) > 0:
@@ -102,19 +100,11 @@ def create_semantic_model_from_bim(
102
100
  client = fabric.FabricRestClient()
103
101
  defPBIDataset = {"version": "1.0", "settings": {}}
104
102
 
105
- def conv_b64(file):
106
-
107
- loadJson = json.dumps(file)
108
- f = base64.b64encode(loadJson.encode("utf-8")).decode("utf-8")
109
-
110
- return f
111
-
112
- payloadPBIDefinition = conv_b64(defPBIDataset)
113
- payloadBim = conv_b64(bim_file)
103
+ payloadPBIDefinition = _conv_b64(defPBIDataset)
104
+ payloadBim = _conv_b64(bim_file)
114
105
 
115
106
  request_body = {
116
107
  "displayName": dataset,
117
- "type": objectType,
118
108
  "definition": {
119
109
  "parts": [
120
110
  {
@@ -131,26 +121,16 @@ def create_semantic_model_from_bim(
131
121
  },
132
122
  }
133
123
 
134
- response = client.post(f"/v1/workspaces/{workspace_id}/items", json=request_body)
124
+ response = client.post(
125
+ f"/v1/workspaces/{workspace_id}/semanticModels",
126
+ json=request_body,
127
+ )
135
128
 
136
- if response.status_code == 201:
137
- print(
138
- f"{icons.green_dot} The '{dataset}' semantic model has been created within the '{workspace}' workspace."
139
- )
140
- print(response.json())
141
- elif response.status_code == 202:
142
- operationId = response.headers["x-ms-operation-id"]
143
- response = client.get(f"/v1/operations/{operationId}")
144
- response_body = json.loads(response.content)
145
- while response_body["status"] != "Succeeded":
146
- time.sleep(3)
147
- response = client.get(f"/v1/operations/{operationId}")
148
- response_body = json.loads(response.content)
149
- response = client.get(f"/v1/operations/{operationId}/result")
150
- print(
151
- f"{icons.green_dot} The '{dataset}' semantic model has been created within the '{workspace}' workspace."
152
- )
153
- print(response.json())
129
+ lro(client, response, status_codes=[201, 202])
130
+
131
+ print(
132
+ f"{icons.green_dot} The '{dataset}' semantic model has been created within the '{workspace}' workspace."
133
+ )
154
134
 
155
135
 
156
136
  def deploy_semantic_model(
@@ -196,11 +176,10 @@ def deploy_semantic_model(
196
176
  target_dataset = source_dataset
197
177
 
198
178
  if target_dataset == source_dataset and target_workspace == source_workspace:
199
- print(
179
+ raise ValueError(
200
180
  f"{icons.red_dot} The 'dataset' and 'new_dataset' parameters have the same value. And, the 'workspace' and 'new_dataset_workspace' "
201
181
  f"parameters have the same value. At least one of these must be different. Please update the parameters."
202
182
  )
203
- return
204
183
 
205
184
  bim = get_semantic_model_bim(dataset=source_dataset, workspace=source_workspace)
206
185
 
@@ -217,7 +196,7 @@ def get_semantic_model_bim(
217
196
  workspace: Optional[str] = None,
218
197
  save_to_file_name: Optional[str] = None,
219
198
  lakehouse_workspace: Optional[str] = None,
220
- ):
199
+ ) -> dict:
221
200
  """
222
201
  Extracts the Model.bim file for a given semantic model.
223
202
 
@@ -238,48 +217,43 @@ def get_semantic_model_bim(
238
217
 
239
218
  Returns
240
219
  -------
241
- str
220
+ dict
242
221
  The Model.bim file for the semantic model.
243
222
  """
244
223
 
245
224
  (workspace, workspace_id) = resolve_workspace_name_and_id(workspace)
246
- if lakehouse_workspace is None:
247
- lakehouse_workspace = workspace
248
225
 
249
226
  fmt = "TMSL"
250
227
  client = fabric.FabricRestClient()
251
- itemList = fabric.list_items(workspace=workspace, type="SemanticModel")
252
- itemListFilt = itemList[(itemList["Display Name"] == dataset)]
253
- itemId = itemListFilt["Id"].iloc[0]
228
+ dataset_id = resolve_dataset_id(dataset=dataset, workspace=workspace)
254
229
  response = client.post(
255
- f"/v1/workspaces/{workspace_id}/items/{itemId}/getDefinition?format={fmt}",
256
- lro_wait=True,
230
+ f"/v1/workspaces/{workspace_id}/semanticModels/{dataset_id}/getDefinition?format={fmt}",
257
231
  )
258
-
259
- df_items = pd.json_normalize(response.json()["definition"]["parts"])
232
+ result = lro(client, response).json()
233
+ df_items = pd.json_normalize(result["definition"]["parts"])
260
234
  df_items_filt = df_items[df_items["path"] == "model.bim"]
261
235
  payload = df_items_filt["payload"].iloc[0]
262
- bimFile = base64.b64decode(payload).decode("utf-8")
236
+ bimFile = _decode_b64(payload)
263
237
  bimJson = json.loads(bimFile)
264
238
 
265
239
  if save_to_file_name is not None:
266
- lakeAttach = lakehouse_attached()
267
- if lakeAttach is False:
240
+ if not lakehouse_attached():
268
241
  raise ValueError(
269
242
  f"{icons.red_dot} In order to save the model.bim file, a lakehouse must be attached to the notebook. Please attach a lakehouse to this notebook."
270
243
  )
271
244
 
272
245
  lakehouse_id = fabric.get_lakehouse_id()
273
- lakehouse = resolve_lakehouse_name(lakehouse_id, lakehouse_workspace)
246
+ lake_workspace = fabric.resolve_workspace_name()
247
+ lakehouse = resolve_lakehouse_name(lakehouse_id, lake_workspace)
274
248
  folderPath = "/lakehouse/default/Files"
275
249
  fileExt = ".bim"
276
250
  if not save_to_file_name.endswith(fileExt):
277
- save_to_file_name = save_to_file_name + fileExt
251
+ save_to_file_name = f"{save_to_file_name}{fileExt}"
278
252
  filePath = os.path.join(folderPath, save_to_file_name)
279
253
  with open(filePath, "w") as json_file:
280
254
  json.dump(bimJson, json_file, indent=4)
281
255
  print(
282
- f"The .bim file for the '{dataset}' semantic model has been saved to the '{lakehouse}' in this location: '{filePath}'.\n\n"
256
+ f"{icons.green_dot} The .bim file for the '{dataset}' semantic model has been saved to the '{lakehouse}' in this location: '{filePath}'.\n\n"
283
257
  )
284
258
 
285
259
  return bimJson
@@ -1,10 +1,17 @@
1
1
  import sempy.fabric as fabric
2
2
  import re
3
+ import json
4
+ import base64
3
5
  import pandas as pd
6
+ from functools import wraps
7
+ import datetime
8
+ import time
4
9
  from pyspark.sql import SparkSession
5
- from typing import Optional, Tuple
10
+ from typing import Optional, Tuple, List
6
11
  from uuid import UUID
7
12
  import sempy_labs._icons as icons
13
+ from sempy.fabric.exceptions import FabricHTTPException
14
+ import urllib.parse
8
15
 
9
16
 
10
17
  def create_abfss_path(
@@ -284,26 +291,34 @@ def get_direct_lake_sql_endpoint(dataset: str, workspace: Optional[str] = None)
284
291
  The ID of SQL Endpoint.
285
292
  """
286
293
 
294
+ from sempy_labs.tom import connect_semantic_model
295
+
287
296
  if workspace is None:
288
297
  workspace_id = fabric.get_workspace_id()
289
298
  workspace = fabric.resolve_workspace_name(workspace_id)
290
299
 
291
- dfP = fabric.list_partitions(dataset=dataset, workspace=workspace)
292
- dfP_filt = dfP[dfP["Mode"] == "DirectLake"]
300
+ # dfP = fabric.list_partitions(dataset=dataset, workspace=workspace)
301
+ # dfP_filt = dfP[dfP["Mode"] == "DirectLake"]
293
302
 
294
- if len(dfP_filt) == 0:
295
- raise ValueError(
296
- f"The '{dataset}' semantic model in the '{workspace}' workspace is not in Direct Lake mode."
297
- )
303
+ # if len(dfP_filt) == 0:
304
+ # raise ValueError(
305
+ # f"The '{dataset}' semantic model in the '{workspace}' workspace is not in Direct Lake mode."
306
+ # )
298
307
 
299
- dfE = fabric.list_expressions(dataset=dataset, workspace=workspace)
300
- dfE_filt = dfE[dfE["Name"] == "DatabaseQuery"]
301
- expr = dfE_filt["Expression"].iloc[0]
308
+ with connect_semantic_model(
309
+ dataset=dataset, readonly=True, workspace=workspace
310
+ ) as tom:
311
+ sqlEndpointId = None
312
+ for e in tom.model.Expressions:
313
+ if e.Name == "DatabaseQuery":
314
+ expr = e.Expression
315
+ matches = re.findall(r'"([^"]+)"', expr)
316
+ sqlEndpointId = matches[1]
302
317
 
303
- matches = re.findall(r'"([^"]*)"', expr)
304
- sqlEndpointId = matches[1]
318
+ if sqlEndpointId is None:
319
+ raise ValueError("SQL Endpoint not found.")
305
320
 
306
- return sqlEndpointId
321
+ return sqlEndpointId
307
322
 
308
323
 
309
324
  def generate_embedded_filter(filter: str):
@@ -375,6 +390,7 @@ def save_as_delta_table(
375
390
  dataframe,
376
391
  delta_table_name: str,
377
392
  write_mode: str,
393
+ merge_schema: Optional[bool] = False,
378
394
  lakehouse: Optional[str] = None,
379
395
  workspace: Optional[str] = None,
380
396
  ):
@@ -389,6 +405,8 @@ def save_as_delta_table(
389
405
  The name of the delta table.
390
406
  write_mode : str
391
407
  The write mode for the save operation. Options: 'append', 'overwrite'.
408
+ merge_schema : bool, default=False
409
+ Merges the schemas of the dataframe to the delta table.
392
410
  lakehouse : str, default=None
393
411
  The Fabric lakehouse used by the Direct Lake semantic model.
394
412
  Defaults to None which resolves to the lakehouse attached to the notebook.
@@ -440,7 +458,13 @@ def save_as_delta_table(
440
458
  lakehouse_workspace_id=workspace_id,
441
459
  delta_table_name=delta_table_name,
442
460
  )
443
- spark_df.write.mode(write_mode).format("delta").save(filePath)
461
+
462
+ if merge_schema:
463
+ spark_df.write.mode(write_mode).format("delta").option(
464
+ "mergeSchema", "true"
465
+ ).save(filePath)
466
+ else:
467
+ spark_df.write.mode(write_mode).format("delta").save(filePath)
444
468
  print(
445
469
  f"{icons.green_dot} The dataframe has been saved as the '{delta_table_name}' table in the '{lakehouse}' lakehouse within the '{workspace}' workspace."
446
470
  )
@@ -506,3 +530,326 @@ def resolve_workspace_name_and_id(workspace: Optional[str] = None) -> Tuple[str,
506
530
  workspace_id = fabric.resolve_workspace_id(workspace)
507
531
 
508
532
  return str(workspace), str(workspace_id)
533
+
534
+
535
+ def _extract_json(dataframe: pd.DataFrame) -> dict:
536
+
537
+ payload = dataframe["payload"].iloc[0]
538
+ json_file = _decode_b64(payload)
539
+
540
+ return json.loads(json_file)
541
+
542
+
543
+ def _conv_b64(file):
544
+
545
+ loadJson = json.dumps(file)
546
+ f = base64.b64encode(loadJson.encode("utf-8")).decode("utf-8")
547
+
548
+ return f
549
+
550
+
551
+ def _decode_b64(file, format: Optional[str] = "utf-8"):
552
+
553
+ result = base64.b64decode(file).decode(format)
554
+
555
+ return result
556
+
557
+
558
+ def is_default_semantic_model(dataset: str, workspace: Optional[str] = None) -> bool:
559
+ """
560
+ Identifies whether a semantic model is a default semantic model.
561
+
562
+ Parameters
563
+ ----------
564
+ dataset : str
565
+ The name of the semantic model.
566
+ workspace : str, default=None
567
+ The Fabric workspace name.
568
+ Defaults to None which resolves to the workspace of the attached lakehouse
569
+ or if no lakehouse attached, resolves to the workspace of the notebook.
570
+
571
+ Returns
572
+ -------
573
+ bool
574
+ A True/False value indicating whether the semantic model is a default semantic model.
575
+ """
576
+
577
+ workspace = fabric.resolve_workspace_name(workspace)
578
+
579
+ dfI = fabric.list_items(workspace=workspace)
580
+ filtered_df = dfI.groupby("Display Name").filter(
581
+ lambda x: set(["Warehouse", "SemanticModel"]).issubset(set(x["Type"]))
582
+ or set(["Lakehouse", "SemanticModel"]).issubset(set(x["Type"]))
583
+ )
584
+ default_semantic_models = filtered_df["Display Name"].unique().tolist()
585
+
586
+ return dataset in default_semantic_models
587
+
588
+
589
+ def resolve_item_type(item_id: UUID, workspace: Optional[str] = None) -> str:
590
+ """
591
+ Obtains the item type for a given Fabric Item Id within a Fabric workspace.
592
+
593
+ Parameters
594
+ ----------
595
+ item_id : UUID
596
+ The item/artifact Id.
597
+ workspace : str, default=None
598
+ The Fabric workspace name.
599
+ Defaults to None which resolves to the workspace of the attached lakehouse
600
+ or if no lakehouse attached, resolves to the workspace of the notebook.
601
+
602
+ Returns
603
+ -------
604
+ str
605
+ The item type for the item Id.
606
+ """
607
+
608
+ workspace = fabric.resolve_workspace_name(workspace)
609
+ dfI = fabric.list_items(workspace=workspace)
610
+ dfI_filt = dfI[dfI["Id"] == item_id]
611
+
612
+ if len(dfI_filt) == 0:
613
+ raise ValueError(
614
+ f"Invalid 'item_id' parameter. The '{item_id}' item was not found in the '{workspace}' workspace."
615
+ )
616
+ item_type = dfI_filt["Type"].iloc[0]
617
+
618
+ return item_type
619
+
620
+
621
+ def resolve_dataset_from_report(
622
+ report: str, workspace: Optional[str] = None
623
+ ) -> Tuple[UUID, str, UUID, str]:
624
+ """
625
+ Obtains the basic semantic model properties from which the report's data is sourced.
626
+
627
+ Parameters
628
+ ----------
629
+ report : str
630
+ The name of the Power BI report.
631
+ workspace : str, default=None
632
+ The Fabric workspace name.
633
+ Defaults to None which resolves to the workspace of the attached lakehouse
634
+ or if no lakehouse attached, resolves to the workspace of the notebook.
635
+
636
+ Returns
637
+ -------
638
+ Tuple[UUID, str, UUID, str]
639
+ The semantic model UUID, semantic model name, semantic model workspace UUID, semantic model workspace name
640
+ """
641
+
642
+ workspace = fabric.resolve_workspace_name(workspace)
643
+
644
+ dfR = fabric.list_reports(workspace=workspace)
645
+ dfR_filt = dfR[dfR["Name"] == report]
646
+ if len(dfR_filt) == 0:
647
+ raise ValueError(
648
+ f"{icons.red_dot} The '{report}' report does not exist within the '{workspace}' workspace."
649
+ )
650
+ dataset_id = dfR_filt["Dataset Id"].iloc[0]
651
+ dataset_workspace_id = dfR_filt["Dataset Workspace Id"].iloc[0]
652
+ dataset_workspace = fabric.resolve_workspace_name(dataset_workspace_id)
653
+ dataset_name = resolve_dataset_name(
654
+ dataset_id=dataset_id, workspace=dataset_workspace
655
+ )
656
+
657
+ return dataset_id, dataset_name, dataset_workspace_id, dataset_workspace
658
+
659
+
660
+ def _add_part(target_dict, path, payload):
661
+
662
+ part = {"path": path, "payload": payload, "payloadType": "InlineBase64"}
663
+
664
+ target_dict["definition"]["parts"].append(part)
665
+
666
+
667
+ def resolve_workspace_capacity(workspace: Optional[str] = None) -> Tuple[UUID, str]:
668
+ """
669
+ Obtains the capacity Id and capacity name for a given workspace.
670
+
671
+ Parameters
672
+ ----------
673
+ workspace : str, default=None
674
+ The Fabric workspace name.
675
+ Defaults to None which resolves to the workspace of the attached lakehouse
676
+ or if no lakehouse attached, resolves to the workspace of the notebook.
677
+
678
+ Returns
679
+ -------
680
+ Tuple[UUID, str]
681
+ capacity Id; capacity came.
682
+ """
683
+
684
+ workspace = fabric.resolve_workspace_name(workspace)
685
+ filter_condition = urllib.parse.quote(workspace)
686
+ dfW = fabric.list_workspaces(filter=f"name eq '{filter_condition}'")
687
+ capacity_id = dfW["Capacity Id"].iloc[0]
688
+ dfC = fabric.list_capacities()
689
+ dfC_filt = dfC[dfC["Id"] == capacity_id]
690
+ if len(dfC_filt) == 1:
691
+ capacity_name = dfC_filt["Display Name"].iloc[0]
692
+ else:
693
+ capacity_name = None
694
+
695
+ return capacity_id, capacity_name
696
+
697
+
698
+ def get_capacity_id(workspace: Optional[str] = None) -> UUID:
699
+ """
700
+ Obtains the Capacity Id for a given workspace.
701
+
702
+ Parameters
703
+ ----------
704
+ workspace : str, default=None
705
+ The Fabric workspace name.
706
+ Defaults to None which resolves to the workspace of the attached lakehouse
707
+ or if no lakehouse attached, resolves to the workspace of the notebook.
708
+
709
+ Returns
710
+ -------
711
+ UUID
712
+ The capacity Id.
713
+ """
714
+
715
+ workspace = fabric.resolve_workspace_name(workspace)
716
+ filter_condition = urllib.parse.quote(workspace)
717
+ dfW = fabric.list_workspaces(filter=f"name eq '{filter_condition}'")
718
+ if len(dfW) == 0:
719
+ raise ValueError(f"{icons.red_dot} The '{workspace}' does not exist'.")
720
+
721
+ return dfW["Capacity Id"].iloc[0]
722
+
723
+
724
+ def get_capacity_name(workspace: Optional[str] = None) -> str:
725
+ """
726
+ Obtains the capacity name for a given workspace.
727
+
728
+ Parameters
729
+ ----------
730
+ workspace : str, default=None
731
+ The Fabric workspace name.
732
+ Defaults to None which resolves to the workspace of the attached lakehouse
733
+ or if no lakehouse attached, resolves to the workspace of the notebook.
734
+
735
+ Returns
736
+ -------
737
+ str
738
+ The capacity name.
739
+ """
740
+
741
+ capacity_id = get_capacity_id(workspace)
742
+ dfC = fabric.list_capacities()
743
+ dfC_filt = dfC[dfC["Id"] == capacity_id]
744
+ if len(dfC_filt) == 0:
745
+ raise ValueError(
746
+ f"{icons.red_dot} The '{capacity_id}' capacity Id does not exist."
747
+ )
748
+
749
+ return dfC_filt["Display Name"].iloc[0]
750
+
751
+
752
+ def resolve_capacity_name(capacity_id: Optional[UUID] = None) -> str:
753
+ """
754
+ Obtains the capacity name for a given capacity Id.
755
+
756
+ Parameters
757
+ ----------
758
+ capacity_id : UUID, default=None
759
+ The capacity Id.
760
+ Defaults to None which resolves to the capacity name of the workspace of the attached lakehouse
761
+ or if no lakehouse attached, resolves to the capacity name of the workspace of the notebook.
762
+
763
+ Returns
764
+ -------
765
+ str
766
+ The capacity name.
767
+ """
768
+
769
+ if capacity_id is None:
770
+ return get_capacity_name()
771
+
772
+ dfC = fabric.list_capacities()
773
+ dfC_filt = dfC[dfC["Id"] == capacity_id]
774
+
775
+ if len(dfC_filt) == 0:
776
+ raise ValueError(
777
+ f"{icons.red_dot} The '{capacity_id}' capacity Id does not exist."
778
+ )
779
+
780
+ return dfC_filt["Display Name"].iloc[0]
781
+
782
+
783
+ def retry(sleep_time: int, timeout_error_message: str):
784
+ def decorator(func):
785
+ @wraps(func)
786
+ def wrapper(*args, **kwargs):
787
+ start_time = datetime.datetime.now()
788
+ timeout = datetime.timedelta(minutes=1)
789
+ while datetime.datetime.now() - start_time <= timeout:
790
+ try:
791
+ return func(*args, **kwargs)
792
+ except Exception:
793
+ time.sleep(sleep_time)
794
+ raise TimeoutError(timeout_error_message)
795
+
796
+ return wrapper
797
+
798
+ return decorator
799
+
800
+
801
+ def lro(
802
+ client,
803
+ response,
804
+ status_codes: Optional[List[str]] = [200, 202],
805
+ sleep_time: Optional[int] = 1,
806
+ return_status_code: Optional[bool] = False,
807
+ ):
808
+
809
+ if response.status_code not in status_codes:
810
+ raise FabricHTTPException(response)
811
+ if response.status_code == status_codes[0]:
812
+ if return_status_code:
813
+ result = response.status_code
814
+ else:
815
+ result = response
816
+ if response.status_code == status_codes[1]:
817
+ operationId = response.headers["x-ms-operation-id"]
818
+ response = client.get(f"/v1/operations/{operationId}")
819
+ response_body = json.loads(response.content)
820
+ while response_body["status"] not in ["Succeeded", "Failed"]:
821
+ time.sleep(sleep_time)
822
+ response = client.get(f"/v1/operations/{operationId}")
823
+ response_body = json.loads(response.content)
824
+ if response_body["status"] != "Succeeded":
825
+ raise FabricHTTPException(response)
826
+ if return_status_code:
827
+ result = response.status_code
828
+ else:
829
+ response = client.get(f"/v1/operations/{operationId}/result")
830
+ result = response
831
+
832
+ return result
833
+
834
+
835
+ def pagination(client, response):
836
+
837
+ responses = []
838
+ response_json = response.json()
839
+ responses.append(response_json)
840
+
841
+ # Check for pagination
842
+ continuation_token = response_json.get("continuationToken")
843
+ continuation_uri = response_json.get("continuationUri")
844
+
845
+ # Loop to handle pagination
846
+ while continuation_token is not None:
847
+ response = client.get(continuation_uri)
848
+ response_json = response.json()
849
+ responses.append(response_json)
850
+
851
+ # Update the continuation token and URI for the next iteration
852
+ continuation_token = response_json.get("continuationToken")
853
+ continuation_uri = response_json.get("continuationUri")
854
+
855
+ return responses
sempy_labs/_icons.py CHANGED
@@ -8,17 +8,26 @@ start_bold = "\033[1m"
8
8
  end_bold = "\033[0m"
9
9
  bullet = "\u2022"
10
10
  warning = "⚠️"
11
+ error = "\u274C"
12
+ info = "ℹ️"
11
13
  data_type_mapping = {
12
14
  "string": "String",
13
- "bigint": "Int64",
14
15
  "int": "Int64",
16
+ "tinyint": "Int64",
15
17
  "smallint": "Int64",
18
+ "bigint": "Int64",
16
19
  "boolean": "Boolean",
17
20
  "timestamp": "DateTime",
18
21
  "date": "DateTime",
19
22
  "decimal(38,18)": "Decimal",
23
+ "decimal(19,4)": "Decimal",
20
24
  "double": "Double",
25
+ "float": "Double",
21
26
  }
22
27
  measure_icon = "\u2211"
23
28
  table_icon = "\u229E"
24
29
  column_icon = "\u229F"
30
+ model_bpa_name = "ModelBPA"
31
+ report_bpa_name = "ReportBPA"
32
+ severity_mapping = {warning: "Warning", error: "Error", info: "Info"}
33
+ special_characters = ['"', "/", '"', ":", "|", "<", ">", "*", "?", "'", "!"]