semantic-link-labs 0.12.0__py3-none-any.whl → 0.12.2__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.

@@ -8,6 +8,7 @@ from sempy_labs._helper_functions import (
8
8
  _base_api,
9
9
  _create_dataframe,
10
10
  resolve_workspace_id,
11
+ resolve_item_id,
11
12
  )
12
13
  from uuid import UUID
13
14
  import sempy_labs._icons as icons
@@ -113,7 +114,7 @@ def _get_item_job_instance(url: str) -> pd.DataFrame:
113
114
  }
114
115
  df = _create_dataframe(columns=columns)
115
116
 
116
- response = _base_api(request=url)
117
+ response = _base_api(request=url, client="fabric_sp")
117
118
 
118
119
  rows = []
119
120
  for v in response.json().get("value", []):
@@ -171,11 +172,9 @@ def list_item_schedules(
171
172
  """
172
173
 
173
174
  workspace_id = resolve_workspace_id(workspace)
174
- (item_name, item_id) = resolve_item_name_and_id(
175
- item=item, type=type, workspace=workspace_id
176
- )
175
+ item_id = resolve_item_id(item=item, type=type, workspace=workspace_id)
177
176
 
178
- columns = {
177
+ base_columns = {
179
178
  "Job Schedule Id": "string",
180
179
  "Enabled": "bool",
181
180
  "Created Date Time": "datetime",
@@ -183,48 +182,75 @@ def list_item_schedules(
183
182
  "End Date Time": "string",
184
183
  "Local Time Zone Id": "string",
185
184
  "Type": "string",
186
- "Interval": "string",
187
- "Weekdays": "string",
188
- "Times": "string",
189
185
  "Owner Id": "string",
190
186
  "Owner Type": "string",
191
- "Recurrence": "int",
187
+ }
188
+
189
+ optional_columns = {
190
+ "Occurrence Day of Month": "int_fillna",
191
+ "Occurrence Week Index": "string",
192
+ "Occurrence Weekday": "string",
192
193
  "Occurrence Type": "string",
193
- "Occurrence Day of Month": "int",
194
+ "Interval": "int_fillna",
195
+ "Times": "string",
196
+ "Recurrence": "int_fillna",
197
+ "Weekdays": "string",
194
198
  }
195
- df = _create_dataframe(columns=columns)
196
199
 
197
200
  response = _base_api(
198
- request=f"v1/workspaces/{workspace_id}/items/{item_id}/jobs/{job_type}/schedules"
201
+ request=f"v1/workspaces/{workspace_id}/items/{item_id}/jobs/{job_type}/schedules",
202
+ client="fabric_sp",
199
203
  )
200
204
 
201
205
  rows = []
202
206
  for v in response.json().get("value", []):
203
207
  config = v.get("configuration", {})
204
208
  own = v.get("owner", {})
205
- rows.append(
206
- {
207
- "Job Schedule Id": v.get("id"),
208
- "Enabled": v.get("enabled"),
209
- "Created Date Time": v.get("createdDateTime"),
210
- "Start Date Time": config.get("startDateTime"),
211
- "End Date Time": config.get("endDateTime"),
212
- "Local Time Zone Id": config.get("localTimeZoneId"),
213
- "Type": config.get("type"),
214
- "Interval": config.get("interval"),
215
- "Weekdays": config.get("weekdays"),
216
- "Times": config.get("times"),
217
- "Owner Id": own.get("id"),
218
- "Owner Type": own.get("type"),
219
- "Recurrence": config.get("recurrence"),
220
- "Occurrence Type": config.get("occurence", {}).get("occurrenceType"),
221
- "Occurrence Day of Month": config.get("occurrence", {}).get(
222
- "dayOfMonth"
223
- ),
224
- }
225
- )
209
+ occurrence = config.get("occurrence", {})
210
+ type = config.get("type")
211
+
212
+ row = {
213
+ "Job Schedule Id": v.get("id"),
214
+ "Enabled": v.get("enabled"),
215
+ "Created Date Time": v.get("createdDateTime"),
216
+ "Start Date Time": config.get("startDateTime"),
217
+ "End Date Time": config.get("endDateTime"),
218
+ "Local Time Zone Id": config.get("localTimeZoneId"),
219
+ "Type": type,
220
+ "Owner Id": own.get("id"),
221
+ "Owner Type": own.get("type"),
222
+ }
223
+
224
+ if type == "Cron":
225
+ row["Interval"] = config.get("interval")
226
+ elif type == "Daily":
227
+ row["Times"] = config.get("times")
228
+ elif type == "Weekly":
229
+ row["Times"] = config.get("times")
230
+ row["Weekdays"] = config.get("weekdays")
231
+ elif type == "Monthly":
232
+ occurrence_type = occurrence.get("occurrenceType")
233
+ row["Times"] = config.get("times")
234
+ row["Recurrence"] = config.get("recurrence")
235
+ row["Occurrence Type"] = occurrence_type
236
+
237
+ if occurrence_type == "OrdinalWeekday":
238
+ row["Occurrence Week Index"] = occurrence.get("weekIndex")
239
+ row["Occurrence Weekday"] = occurrence.get("weekday")
240
+ elif occurrence_type == "DayOfMonth":
241
+ row["Occurrence Day of Month"] = occurrence.get("dayOfMonth")
242
+
243
+ rows.append(row)
244
+
245
+ # Build final column map based on what was actually present
246
+ columns = base_columns.copy()
226
247
 
227
248
  if rows:
249
+ # Find which optional columns were actually included in rows
250
+ all_used_columns = set().union(*(r.keys() for r in rows))
251
+ for col in all_used_columns:
252
+ if col in optional_columns:
253
+ columns[col] = optional_columns[col]
228
254
  df = pd.DataFrame(rows, columns=list(columns.keys()))
229
255
  _update_dataframe_datatypes(dataframe=df, column_map=columns)
230
256
 
@@ -267,6 +293,7 @@ def run_on_demand_item_job(
267
293
  method="post",
268
294
  lro_return_status_code=True,
269
295
  status_codes=202,
296
+ client="fabric_sp",
270
297
  )
271
298
 
272
299
  print(f"{icons.green_dot} The '{item_name}' {type.lower()} has been executed.")
@@ -334,6 +361,7 @@ def create_item_schedule_cron(
334
361
  method="post",
335
362
  payload=payload,
336
363
  status_codes=201,
364
+ client="fabric_sp",
337
365
  )
338
366
 
339
367
  print(
@@ -403,6 +431,7 @@ def create_item_schedule_daily(
403
431
  method="post",
404
432
  payload=payload,
405
433
  status_codes=201,
434
+ client="fabric_sp",
406
435
  )
407
436
 
408
437
  print(
@@ -492,6 +521,7 @@ def create_item_schedule_weekly(
492
521
  method="post",
493
522
  payload=payload,
494
523
  status_codes=201,
524
+ client="fabric_sp",
495
525
  )
496
526
 
497
527
  print(
sempy_labs/_labels.py CHANGED
@@ -5,6 +5,9 @@ from typing import Optional, Union
5
5
  from uuid import UUID
6
6
  from sempy.fabric.exceptions import FabricHTTPException
7
7
  from sempy._utils._log import log
8
+ from sempy_labs._helper_functions import (
9
+ _get_url_prefix,
10
+ )
8
11
 
9
12
 
10
13
  @log
@@ -87,12 +90,7 @@ def list_item_labels(workspace: Optional[Union[str, UUID]] = None) -> pd.DataFra
87
90
  if artifact_ids:
88
91
  payload["artifacts"] = [{"artifactId": i} for i in artifact_ids]
89
92
 
90
- client = fabric.PowerBIRestClient()
91
- response = client.get("/v1.0/myorg/capacities")
92
- if response.status_code != 200:
93
- raise FabricHTTPException("Failed to retrieve URL prefix.")
94
- context = response.json().get("@odata.context")
95
- prefix = context.split("/v1.0")[0]
93
+ prefix = _get_url_prefix()
96
94
 
97
95
  response = requests.post(
98
96
  f"{prefix}/metadata/informationProtection/artifacts",
@@ -334,7 +334,10 @@ def measure_dependency_tree(
334
334
 
335
335
  # Visualize the tree structure using RenderTree
336
336
  for pre, _, node in RenderTree(node_dict[measure_name]):
337
- if icons.table_icon in node.custom_property:
337
+ if (
338
+ hasattr(node, "custom_property")
339
+ and icons.table_icon in node.custom_property
340
+ ):
338
341
  print(f"{pre}{node.custom_property}'{node.name}'")
339
342
  else:
340
- print(f"{pre}{node.custom_property}[{node.name}]")
343
+ print(f"{pre}'{node.name}'")
sempy_labs/_warehouses.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from ._helper_functions import (
2
+ resolve_item_id,
2
3
  resolve_workspace_name_and_id,
3
4
  _base_api,
4
5
  _create_dataframe,
@@ -232,3 +233,57 @@ def get_warehouse_columns(
232
233
  )
233
234
 
234
235
  return df
236
+
237
+
238
+ @log
239
+ def get_warehouse_connection_string(
240
+ warehouse: str | UUID,
241
+ workspace: Optional[str | UUID] = None,
242
+ guest_tenant_id: Optional[UUID] = None,
243
+ private_link_type: Optional[str] = None,
244
+ ) -> str:
245
+ """
246
+ Returns the SQL connection string of the specified warehouse.
247
+
248
+ Parameters
249
+ ----------
250
+ warehouse : str | uuid.UUID
251
+ Name or ID of the Fabric warehouse.
252
+ workspace : str | uuid.UUID, default=None
253
+ The Fabric workspace name or ID.
254
+ Defaults to None which resolves to the workspace of the attached lakehouse
255
+ or if no lakehouse attached, resolves to the workspace of the notebook.
256
+ guest_tenant_id : uuid.UUID, default=None
257
+ The guest tenant ID if the end user's tenant is different from the warehouse tenant.
258
+ private_link_type : str, default=None
259
+ Indicates the type of private link this connection string uses. Must be either 'Workspace' or 'None' or left as None.
260
+
261
+ Returns
262
+ -------
263
+ str
264
+ Returns the SQL connection string of the specified warehouse.
265
+ """
266
+ workspace_id = resolve_workspace_id(workspace)
267
+ warehouse_id = resolve_item_id(
268
+ item=warehouse, type="Warehouse", workspace=workspace
269
+ )
270
+
271
+ url = f"/v1/workspaces/{workspace_id}/warehouses/{warehouse_id}/connectionString"
272
+
273
+ if private_link_type is not None and private_link_type not in ["Workspace", "None"]:
274
+ raise ValueError(
275
+ f"{icons.red_dot} private_link_type must be 'Workspace' or 'None' or left as None."
276
+ )
277
+
278
+ if guest_tenant_id or private_link_type:
279
+ params = []
280
+ if guest_tenant_id:
281
+ params.append(f"guestTenantId={guest_tenant_id}")
282
+ if private_link_type:
283
+ params.append(f"privateLinkType={private_link_type}")
284
+ param_str = "?" + "&".join(params)
285
+ url += param_str
286
+
287
+ response = _base_api(request=url, client="fabric_sp")
288
+
289
+ return response.json().get("connectionString")
@@ -94,6 +94,10 @@ from ._tenant_keys import (
94
94
  list_tenant_keys,
95
95
  rotate_tenant_key,
96
96
  )
97
+ from ._sharing_links import (
98
+ remove_all_sharing_links,
99
+ remove_sharing_links,
100
+ )
97
101
 
98
102
  __all__ = [
99
103
  "list_items",
@@ -155,4 +159,6 @@ __all__ = [
155
159
  "delete_tag",
156
160
  "list_tenant_keys",
157
161
  "rotate_tenant_key",
162
+ "remove_all_sharing_links",
163
+ "remove_sharing_links",
158
164
  ]
@@ -285,16 +285,16 @@ def list_capacities(
285
285
  "Users": i.get("users", []),
286
286
  }
287
287
 
288
- if include_tenant_key:
289
- tenant_key = i.get("tenantKey") or {}
290
- row.update(
291
- {
292
- "Tenant Key Id": tenant_key.get("id"),
293
- "Tenant Key Name": tenant_key.get("name"),
294
- }
295
- )
296
-
297
- rows.append(row)
288
+ if include_tenant_key:
289
+ tenant_key = i.get("tenantKey") or {}
290
+ row.update(
291
+ {
292
+ "Tenant Key Id": tenant_key.get("id"),
293
+ "Tenant Key Name": tenant_key.get("name"),
294
+ }
295
+ )
296
+
297
+ rows.append(row)
298
298
 
299
299
  if rows:
300
300
  df = pd.DataFrame(rows, columns=list(columns.keys()))
@@ -0,0 +1,110 @@
1
+ from sempy._utils._log import log
2
+ from sempy_labs._helper_functions import (
3
+ _base_api,
4
+ _is_valid_uuid,
5
+ )
6
+ import sempy_labs._icons as icons
7
+ from typing import List
8
+
9
+
10
+ @log
11
+ def remove_all_sharing_links(sharing_link_type: str = "OrgLink"):
12
+ """
13
+ Deletes all organization sharing links for all Fabric items in the tenant. This action cannot be undone.
14
+
15
+ This is a wrapper function for the following API: `Sharing Links - Remove All Sharing Links <https://learn.microsoft.com/rest/api/fabric/admin/sharing-links/remove-all-sharing-links>`_.
16
+
17
+ Service Principal Authentication is supported (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
18
+
19
+ Parameters
20
+ ----------
21
+ sharing_link_type : str, default='OrgLink'
22
+ Specifies the type of sharing link that is required to be deleted. Additional sharing link types may be added over time.
23
+ """
24
+
25
+ payload = {"sharingLinkType": sharing_link_type}
26
+
27
+ _base_api(
28
+ request="/v1/admin/items/removeAllSharingLinks",
29
+ client="fabric_sp",
30
+ lro_return_status_code=True,
31
+ status_codes=[200, 202],
32
+ method="post",
33
+ payload=payload,
34
+ )
35
+
36
+ print(
37
+ f"{icons.green_dot} All organization sharing links for all Fbric items in the tenant have been deleted."
38
+ )
39
+
40
+
41
+ @log
42
+ def remove_sharing_links(items: List[dict], sharing_link_type: str = "OrgLink"):
43
+ """
44
+ Deletes all organization sharing links for the specified Fabric items. This action cannot be undone.
45
+
46
+ This is a wrapper function for the following API: `Sharing Links - Bulk Remove Sharing Links <https://learn.microsoft.com/rest/api/fabric/admin/sharing-links/bulk-remove-sharing-links>`_.
47
+
48
+ Service Principal Authentication is supported (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
49
+
50
+ Parameters
51
+ ----------
52
+ items : List[dict]
53
+ A list of dictionaries, each representing an item. The 'item' and 'workspace' accepts either name or ID. Examples:
54
+
55
+ [
56
+ {
57
+ "item": "MyReport",
58
+ "type": "Report",
59
+ "workspace": "Workpsace 1",
60
+ },
61
+ {
62
+ "item": "MyReport2",
63
+ "type": "Report",
64
+ "workspace": "Workspace 2",
65
+ },
66
+ ]
67
+
68
+ [
69
+ {
70
+ "item": "fe472f5e-636e-4c10-a1c6-7e9edc0b542a",
71
+ "type": "Report",
72
+ "workspace": "Workspace 1",
73
+ },
74
+ {
75
+ "item": "fe472f5e-636e-4c10-a1c6-7e9edc0b542c",
76
+ "type": "Notebook",
77
+ "workspace": "476fcafe-b514-495d-b13f-ca9a4f0b1d8f",
78
+ },
79
+ ]
80
+
81
+ sharing_link_type : str, default='OrgLink'
82
+ Specifies the type of sharing link that is required to be deleted. Additional sharing link types may be added over time.
83
+ """
84
+
85
+ from sempy_labs.admin._items import _resolve_item_id
86
+
87
+ payload = {"items": [], "sharingLinkType": sharing_link_type}
88
+
89
+ for i in items:
90
+ item = i.get("item")
91
+ type = i.get("type")
92
+ workspace = i.get("workspace")
93
+ if _is_valid_uuid(item):
94
+ payload["items"].append({"id": item, "type": type})
95
+ else:
96
+ item_id = _resolve_item_id(item=item, type=type, workspace=workspace)
97
+ payload["items"].append({"id": item_id, "type": type})
98
+
99
+ _base_api(
100
+ request="/v1/admin/items/bulkRemoveSharingLinks",
101
+ client="fabric_sp",
102
+ method="post",
103
+ payload=payload,
104
+ lro_return_status_code=True,
105
+ status_codes=[200, 202],
106
+ )
107
+
108
+ print(
109
+ f"{icons.green_dot} Organizational sharing links for all specified items have been deleted."
110
+ )
@@ -6,12 +6,18 @@ from ._groups import (
6
6
  add_group_owners,
7
7
  resolve_group_id,
8
8
  renew_group,
9
+ create_group,
10
+ delete_group,
11
+ update_group,
9
12
  )
10
13
  from ._users import (
11
14
  resolve_user_id,
12
15
  get_user,
13
16
  list_users,
14
17
  send_mail,
18
+ create_user,
19
+ delete_user,
20
+ update_user,
15
21
  )
16
22
  from ._teams import (
17
23
  list_teams,
@@ -30,4 +36,10 @@ __all__ = [
30
36
  "list_users",
31
37
  "send_mail",
32
38
  "list_teams",
39
+ "create_user",
40
+ "create_group",
41
+ "delete_user",
42
+ "delete_group",
43
+ "update_user",
44
+ "update_group",
33
45
  ]
@@ -1,6 +1,6 @@
1
1
  import pandas as pd
2
2
  from uuid import UUID
3
- from .._helper_functions import (
3
+ from sempy_labs._helper_functions import (
4
4
  _is_valid_uuid,
5
5
  _base_api,
6
6
  _create_dataframe,
@@ -8,7 +8,7 @@ from .._helper_functions import (
8
8
  )
9
9
  from sempy._utils._log import log
10
10
  import sempy_labs._icons as icons
11
- from typing import List, Literal
11
+ from typing import List, Literal, Optional
12
12
 
13
13
 
14
14
  @log
@@ -424,3 +424,158 @@ def renew_group(group: str | UUID):
424
424
  )
425
425
 
426
426
  print(f"{icons.green_dot} The '{group}' group has been renewed.")
427
+
428
+
429
+ @log
430
+ def create_group(
431
+ display_name: str,
432
+ description: Optional[str] = None,
433
+ mail_enabled: bool = False,
434
+ security_enabled: bool = True,
435
+ mail_nickname: str = None,
436
+ owners: Optional[str | UUID | List[str | UUID]] = None,
437
+ members: Optional[str | UUID | List[str | UUID]] = None,
438
+ ):
439
+ """
440
+ Creates a new group.
441
+
442
+ This is a wrapper function for the following API: `Create group <https://learn.microsoft.com/graph/api/group-post-groups>`_.
443
+
444
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
445
+
446
+ Parameters
447
+ ----------
448
+ display_name : str
449
+ The name of the group.
450
+ description : str, optional
451
+ The description of the group.
452
+ mail_enabled : bool, default=False
453
+ Whether the group is mail-enabled.
454
+ security_enabled : bool, default=True
455
+ Whether the group is security-enabled.
456
+ mail_nickname : str, default=None
457
+ The mail alias for the group.
458
+ owners : str | uuid.UUID | List[str | uuid.UUID], default=None
459
+ The owners of the group.
460
+ members : str | uuid.UUID | List[str | uuid.UUID], default=None
461
+ The members of the group.
462
+ """
463
+ from sempy_labs.graph._users import resolve_user_id
464
+
465
+ payload = {
466
+ "displayName": display_name,
467
+ "description": description,
468
+ "mailEnabled": mail_enabled,
469
+ "securityEnabled": security_enabled,
470
+ "mailNickname": mail_nickname,
471
+ }
472
+
473
+ if owners:
474
+ if isinstance(owners, str):
475
+ owners = [owners]
476
+ user_list = []
477
+ for o in owners:
478
+ user_id = resolve_user_id(o)
479
+ user_list.append(f"https://graph.microsoft.com/v1.0/users/{user_id}")
480
+ payload["owners@odata.bind"] = user_list
481
+ if members:
482
+ if isinstance(members, str):
483
+ members = [members]
484
+ user_list = []
485
+ for m in members:
486
+ user_id = resolve_user_id(m)
487
+ user_list.append(f"https://graph.microsoft.com/v1.0/users/{user_id}")
488
+ payload["members@odata.bind"] = user_list
489
+
490
+ _base_api(
491
+ request="groups",
492
+ client="graph",
493
+ payload=payload,
494
+ method="post",
495
+ status_codes=201,
496
+ )
497
+
498
+ print(f"{icons.green_dot} The '{display_name}' group has been created.")
499
+
500
+
501
+ @log
502
+ def delete_group(group: str | UUID):
503
+ """
504
+ Deletes a group.
505
+
506
+ This is a wrapper function for the following API: `Delete group <https://learn.microsoft.com/graph/api/group-delete>`_.
507
+
508
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
509
+
510
+ Parameters
511
+ ----------
512
+ group : str | uuid.UUID
513
+ The group name or ID.
514
+ """
515
+
516
+ group_id = resolve_group_id(group)
517
+
518
+ _base_api(
519
+ request=f"groups/{group_id}",
520
+ client="graph",
521
+ status_codes=204,
522
+ method="delete",
523
+ )
524
+
525
+ print(f"{icons.green_dot} The '{group}' group has been deleted successfully.")
526
+
527
+
528
+ @log
529
+ def update_group(
530
+ group: str | UUID,
531
+ display_name: Optional[str] = None,
532
+ mail_nickname: Optional[str] = None,
533
+ description: Optional[str] = None,
534
+ security_enabled: Optional[bool] = None,
535
+ ):
536
+ """
537
+ Updates a group's properties.
538
+
539
+ This is a wrapper function for the following API: `Update group <https://learn.microsoft.com/en-us/graph/api/group-update>`_.
540
+
541
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
542
+
543
+ Parameters
544
+ ----------
545
+ group : str | uuid.UUID
546
+ The group name or ID.
547
+ display_name : str, default=None
548
+ The new display name for the group.
549
+ mail_nickname : str, default=None
550
+ The new mail nickname for the group.
551
+ description : str, default=None
552
+ The new description for the group.
553
+ security_enabled : bool, default=None
554
+ Whether the group is security-enabled.
555
+ """
556
+
557
+ group_id = resolve_group_id(group)
558
+
559
+ payload = {}
560
+ if display_name:
561
+ payload["displayName"] = display_name
562
+ if mail_nickname:
563
+ payload["mailNickname"] = mail_nickname
564
+ if description:
565
+ payload["description"] = description
566
+ if security_enabled is not None and isinstance(security_enabled, bool):
567
+ payload["securityEnabled"] = security_enabled
568
+
569
+ if not payload:
570
+ print(f"{icons.info} No properties to update.")
571
+ return
572
+
573
+ _base_api(
574
+ request=f"groups/{group_id}",
575
+ client="graph",
576
+ status_codes=204,
577
+ payload=payload,
578
+ method="patch",
579
+ )
580
+
581
+ print(f"{icons.green_dot} The '{group}' group has been updated successfully.")