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.
- semantic_link_labs-0.7.1.dist-info/METADATA +148 -0
- semantic_link_labs-0.7.1.dist-info/RECORD +111 -0
- {semantic_link_labs-0.6.0.dist-info → semantic_link_labs-0.7.1.dist-info}/WHEEL +1 -1
- sempy_labs/__init__.py +26 -2
- sempy_labs/_ai.py +3 -65
- sempy_labs/_bpa_translation/_translations_am-ET.po +828 -0
- sempy_labs/_bpa_translation/_translations_ar-AE.po +860 -0
- sempy_labs/_bpa_translation/_translations_cs-CZ.po +894 -0
- sempy_labs/_bpa_translation/_translations_da-DK.po +894 -0
- sempy_labs/_bpa_translation/_translations_de-DE.po +933 -0
- sempy_labs/_bpa_translation/_translations_el-GR.po +936 -0
- sempy_labs/_bpa_translation/_translations_es-ES.po +915 -0
- sempy_labs/_bpa_translation/_translations_fa-IR.po +883 -0
- sempy_labs/_bpa_translation/_translations_fr-FR.po +938 -0
- sempy_labs/_bpa_translation/_translations_ga-IE.po +912 -0
- sempy_labs/_bpa_translation/_translations_he-IL.po +855 -0
- sempy_labs/_bpa_translation/_translations_hi-IN.po +892 -0
- sempy_labs/_bpa_translation/_translations_hu-HU.po +910 -0
- sempy_labs/_bpa_translation/_translations_is-IS.po +887 -0
- sempy_labs/_bpa_translation/_translations_it-IT.po +931 -0
- sempy_labs/_bpa_translation/_translations_ja-JP.po +805 -0
- sempy_labs/_bpa_translation/_translations_nl-NL.po +924 -0
- sempy_labs/_bpa_translation/_translations_pl-PL.po +913 -0
- sempy_labs/_bpa_translation/_translations_pt-BR.po +909 -0
- sempy_labs/_bpa_translation/_translations_pt-PT.po +904 -0
- sempy_labs/_bpa_translation/_translations_ru-RU.po +909 -0
- sempy_labs/_bpa_translation/_translations_ta-IN.po +922 -0
- sempy_labs/_bpa_translation/_translations_te-IN.po +896 -0
- sempy_labs/_bpa_translation/_translations_th-TH.po +873 -0
- sempy_labs/_bpa_translation/_translations_zh-CN.po +767 -0
- sempy_labs/_bpa_translation/_translations_zu-ZA.po +916 -0
- sempy_labs/_clear_cache.py +9 -4
- sempy_labs/_generate_semantic_model.py +30 -56
- sempy_labs/_helper_functions.py +361 -14
- sempy_labs/_icons.py +10 -1
- sempy_labs/_list_functions.py +539 -260
- sempy_labs/_model_bpa.py +194 -18
- sempy_labs/_model_bpa_bulk.py +367 -0
- sempy_labs/_model_bpa_rules.py +19 -8
- sempy_labs/_model_dependencies.py +12 -10
- sempy_labs/_one_lake_integration.py +7 -7
- sempy_labs/_query_scale_out.py +61 -96
- sempy_labs/_refresh_semantic_model.py +7 -0
- sempy_labs/_translations.py +154 -1
- sempy_labs/_vertipaq.py +103 -90
- sempy_labs/directlake/__init__.py +5 -1
- sempy_labs/directlake/_directlake_schema_compare.py +27 -31
- sempy_labs/directlake/_directlake_schema_sync.py +55 -66
- sempy_labs/directlake/_dl_helper.py +233 -0
- sempy_labs/directlake/_get_directlake_lakehouse.py +6 -7
- sempy_labs/directlake/_get_shared_expression.py +1 -1
- sempy_labs/directlake/_guardrails.py +17 -13
- sempy_labs/directlake/_update_directlake_partition_entity.py +54 -30
- sempy_labs/directlake/_warm_cache.py +1 -1
- sempy_labs/lakehouse/__init__.py +2 -0
- sempy_labs/lakehouse/_get_lakehouse_tables.py +61 -69
- sempy_labs/lakehouse/_lakehouse.py +66 -9
- sempy_labs/lakehouse/_shortcuts.py +1 -1
- sempy_labs/migration/_create_pqt_file.py +174 -182
- sempy_labs/migration/_migrate_calctables_to_lakehouse.py +236 -268
- sempy_labs/migration/_migrate_calctables_to_semantic_model.py +75 -73
- sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +442 -426
- sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +91 -97
- sempy_labs/migration/_refresh_calc_tables.py +92 -101
- sempy_labs/report/_BPAReportTemplate.json +232 -0
- sempy_labs/report/__init__.py +6 -2
- sempy_labs/report/_bpareporttemplate/.pbi/localSettings.json +9 -0
- sempy_labs/report/_bpareporttemplate/.platform +11 -0
- sempy_labs/report/_bpareporttemplate/StaticResources/SharedResources/BaseThemes/CY24SU06.json +710 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/page.json +11 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/1b08bce3bebabb0a27a8/visual.json +191 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/2f22ddb70c301693c165/visual.json +438 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/3b1182230aa6c600b43a/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/58577ba6380c69891500/visual.json +576 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/a2a8fa5028b3b776c96c/visual.json +207 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/adfd47ef30652707b987/visual.json +506 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/b6a80ee459e716e170b1/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/ce3130a721c020cc3d81/visual.json +513 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/page.json +8 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/visuals/66e60dfb526437cd78d1/visual.json +112 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/page.json +11 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/07deb8bce824e1be37d7/visual.json +513 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0b1c68838818b32ad03b/visual.json +352 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0c171de9d2683d10b930/visual.json +37 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0efa01be0510e40a645e/visual.json +542 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/6bf2f0eb830ab53cc668/visual.json +221 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/88d8141cb8500b60030c/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/a753273590beed656a03/visual.json +576 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/b8fdc82cddd61ac447bc/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/page.json +9 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/visuals/ce8532a7e25020271077/visual.json +38 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/pages.json +10 -0
- sempy_labs/report/_bpareporttemplate/definition/report.json +176 -0
- sempy_labs/report/_bpareporttemplate/definition/version.json +4 -0
- sempy_labs/report/_bpareporttemplate/definition.pbir +14 -0
- sempy_labs/report/_generate_report.py +255 -139
- sempy_labs/report/_report_functions.py +26 -33
- sempy_labs/report/_report_rebind.py +31 -26
- sempy_labs/tom/_model.py +75 -58
- semantic_link_labs-0.6.0.dist-info/METADATA +0 -22
- semantic_link_labs-0.6.0.dist-info/RECORD +0 -54
- sempy_labs/directlake/_fallback.py +0 -60
- {semantic_link_labs-0.6.0.dist-info → semantic_link_labs-0.7.1.dist-info}/LICENSE +0 -0
- {semantic_link_labs-0.6.0.dist-info → semantic_link_labs-0.7.1.dist-info}/top_level.txt +0 -0
sempy_labs/_clear_cache.py
CHANGED
|
@@ -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
|
-
|
|
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>{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
124
|
+
response = client.post(
|
|
125
|
+
f"/v1/workspaces/{workspace_id}/semanticModels",
|
|
126
|
+
json=request_body,
|
|
127
|
+
)
|
|
135
128
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}/
|
|
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(
|
|
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 =
|
|
236
|
+
bimFile = _decode_b64(payload)
|
|
263
237
|
bimJson = json.loads(bimFile)
|
|
264
238
|
|
|
265
239
|
if save_to_file_name is not None:
|
|
266
|
-
|
|
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
|
-
|
|
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
|
|
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
|
sempy_labs/_helper_functions.py
CHANGED
|
@@ -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
|
-
|
|
296
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
304
|
-
|
|
318
|
+
if sqlEndpointId is None:
|
|
319
|
+
raise ValueError("SQL Endpoint not found.")
|
|
305
320
|
|
|
306
|
-
|
|
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
|
-
|
|
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 = ['"', "/", '"', ":", "|", "<", ">", "*", "?", "'", "!"]
|