semantic-link-labs 0.9.11__py3-none-any.whl → 0.10.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.

@@ -17,6 +17,8 @@ import numpy as np
17
17
  from IPython.display import display, HTML
18
18
  import requests
19
19
  import sempy_labs._authentication as auth
20
+ from jsonpath_ng.ext import parse
21
+ from jsonpath_ng.jsonpath import Fields, Index
20
22
 
21
23
 
22
24
  def _build_url(url: str, params: dict) -> str:
@@ -2270,3 +2272,166 @@ def file_exists(file_path: str) -> bool:
2270
2272
  import notebookutils
2271
2273
 
2272
2274
  return len(notebookutils.fs.ls(file_path)) > 0
2275
+
2276
+
2277
+ def generate_number_guid():
2278
+
2279
+ guid = uuid.uuid4()
2280
+ return str(guid.int & ((1 << 64) - 1))
2281
+
2282
+
2283
+ def get_url_content(url: str):
2284
+
2285
+ if "github.com" in url and "/blob/" in url:
2286
+ url = url.replace("github.com", "raw.githubusercontent.com")
2287
+ url = url.replace("/blob/", "/")
2288
+
2289
+ response = requests.get(url)
2290
+ if response.ok:
2291
+ try:
2292
+ data = response.json() # Only works if the response is valid JSON
2293
+ except ValueError:
2294
+ data = response.text # Fallback: get raw text content
2295
+ return data
2296
+ else:
2297
+ print(f"Failed to fetch raw content: {response.status_code}")
2298
+
2299
+
2300
+ def generate_hex(length: int = 10) -> str:
2301
+ """
2302
+ Generate a random hex string of the specified length. Used for generating IDs for report objects (page, visual, bookmark etc.).
2303
+ """
2304
+ import secrets
2305
+
2306
+ return secrets.token_hex(length)
2307
+
2308
+
2309
+ def decode_payload(payload):
2310
+
2311
+ if is_base64(payload):
2312
+ try:
2313
+ decoded_payload = json.loads(base64.b64decode(payload).decode("utf-8"))
2314
+ except Exception:
2315
+ decoded_payload = base64.b64decode(payload)
2316
+ elif isinstance(payload, dict):
2317
+ decoded_payload = payload
2318
+ else:
2319
+ raise ValueError("Payload must be a dictionary or a base64 encoded value.")
2320
+
2321
+ return decoded_payload
2322
+
2323
+
2324
+ def is_base64(s):
2325
+ try:
2326
+ # Add padding if needed
2327
+ s_padded = s + "=" * (-len(s) % 4)
2328
+ decoded = base64.b64decode(s_padded, validate=True)
2329
+ # Optional: check if re-encoding gives the original (excluding padding)
2330
+ return base64.b64encode(decoded).decode().rstrip("=") == s.rstrip("=")
2331
+ except Exception:
2332
+ return False
2333
+
2334
+
2335
+ def get_jsonpath_value(
2336
+ data, path, default=None, remove_quotes=False, fix_true: bool = False
2337
+ ):
2338
+ matches = parse(path).find(data)
2339
+ result = matches[0].value if matches else default
2340
+ if result and remove_quotes and isinstance(result, str):
2341
+ if result.startswith("'") and result.endswith("'"):
2342
+ result = result[1:-1]
2343
+ if fix_true and isinstance(result, str):
2344
+ if result.lower() == "true":
2345
+ result = True
2346
+ elif result.lower() == "false":
2347
+ result = False
2348
+ return result
2349
+
2350
+
2351
+ def set_json_value(payload: dict, json_path: str, json_value: str | dict | List):
2352
+
2353
+ jsonpath_expr = parse(json_path)
2354
+ matches = jsonpath_expr.find(payload)
2355
+
2356
+ if matches:
2357
+ # Update all matches
2358
+ for match in matches:
2359
+ parent = match.context.value
2360
+ path = match.path
2361
+ if isinstance(path, Fields):
2362
+ parent[path.fields[0]] = json_value
2363
+ elif isinstance(path, Index):
2364
+ parent[path.index] = json_value
2365
+ else:
2366
+ # Handle creation
2367
+ parts = json_path.lstrip("$").strip(".").split(".")
2368
+ current = payload
2369
+
2370
+ for i, part in enumerate(parts):
2371
+ is_last = i == len(parts) - 1
2372
+
2373
+ # Detect list syntax like "lockAspect[*]"
2374
+ list_match = re.match(r"(\w+)\[\*\]", part)
2375
+ if list_match:
2376
+ list_key = list_match.group(1)
2377
+ if list_key not in current or not isinstance(current[list_key], list):
2378
+ # Initialize with one dict element
2379
+ current[list_key] = [{}]
2380
+
2381
+ for item in current[list_key]:
2382
+ if is_last:
2383
+ # Last part, assign value
2384
+ item = json_value
2385
+ else:
2386
+ # Proceed to next level
2387
+ if not isinstance(item, dict):
2388
+ raise ValueError(
2389
+ f"Expected dict in list for key '{list_key}', got {type(item)}"
2390
+ )
2391
+ next_part = ".".join(parts[i + 1 :])
2392
+ set_json_value(item, "$." + next_part, json_value)
2393
+ return payload
2394
+ else:
2395
+ if part not in current or not isinstance(current[part], dict):
2396
+ current[part] = {} if not is_last else json_value
2397
+ elif is_last:
2398
+ current[part] = json_value
2399
+ current = current[part]
2400
+
2401
+ return payload
2402
+
2403
+
2404
+ def remove_json_value(path: str, payload: dict, json_path: str, verbose: bool = True):
2405
+
2406
+ if not isinstance(payload, dict):
2407
+ raise ValueError(
2408
+ f"{icons.red_dot} Cannot apply json_path to non-dictionary payload in '{path}'."
2409
+ )
2410
+
2411
+ jsonpath_expr = parse(json_path)
2412
+ matches = jsonpath_expr.find(payload)
2413
+
2414
+ if not matches and verbose:
2415
+ print(
2416
+ f"{icons.red_dot} No match found for '{json_path}' in '{path}'. Skipping."
2417
+ )
2418
+ return payload
2419
+
2420
+ for match in matches:
2421
+ parent = match.context.value
2422
+ path_expr = match.path
2423
+
2424
+ if isinstance(path_expr, Fields):
2425
+ key = path_expr.fields[0]
2426
+ if key in parent:
2427
+ del parent[key]
2428
+ if verbose:
2429
+ print(f"{icons.green_dot} Removed key '{key}' from '{path}'.")
2430
+ elif isinstance(path_expr, Index):
2431
+ index = path_expr.index
2432
+ if isinstance(parent, list) and 0 <= index < len(parent):
2433
+ parent.pop(index)
2434
+ if verbose:
2435
+ print(f"{icons.green_dot} Removed index [{index}] from '{path}'.")
2436
+
2437
+ return payload
@@ -642,49 +642,6 @@ def list_lakehouses(workspace: Optional[str | UUID] = None) -> pd.DataFrame:
642
642
  return df
643
643
 
644
644
 
645
- def list_sql_endpoints(workspace: Optional[str | UUID] = None) -> pd.DataFrame:
646
- """
647
- Shows the SQL endpoints within a workspace.
648
-
649
- Parameters
650
- ----------
651
- workspace : str | uuid.UUID, default=None
652
- The Fabric workspace name or ID.
653
- Defaults to None which resolves to the workspace of the attached lakehouse
654
- or if no lakehouse attached, resolves to the workspace of the notebook.
655
-
656
- Returns
657
- -------
658
- pandas.DataFrame
659
- A pandas dataframe showing the SQL endpoints within a workspace.
660
- """
661
-
662
- columns = {
663
- "SQL Endpoint Id": "string",
664
- "SQL Endpoint Name": "string",
665
- "Description": "string",
666
- }
667
- df = _create_dataframe(columns=columns)
668
-
669
- (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace)
670
-
671
- responses = _base_api(
672
- request=f"/v1/workspaces/{workspace_id}/sqlEndpoints", uses_pagination=True
673
- )
674
-
675
- for r in responses:
676
- for v in r.get("value", []):
677
-
678
- new_data = {
679
- "SQL Endpoint Id": v.get("id"),
680
- "SQL Endpoint Name": v.get("displayName"),
681
- "Description": v.get("description"),
682
- }
683
- df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
684
-
685
- return df
686
-
687
-
688
645
  def list_datamarts(workspace: Optional[str | UUID] = None) -> pd.DataFrame:
689
646
  """
690
647
  Shows the datamarts within a workspace.
sempy_labs/_notebooks.py CHANGED
@@ -159,6 +159,7 @@ def import_notebook_from_web(
159
159
  notebook_content=response.content,
160
160
  workspace=workspace_id,
161
161
  description=description,
162
+ format="ipynb",
162
163
  )
163
164
  elif len(dfI_filt) > 0 and overwrite:
164
165
  print(f"{icons.info} Overwrite of notebooks is currently not supported.")
@@ -202,9 +203,8 @@ def create_notebook(
202
203
  otherwise notebook_content should be GIT friendly format
203
204
  """
204
205
 
205
- notebook_payload = base64.b64encode(notebook_content.encode("utf-8")).decode(
206
- "utf-8"
207
- )
206
+ notebook_payload = base64.b64encode(notebook_content).decode("utf-8")
207
+
208
208
  definition_payload = {
209
209
  "parts": [
210
210
  {
@@ -8,6 +8,8 @@ from sempy_labs._helper_functions import (
8
8
  resolve_workspace_name_and_id,
9
9
  resolve_dataset_name_and_id,
10
10
  delete_item,
11
+ resolve_dataset_id,
12
+ resolve_workspace_id,
11
13
  )
12
14
  import sempy_labs._icons as icons
13
15
  import re
@@ -227,3 +229,102 @@ def update_semantic_model_refresh_schedule(
227
229
  print(
228
230
  f"{icons.green_dot} Refresh schedule for the '{dataset_name}' within the '{workspace_name}' workspace has been updated."
229
231
  )
232
+
233
+
234
+ def list_semantic_model_datasources(
235
+ dataset: str | UUID,
236
+ workspace: Optional[str | UUID] = None,
237
+ expand_details: bool = True,
238
+ ) -> pd.DataFrame:
239
+ """
240
+ Lists the data sources for the specified semantic model.
241
+
242
+ This is a wrapper function for the following API: `Datasets - Get Datasources In Group <https://learn.microsoft.com/rest/api/power-bi/datasets/get-datasources-in-group>`_.
243
+
244
+ Parameters
245
+ ----------
246
+ dataset : str | uuid.UUID
247
+ Name or ID of the semantic model.
248
+ workspace : str | uuid.UUID, default=None
249
+ The workspace name or ID.
250
+ Defaults to None which resolves to the workspace of the attached lakehouse
251
+ or if no lakehouse attached, resolves to the workspace of the notebook.
252
+ expand_details : bool, default=True
253
+ If True, expands the connection details for each data source.
254
+
255
+ Returns
256
+ -------
257
+ pandas.DataFrame
258
+ DataFrame containing the data sources for the specified semantic model.
259
+ """
260
+
261
+ workspace_id = resolve_workspace_id(workspace)
262
+ dataset_id = resolve_dataset_id(dataset, workspace_id)
263
+
264
+ if expand_details:
265
+ columns = {
266
+ "Datasource Type": "str",
267
+ "Connection Server": "str",
268
+ "Connection Database": "str",
269
+ "Connection Path": "str",
270
+ "Connection Account": "str",
271
+ "Connection Domain": "str",
272
+ "Connection Kind": "str",
273
+ "Connection Email Address": "str",
274
+ "Connection URL": "str",
275
+ "Connection Class Info": "str",
276
+ "Connection Login Server": "str",
277
+ "Datasource Id": "str",
278
+ "Gateway Id": "str",
279
+ }
280
+ else:
281
+ columns = {
282
+ "Datasource Type": "str",
283
+ "Connection Details": "str",
284
+ "Datasource Id": "str",
285
+ "Gateway Id": "str",
286
+ }
287
+
288
+ df = _create_dataframe(columns)
289
+
290
+ response = _base_api(
291
+ request=f"/v1.0/myorg/groups/{workspace_id}/datasets/{dataset_id}/datasources",
292
+ client="fabric_sp",
293
+ )
294
+
295
+ dfs = []
296
+ for item in response.json().get("value", []):
297
+ ds_type = item.get("datasourceType")
298
+ conn_details = item.get("connectionDetails", {})
299
+ ds_id = item.get("datasourceId")
300
+ gateway_id = item.get("gatewayId")
301
+ if expand_details:
302
+ new_data = {
303
+ "Datasource Type": ds_type,
304
+ "Connection Server": conn_details.get("server"),
305
+ "Connection Database": conn_details.get("database"),
306
+ "Connection Path": conn_details.get("path"),
307
+ "Connection Account": conn_details.get("account"),
308
+ "Connection Domain": conn_details.get("domain"),
309
+ "Connection Kind": conn_details.get("kind"),
310
+ "Connection Email Address": conn_details.get("emailAddress"),
311
+ "Connection URL": conn_details.get("url"),
312
+ "Connection Class Info": conn_details.get("classInfo"),
313
+ "Connection Login Server": conn_details.get("loginServer"),
314
+ "Datasource Id": ds_id,
315
+ "Gateway Id": gateway_id,
316
+ }
317
+ dfs.append(pd.DataFrame(new_data, index=[0]))
318
+ else:
319
+ new_data = {
320
+ "Datasource Type": ds_type,
321
+ "Connection Details": conn_details,
322
+ "Datasource Id": ds_id,
323
+ "Gateway Id": gateway_id,
324
+ }
325
+ dfs.append(pd.DataFrame([new_data]))
326
+
327
+ if dfs:
328
+ df = pd.concat(dfs, ignore_index=True)
329
+
330
+ return df
sempy_labs/_sql.py CHANGED
@@ -82,7 +82,7 @@ class ConnectBase:
82
82
  )
83
83
 
84
84
  # Set up the connection string
85
- access_token = SynapseTokenProvider()()
85
+ access_token = SynapseTokenProvider()("sql")
86
86
  tokenstruct = _bytes2mswin_bstr(access_token.encode())
87
87
  if endpoint_type == "sqldatabase":
88
88
  conn_str = f"DRIVER={{ODBC Driver 18 for SQL Server}};SERVER={tds_endpoint};DATABASE={resource_name}-{resource_id};Encrypt=Yes;"
@@ -0,0 +1,185 @@
1
+ from typing import Optional, Literal
2
+ from uuid import UUID
3
+ import pandas as pd
4
+ from sempy_labs._helper_functions import (
5
+ _base_api,
6
+ _create_dataframe,
7
+ resolve_workspace_name_and_id,
8
+ resolve_item_name_and_id,
9
+ _update_dataframe_datatypes,
10
+ )
11
+ import sempy_labs._icons as icons
12
+
13
+
14
+ def list_sql_endpoints(workspace: Optional[str | UUID] = None) -> pd.DataFrame:
15
+ """
16
+ Shows the SQL endpoints within a workspace.
17
+
18
+ Parameters
19
+ ----------
20
+ workspace : str | uuid.UUID, default=None
21
+ The Fabric workspace name or ID.
22
+ Defaults to None which resolves to the workspace of the attached lakehouse
23
+ or if no lakehouse attached, resolves to the workspace of the notebook.
24
+
25
+ Returns
26
+ -------
27
+ pandas.DataFrame
28
+ A pandas dataframe showing the SQL endpoints within a workspace.
29
+ """
30
+
31
+ columns = {
32
+ "SQL Endpoint Id": "string",
33
+ "SQL Endpoint Name": "string",
34
+ "Description": "string",
35
+ }
36
+ df = _create_dataframe(columns=columns)
37
+
38
+ (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace)
39
+
40
+ responses = _base_api(
41
+ request=f"/v1/workspaces/{workspace_id}/sqlEndpoints", uses_pagination=True
42
+ )
43
+
44
+ for r in responses:
45
+ for v in r.get("value", []):
46
+
47
+ new_data = {
48
+ "SQL Endpoint Id": v.get("id"),
49
+ "SQL Endpoint Name": v.get("displayName"),
50
+ "Description": v.get("description"),
51
+ }
52
+ df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
53
+
54
+ return df
55
+
56
+
57
+ def refresh_sql_endpoint_metadata(
58
+ item: str | UUID,
59
+ type: Literal["Lakehouse", "MirroredDatabase"],
60
+ workspace: Optional[str | UUID] = None,
61
+ tables: dict[str, list[str]] = None,
62
+ ) -> pd.DataFrame:
63
+ """
64
+ Refreshes the metadata of a SQL endpoint.
65
+
66
+ This is a wrapper function for the following API: `Items - Refresh Sql Endpoint Metadata <https://learn.microsoft.com/rest/api/fabric/sqlendpoint/items/refresh-sql-endpoint-metadata>`_.
67
+
68
+ Parameters
69
+ ----------
70
+ item : str | uuid.UUID
71
+ The name or ID of the item (Lakehouse or MirroredDatabase).
72
+ type : Literal['Lakehouse', 'MirroredDatabase']
73
+ The type of the item. Must be 'Lakehouse' or 'MirroredDatabase'.
74
+ workspace : str | uuid.UUID, default=None
75
+ The Fabric workspace name or ID.
76
+ Defaults to None which resolves to the workspace of the attached lakehouse
77
+ or if no lakehouse attached, resolves to the workspace of the notebook.
78
+ tables : dict[str, list[str]], default=None
79
+ A dictionary where the keys are schema names and the values are lists of table names.
80
+ If empty, all table metadata will be refreshed.
81
+
82
+ Example:
83
+ {
84
+ "dbo": ["DimDate", "DimGeography"],
85
+ "sls": ["FactSales", "FactBudget"],
86
+ }
87
+
88
+ Returns
89
+ -------
90
+ pandas.DataFrame
91
+ A pandas dataframe showing the status of the metadata refresh operation.
92
+ """
93
+
94
+ (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace)
95
+
96
+ (item_name, item_id) = resolve_item_name_and_id(
97
+ item=item, type=type, workspace=workspace
98
+ )
99
+
100
+ if type == "Lakehouse":
101
+ response = _base_api(
102
+ request=f"/v1/workspaces/{workspace_id}/lakehouses/{item_id}",
103
+ client="fabric_sp",
104
+ )
105
+ sql_endpoint_id = (
106
+ response.json()
107
+ .get("properties", {})
108
+ .get("sqlEndpointProperties", {})
109
+ .get("id")
110
+ )
111
+ elif type == "MirroredDatabase":
112
+ response = _base_api(
113
+ request=f"/v1/workspaces/{workspace_id}/mirroredDatabases/{item_id}",
114
+ client="fabric_sp",
115
+ )
116
+ sql_endpoint_id = (
117
+ response.json()
118
+ .get("properties", {})
119
+ .get("sqlEndpointProperties", {})
120
+ .get("id")
121
+ )
122
+ else:
123
+ raise ValueError("Invalid type. Must be 'Lakehouse' or 'MirroredDatabase'.")
124
+
125
+ payload = {}
126
+ if tables:
127
+ payload = {
128
+ "tableDefinitions": [
129
+ {"schema": schema, "tableNames": tables}
130
+ for schema, tables in tables.items()
131
+ ]
132
+ }
133
+
134
+ result = _base_api(
135
+ request=f"v1/workspaces/{workspace_id}/sqlEndpoints/{sql_endpoint_id}/refreshMetadata?preview=true",
136
+ method="post",
137
+ status_codes=[200, 202],
138
+ lro_return_json=True,
139
+ payload=payload,
140
+ )
141
+
142
+ columns = {
143
+ "Table Name": "string",
144
+ "Status": "string",
145
+ "Start Time": "datetime",
146
+ "End Time": "datetime",
147
+ "Last Successful Sync Time": "datetime",
148
+ "Error Code": "string",
149
+ "Error Message": "string",
150
+ }
151
+
152
+ df = pd.json_normalize(result)
153
+
154
+ # Extract error code and message, set to None if no error
155
+ df['Error Code'] = df.get('error.errorCode', None)
156
+ df['Error Message'] = df.get('error.message', None)
157
+
158
+ # Friendly column renaming
159
+ df.rename(columns={
160
+ 'tableName': 'Table Name',
161
+ 'startDateTime': 'Start Time',
162
+ 'endDateTime': 'End Time',
163
+ 'status': 'Status',
164
+ 'lastSuccessfulSyncDateTime': 'Last Successful Sync Time'
165
+ }, inplace=True)
166
+
167
+ # Drop the original 'error' column if present
168
+ df.drop(columns=[col for col in ['error'] if col in df.columns], inplace=True)
169
+
170
+ # Optional: Reorder columns
171
+ column_order = [
172
+ 'Table Name', 'Status', 'Start Time', 'End Time',
173
+ 'Last Successful Sync Time', 'Error Code', 'Error Message'
174
+ ]
175
+ df = df[column_order]
176
+
177
+ _update_dataframe_datatypes(df, columns)
178
+
179
+ printout = f"{icons.green_dot} The metadata of the SQL endpoint for the '{item_name}' {type.lower()} within the '{workspace_name}' workspace has been refreshed"
180
+ if tables:
181
+ print(f"{printout} for the following tables: {tables}.")
182
+ else:
183
+ print(f"{printout} for all tables.")
184
+
185
+ return df
@@ -0,0 +1,42 @@
1
+ from sempy_labs.lakehouse._blobs import _request_blob_api
2
+ from sempy_labs._helper_functions import (
3
+ _xml_to_dict,
4
+ )
5
+ from datetime import datetime, timedelta, timezone
6
+ import xml.etree.ElementTree as ET
7
+
8
+
9
+ def get_user_delegation_key():
10
+ """
11
+ Gets a key that can be used to sign a user delegation SAS (shared access signature). A user delegation SAS grants access to Azure Blob Storage resources by using Microsoft Entra credentials.
12
+
13
+ This is a wrapper function for the following API: `Get User Delegation Key <https://learn.microsoft.com/rest/api/storageservices/get-user-delegation-key>`_.
14
+
15
+ Returns
16
+ -------
17
+ str
18
+ The user delegation key value.
19
+ """
20
+
21
+ utc_now = datetime.now(timezone.utc)
22
+ start_time = utc_now + timedelta(minutes=2)
23
+ expiry_time = start_time + timedelta(minutes=60)
24
+ start_str = start_time.strftime("%Y-%m-%dT%H:%M:%SZ")
25
+ expiry_str = expiry_time.strftime("%Y-%m-%dT%H:%M:%SZ")
26
+
27
+ payload = f"""<?xml version="1.0" encoding="utf-8"?>
28
+ <KeyInfo>
29
+ <Start>{start_str}</Start>
30
+ <Expiry>{expiry_str}</Expiry>
31
+ </KeyInfo>"""
32
+
33
+ response = _request_blob_api(
34
+ request="?restype=service&comp=userdelegationkey",
35
+ method="post",
36
+ payload=payload,
37
+ )
38
+
39
+ root = ET.fromstring(response.content)
40
+ response_json = _xml_to_dict(root)
41
+
42
+ return response_json.get("UserDelegationKey", {}).get("Value", None)
sempy_labs/_vpax.py CHANGED
@@ -16,6 +16,7 @@ from sempy_labs._helper_functions import (
16
16
  file_exists,
17
17
  create_abfss_path_from_path,
18
18
  )
19
+ from sempy._utils._log import log
19
20
  import sempy_labs._icons as icons
20
21
  import zipfile
21
22
  import requests
@@ -134,6 +135,7 @@ def init_vertipaq_analyzer():
134
135
  _vpa_initialized = True
135
136
 
136
137
 
138
+ @log
137
139
  def create_vpax(
138
140
  dataset: str | UUID,
139
141
  workspace: Optional[str | UUID] = None,
@@ -111,9 +111,9 @@ def update_direct_lake_model_connection(
111
111
 
112
112
  Parameters
113
113
  ----------
114
- dataset : str | UUID
114
+ dataset : str | uuid.UUID
115
115
  Name or ID of the semantic model.
116
- workspace : str | UUID, default=None
116
+ workspace : str | uuid.UUID, default=None
117
117
  The Fabric workspace name or ID in which the semantic model exists.
118
118
  Defaults to None which resolves to the workspace of the attached lakehouse
119
119
  or if no lakehouse attached, resolves to the workspace of the notebook.
@@ -122,7 +122,7 @@ def update_direct_lake_model_connection(
122
122
  Defaults to None which resolves to the lakehouse attached to the notebook.
123
123
  source_type : str, default="Lakehouse"
124
124
  The type of source for the Direct Lake semantic model. Valid options: "Lakehouse", "Warehouse".
125
- source_workspace : str | UUID, default=None
125
+ source_workspace : str | uuid.UUID, default=None
126
126
  The Fabric workspace name or ID used by the lakehouse/warehouse.
127
127
  Defaults to None which resolves to the workspace of the attached lakehouse
128
128
  or if no lakehouse attached, resolves to the workspace of the notebook.
@@ -20,7 +20,6 @@ from sempy_labs.lakehouse._shortcuts import (
20
20
  from sempy_labs.lakehouse._blobs import (
21
21
  recover_lakehouse_object,
22
22
  list_blobs,
23
- get_user_delegation_key,
24
23
  )
25
24
  from sempy_labs.lakehouse._livy_sessions import (
26
25
  list_livy_sessions,
@@ -51,5 +50,4 @@ __all__ = [
51
50
  "delete_lakehouse",
52
51
  "update_lakehouse",
53
52
  "load_table",
54
- "get_user_delegation_key",
55
53
  ]
@@ -244,40 +244,3 @@ def recover_lakehouse_object(
244
244
  print(
245
245
  f"{icons.red_dot} An error occurred while recovering the '{blob_name}' blob: {e}"
246
246
  )
247
-
248
-
249
- def get_user_delegation_key():
250
- """
251
- Gets a key that can be used to sign a user delegation SAS (shared access signature). A user delegation SAS grants access to Azure Blob Storage resources by using Microsoft Entra credentials.
252
-
253
- This is a wrapper function for the following API: `Get User Delegation Key <https://learn.microsoft.com/rest/api/storageservices/get-user-delegation-key>`_.
254
-
255
- Returns
256
- -------
257
- str
258
- The user delegation key value.
259
- """
260
-
261
- from datetime import datetime, timedelta, timezone
262
-
263
- utc_now = datetime.now(timezone.utc)
264
- start_time = utc_now + timedelta(minutes=2)
265
- expiry_time = start_time + timedelta(minutes=60)
266
- start_str = start_time.strftime("%Y-%m-%dT%H:%M:%SZ")
267
- expiry_str = expiry_time.strftime("%Y-%m-%dT%H:%M:%SZ")
268
-
269
- payload = f"""<?xml version="1.0" encoding="utf-8"?>
270
- <KeyInfo>
271
- <Start>{start_str}</Start>
272
- <Expiry>{expiry_str}</Expiry>
273
- </KeyInfo>"""
274
-
275
- response = _request_blob_api(
276
- request="?restype=service&comp=userdelegationkey",
277
- method="post",
278
- payload=payload,
279
- )
280
-
281
- root = ET.fromstring(response.content)
282
- response_json = _xml_to_dict(root)
283
- return response_json.get("UserDelegationKey", {}).get("Value", None)