semantic-link-labs 0.8.5__py3-none-any.whl → 0.8.7__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 (36) hide show
  1. {semantic_link_labs-0.8.5.dist-info → semantic_link_labs-0.8.7.dist-info}/METADATA +15 -6
  2. {semantic_link_labs-0.8.5.dist-info → semantic_link_labs-0.8.7.dist-info}/RECORD +36 -30
  3. {semantic_link_labs-0.8.5.dist-info → semantic_link_labs-0.8.7.dist-info}/WHEEL +1 -1
  4. sempy_labs/__init__.py +37 -6
  5. sempy_labs/_authentication.py +108 -0
  6. sempy_labs/_connections.py +355 -176
  7. sempy_labs/_dataflows.py +0 -1
  8. sempy_labs/_gateways.py +439 -0
  9. sempy_labs/_generate_semantic_model.py +51 -30
  10. sempy_labs/_git.py +13 -5
  11. sempy_labs/_helper_functions.py +14 -3
  12. sempy_labs/_list_functions.py +1 -1
  13. sempy_labs/_model_auto_build.py +4 -2
  14. sempy_labs/_model_bpa.py +2 -15
  15. sempy_labs/_model_bpa_bulk.py +14 -6
  16. sempy_labs/_model_dependencies.py +3 -1
  17. sempy_labs/_refresh_semantic_model.py +6 -0
  18. sempy_labs/admin/__init__.py +19 -9
  19. sempy_labs/admin/_basic_functions.py +475 -548
  20. sempy_labs/admin/_external_data_share.py +97 -0
  21. sempy_labs/admin/_git.py +69 -0
  22. sempy_labs/admin/_items.py +264 -0
  23. sempy_labs/admin/_scanner.py +104 -0
  24. sempy_labs/directlake/_dl_helper.py +6 -2
  25. sempy_labs/directlake/_get_shared_expression.py +5 -35
  26. sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +3 -2
  27. sempy_labs/migration/_create_pqt_file.py +4 -2
  28. sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +4 -2
  29. sempy_labs/report/_generate_report.py +10 -4
  30. sempy_labs/report/_report_bpa.py +1 -0
  31. sempy_labs/report/_report_helper.py +58 -0
  32. sempy_labs/report/_report_list_functions.py +2 -0
  33. sempy_labs/report/_reportwrapper.py +358 -175
  34. sempy_labs/tom/_model.py +2 -1
  35. {semantic_link_labs-0.8.5.dist-info → semantic_link_labs-0.8.7.dist-info}/LICENSE +0 -0
  36. {semantic_link_labs-0.8.5.dist-info → semantic_link_labs-0.8.7.dist-info}/top_level.txt +0 -0
@@ -4,178 +4,150 @@ from uuid import UUID
4
4
  import sempy_labs._icons as icons
5
5
  from sempy.fabric.exceptions import FabricHTTPException
6
6
  from sempy_labs._helper_functions import (
7
- resolve_workspace_name_and_id,
8
7
  pagination,
8
+ _is_valid_uuid,
9
+ _build_url,
9
10
  )
10
11
  import numpy as np
11
12
  import pandas as pd
12
- import time
13
+ from dateutil.parser import parse as dtparser
13
14
  import urllib.parse
14
- from datetime import datetime
15
15
 
16
16
 
17
17
  def list_workspaces(
18
- top: Optional[int] = 5000,
19
- filter: Optional[str] = None,
20
- skip: Optional[int] = None,
18
+ capacity: Optional[str | UUID] = None,
19
+ workspace: Optional[str | UUID] = None,
20
+ workspace_state: Optional[str] = None,
21
+ workspace_type: Optional[str] = None,
22
+ **kwargs,
21
23
  ) -> pd.DataFrame:
22
24
  """
23
25
  Lists workspaces for the organization. This function is the admin version of list_workspaces.
24
26
 
25
- This is a wrapper function for the following API: `Admin - Groups GetGroupsAsAdmin <https://learn.microsoft.com/rest/api/power-bi/admin/groups-get-groups-as-admin>`_.
27
+ This is a wrapper function for the following API: `Workspaces - List Workspaces - REST API (Admin) <https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/list-workspaces>`_.
26
28
 
27
29
  Parameters
28
30
  ----------
29
- top : int, default=5000
30
- Returns only the first n results. This parameter is mandatory and must be in the range of 1-5000.
31
- filter : str, default=None
32
- Returns a subset of a results based on `Odata filter <https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_SystemQueryOptions>`_ query parameter condition.
33
- skip : int, default=None
34
- Skips the first n results. Use with top to fetch results beyond the first 5000.
31
+ capacity : str | UUID, default=None
32
+ Returns only the workspaces in the specified Capacity.
33
+ workspace : str | UUID, default=None
34
+ Returns the workspace with the specific name.
35
+ workspace_state : str, default=None
36
+ Return only the workspace with the requested state. You can find the possible states in `Workspace States <https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/list-workspaces?tabs=HTTP#workspacestate>`_.
37
+ workspace_type : str, default=None
38
+ Return only the workspace of the specific type. You can find the possible types in `Workspace Types <https://learn.microsoft.com/en-us/rest/api/fabric/admin/workspaces/list-workspaces?tabs=HTTP#workspacetype>`_.
35
39
 
36
40
  Returns
37
41
  -------
38
42
  pandas.DataFrame
39
43
  A pandas dataframe showing a list of workspaces for the organization.
40
44
  """
45
+ if "filter" in kwargs:
46
+ print(
47
+ "The 'filter' parameter has been deprecated. Please remove this parameter from the function going forward."
48
+ )
49
+ del kwargs["filter"]
50
+
51
+ if "top" in kwargs:
52
+ print(
53
+ "The 'top' parameter has been deprecated. Please remove this parameter from the function going forward."
54
+ )
55
+ del kwargs["top"]
56
+
57
+ if "skip" in kwargs:
58
+ print(
59
+ "The 'skip' parameter has been deprecated. Please remove this parameter from the function going forward."
60
+ )
61
+ del kwargs["skip"]
62
+
63
+ client = fabric.FabricRestClient()
41
64
 
42
65
  df = pd.DataFrame(
43
66
  columns=[
44
67
  "Id",
45
- "Is Read Only",
46
- "Is On Dedicated Capacity",
47
- "Type",
48
68
  "Name",
69
+ "State",
70
+ "Type",
49
71
  "Capacity Id",
50
- "Default Dataset Storage Format",
51
- "Pipeline Id",
52
- "Has Workspace Level Settings",
53
72
  ]
54
73
  )
55
74
 
56
- url = f"/v1.0/myorg/admin/groups?$top={top}"
57
- if skip is not None:
58
- url = f"{url}&$skip={skip}"
59
- if filter is not None:
60
- url = f"{url}&$filter={filter}"
75
+ url = "/v1/admin/workspaces"
76
+ params = {}
61
77
 
62
- client = fabric.PowerBIRestClient()
63
- response = client.get(url)
78
+ if capacity is not None:
79
+ params["capacityId"] = _resolve_capacity_name_and_id(capacity)[1]
64
80
 
65
- if response.status_code != 200:
66
- raise FabricHTTPException(response)
81
+ if workspace is not None and not _is_valid_uuid(workspace):
82
+ params["name"] = workspace
67
83
 
68
- for v in response.json().get("value", []):
69
- capacity_id = v.get("capacityId")
70
- if capacity_id:
71
- capacity_id = capacity_id.lower()
72
- new_data = {
73
- "Id": v.get("id"),
74
- "Is Read Only": v.get("isReadOnly"),
75
- "Is On Dedicated Capacity": v.get("isOnDedicatedCapacity"),
76
- "Capacity Id": capacity_id,
77
- "Default Dataset Storage Format": v.get("defaultDatasetStorageFormat"),
78
- "Type": v.get("type"),
79
- "Name": v.get("name"),
80
- "State": v.get("state"),
81
- "Pipeline Id": v.get("pipelineId"),
82
- "Has Workspace Level Settings": v.get("hasWorkspaceLevelSettings"),
83
- }
84
- df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
84
+ if workspace_state is not None:
85
+ params["state"] = workspace_state
85
86
 
86
- bool_cols = [
87
- "Is Read Only",
88
- "Is On Dedicated Capacity",
89
- "Has Workspace Level Settings",
90
- ]
91
- df[bool_cols] = df[bool_cols].astype(bool)
87
+ if workspace_type is not None:
88
+ params["type"] = workspace_type
92
89
 
93
- return df
90
+ url = _build_url(url, params)
94
91
 
92
+ response = client.get(path_or_url=url)
95
93
 
96
- def assign_workspaces_to_capacity(
97
- source_capacity: str,
98
- target_capacity: str,
99
- workspace: Optional[str | List[str]] = None,
100
- ):
101
- """
102
- Assigns a workspace to a capacity. This function is the admin version.
94
+ if response.status_code != 200:
95
+ raise FabricHTTPException(response)
103
96
 
104
- This is a wrapper function for the following API: `Admin - Capacities AssignWorkspacesToCapacity <https://learn.microsoft.com/rest/api/power-bi/admin/capacities-assign-workspaces-to-capacity>`_.
97
+ responsePaginated = pagination(client, response)
105
98
 
106
- Parameters
107
- ----------
108
- source_capacity : str
109
- The name of the source capacity.
110
- target_capacity : str
111
- The name of the target capacity.
112
- workspace : str | List[str], default=None
113
- The name of the workspace(s).
114
- Defaults to None which resolves to migrating all workspaces within the source capacity to the target capacity.
115
- """
99
+ workspaces = []
116
100
 
117
- if isinstance(workspace, str):
118
- workspace = [workspace]
101
+ for r in responsePaginated:
102
+ workspaces = workspaces + r.get("workspaces", [])
119
103
 
120
- dfC = list_capacities()
121
- dfC_filt = dfC[dfC["Capacity Name"] == source_capacity]
122
- source_capacity_id = dfC_filt["Capacity Id"].iloc[0]
104
+ if len(workspaces) > 0:
105
+ df = pd.DataFrame(workspaces)
106
+ df.rename(
107
+ columns={
108
+ "id": "Id",
109
+ "name": "Name",
110
+ "state": "State",
111
+ "type": "Type",
112
+ "capacityId": "Capacity Id",
113
+ },
114
+ inplace=True,
115
+ )
123
116
 
124
- dfC_filt = dfC[dfC["Capacity Name"] == target_capacity]
125
- target_capacity_id = dfC_filt["Capacity Id"].iloc[0]
117
+ df["Capacity Id"] = df["Capacity Id"].str.lower()
126
118
 
127
- if workspace is None:
128
- dfW = list_workspaces()
129
- dfW = dfW[dfW["Capacity Id"].str.upper() == source_capacity_id.upper()]
130
- workspaces = dfW["Id"].tolist()
131
- else:
132
- dfW = list_workspaces()
133
- workspaces = dfW[dfW["Name"].isin(workspace)]["Id"].tolist()
134
-
135
- workspaces = np.array(workspaces)
136
- batch_size = 999
137
- for i in range(0, len(workspaces), batch_size):
138
- batch = workspaces[i : i + batch_size].tolist()
139
- request_body = {
140
- "capacityMigrationAssignments": [
141
- {
142
- "targetCapacityObjectId": target_capacity_id.upper(),
143
- "workspacesToAssign": batch,
144
- }
145
- ]
146
- }
119
+ if workspace is not None and _is_valid_uuid(workspace):
120
+ df = df[df["Id"] == workspace.lower()]
147
121
 
148
- client = fabric.PowerBIRestClient()
149
- response = client.post(
150
- "/v1.0/myorg/admin/capacities/AssignWorkspaces",
151
- json=request_body,
152
- )
153
-
154
- if response.status_code != 200:
155
- raise FabricHTTPException(response)
156
- print(
157
- f"{icons.green_dot} The workspaces have been assigned to the '{target_capacity}' capacity."
158
- )
122
+ return df
159
123
 
160
124
 
161
- def list_capacities() -> pd.DataFrame:
125
+ def list_capacities(
126
+ capacity: Optional[str | UUID] = None,
127
+ ) -> pd.DataFrame:
162
128
  """
163
129
  Shows the a list of capacities and their properties. This function is the admin version.
164
130
 
165
131
  This is a wrapper function for the following API: `Admin - Get Capacities As Admin <https://learn.microsoft.com/rest/api/power-bi/admin/get-capacities-as-admin>`_.
166
132
 
133
+ Parameters
134
+ ----------
135
+ capacity : str | UUID, default=None
136
+ Capacity name or id to filter.
137
+
167
138
  Returns
168
139
  -------
169
140
  pandas.DataFrame
170
141
  A pandas dataframe showing the capacities and their properties
171
142
  """
143
+ client = fabric.FabricRestClient()
172
144
 
173
145
  df = pd.DataFrame(
174
146
  columns=["Capacity Id", "Capacity Name", "Sku", "Region", "State", "Admins"]
175
147
  )
176
148
 
177
- client = fabric.PowerBIRestClient()
178
149
  response = client.get("/v1.0/myorg/admin/capacities")
150
+
179
151
  if response.status_code != 200:
180
152
  raise FabricHTTPException(response)
181
153
 
@@ -193,85 +165,108 @@ def list_capacities() -> pd.DataFrame:
193
165
  }
194
166
  df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
195
167
 
168
+ if capacity is not None:
169
+ if _is_valid_uuid(capacity):
170
+ df = df[df["Capacity Id"] == capacity.lower()]
171
+ else:
172
+ df = df[df["Capacity Name"] == capacity]
173
+
196
174
  return df
197
175
 
198
176
 
199
- def list_tenant_settings() -> pd.DataFrame:
177
+ def assign_workspaces_to_capacity(
178
+ source_capacity: Optional[str | UUID] = None,
179
+ target_capacity: Optional[str | UUID] = None,
180
+ workspace: Optional[str | List[str] | UUID | List[UUID]] = None,
181
+ ):
200
182
  """
201
- Lists all tenant settings.
183
+ Assigns a workspace to a capacity. This function is the admin version.
202
184
 
203
- This is a wrapper function for the following API: `Tenants - List Tenant Settings <https://learn.microsoft.com/rest/api/fabric/admin/tenants/list-tenant-settings>`_.
185
+ This is a wrapper function for the following API: `Admin - Capacities AssignWorkspacesToCapacity <https://learn.microsoft.com/rest/api/power-bi/admin/capacities-assign-workspaces-to-capacity>`_.
204
186
 
205
- Returns
206
- -------
207
- pandas.DataFrame
208
- A pandas dataframe showing the tenant settings.
187
+ Parameters
188
+ ----------
189
+ source_capacity : str | UUID, default=None
190
+ The name of the source capacity. If the Workspace is not specified, this is parameter mandatory.
191
+ target_capacity : str | UUID, default=None
192
+ The name of the target capacity.
193
+ workspace : str | List[str] | UUID | List[UUID], default=None
194
+ The name or id of the workspace(s).
195
+ Defaults to None which resolves to migrating all workspaces within the source capacity to the target capacity.
209
196
  """
197
+ if target_capacity is None:
198
+ raise ValueError(
199
+ f"{icons.red_dot} The parameter 'target_capacity' is mandatory."
200
+ )
210
201
 
211
- client = fabric.FabricRestClient()
212
- response = client.get("/v1/admin/tenantsettings")
213
-
214
- if response.status_code != 200:
215
- raise FabricHTTPException(response)
202
+ if source_capacity is None and workspace is None:
203
+ raise ValueError(
204
+ f"{icons.red_dot} The parameters 'source_capacity' or 'workspace' needs to be specified."
205
+ )
216
206
 
217
- df = pd.DataFrame(
218
- columns=[
219
- "Setting Name",
220
- "Title",
221
- "Enabled",
222
- "Can Specify Security Groups",
223
- "Tenant Setting Group",
224
- "Enabled Security Groups",
207
+ if workspace is None:
208
+ source_capacity_id = _resolve_capacity_name_and_id(source_capacity)[1]
209
+ dfW = list_workspaces(capacity=source_capacity_id)
210
+ workspaces = dfW["Id"].tolist()
211
+ else:
212
+ if isinstance(workspace, str) or isinstance(workspace, UUID):
213
+ workspace = [workspace]
214
+ if source_capacity is None:
215
+ dfW = list_workspaces()
216
+ else:
217
+ dfW = list_workspaces(capacity=source_capacity_id)
218
+
219
+ # Extract names and IDs that are mapped in dfW
220
+ workspaces_names = dfW[dfW["Name"].isin(workspace)]["Name"].tolist()
221
+ workspaces_ids = dfW[dfW["Id"].isin(workspace)]["Id"].tolist()
222
+
223
+ # Combine IDs into the final workspaces list
224
+ workspaces = workspaces_ids + dfW[dfW["Name"].isin(workspace)]["Id"].tolist()
225
+
226
+ # Identify unmapped workspaces
227
+ unmapped_workspaces = [
228
+ item
229
+ for item in workspace
230
+ if item not in workspaces_names and item not in workspaces_ids
225
231
  ]
226
- )
227
232
 
228
- for i in response.json().get("tenantSettings", []):
229
- new_data = {
230
- "Setting Name": i.get("settingName"),
231
- "Title": i.get("title"),
232
- "Enabled": i.get("enabled"),
233
- "Can Specify Security Groups": i.get("canSpecifySecurityGroups"),
234
- "Tenant Setting Group": i.get("tenantSettingGroup"),
235
- "Enabled Security Groups": [i.get("enabledSecurityGroups", [])],
236
- }
237
- df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
238
-
239
- bool_cols = ["Enabled", "Can Specify Security Groups"]
240
- df[bool_cols] = df[bool_cols].astype(bool)
233
+ if len(workspace) != len(workspaces):
234
+ raise ValueError(
235
+ f"{icons.red_dot} The following workspaces are invalid: {unmapped_workspaces}."
236
+ )
241
237
 
242
- return df
238
+ target_capacity_id = _resolve_capacity_name_and_id(target_capacity)[1]
243
239
 
240
+ workspaces = np.array(workspaces)
241
+ batch_size = 999
242
+ for i in range(0, len(workspaces), batch_size):
243
+ batch = workspaces[i : i + batch_size].tolist()
244
+ request_body = {
245
+ "capacityMigrationAssignments": [
246
+ {
247
+ "targetCapacityObjectId": target_capacity_id.upper(),
248
+ "workspacesToAssign": batch,
249
+ }
250
+ ]
251
+ }
244
252
 
245
- def _list_capacities_meta() -> pd.DataFrame:
253
+ client = fabric.FabricRestClient()
246
254
 
247
- df = pd.DataFrame(
248
- columns=["Capacity Id", "Capacity Name", "Sku", "Region", "State", "Admins"]
249
- )
255
+ response = client.post(
256
+ "/v1.0/myorg/admin/capacities/AssignWorkspaces",
257
+ json=request_body,
258
+ )
250
259
 
251
- client = fabric.PowerBIRestClient()
252
- try:
253
- response = client.get("/v1.0/myorg/admin/capacities")
254
- except Exception as e:
255
- if e.status_code not in [200, 401]:
260
+ if response.status_code != 200:
256
261
  raise FabricHTTPException(response)
257
- elif e.status_code == 401:
258
- response = client.get("/v1.0/myorg/capacities")
259
-
260
- for i in response.json().get("value", []):
261
- new_data = {
262
- "Capacity Id": i.get("id").lower(),
263
- "Capacity Name": i.get("displayName"),
264
- "Sku": i.get("sku"),
265
- "Region": i.get("region"),
266
- "State": i.get("state"),
267
- "Admins": [i.get("admins", [])],
268
- }
269
- df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
270
-
271
- return df
262
+ print(
263
+ f"{icons.green_dot} The workspaces have been assigned to the '{target_capacity}' capacity. A total of {len(workspaces)} were moved."
264
+ )
272
265
 
273
266
 
274
- def unassign_workspaces_from_capacity(workspaces: str | List[str]):
267
+ def unassign_workspaces_from_capacity(
268
+ workspaces: str | List[str] | UUID | List[UUID],
269
+ ):
275
270
  """
276
271
  Unassigns workspace(s) from their capacity.
277
272
 
@@ -279,14 +274,22 @@ def unassign_workspaces_from_capacity(workspaces: str | List[str]):
279
274
 
280
275
  Parameters
281
276
  ----------
282
- workspaces : str | List[str]
283
- The Fabric workspace name(s).
277
+ workspaces : str | List[str] | UUID | List[UUID]
278
+ The Fabric workspace name(s) or id(s).
284
279
  """
285
-
286
280
  if isinstance(workspaces, str):
287
281
  workspaces = [workspaces]
288
282
 
289
- payload = {"workspacesToUnassign": workspaces}
283
+ dfW = list_workspaces()
284
+ workspacesIds = dfW[dfW["Name"].isin(workspaces)]["Id"].tolist()
285
+ workspacesIds = workspacesIds + dfW[dfW["Id"].isin(workspaces)]["Id"].tolist()
286
+
287
+ if len(workspacesIds) != len(workspaces):
288
+ raise ValueError(
289
+ f"{icons.red_dot} Some of the workspaces provided are not valid."
290
+ )
291
+
292
+ payload = {"workspacesToUnassign": workspacesIds}
290
293
 
291
294
  client = fabric.PowerBIRestClient()
292
295
  response = client.post(
@@ -297,102 +300,57 @@ def unassign_workspaces_from_capacity(workspaces: str | List[str]):
297
300
  if response.status_code != 200:
298
301
  raise FabricHTTPException(response)
299
302
 
300
- print(f"{icons.green_dot} The workspaces have been unassigned.")
303
+ print(
304
+ f"{icons.green_dot} A total of {len(workspacesIds)} workspaces have been unassigned."
305
+ )
301
306
 
302
307
 
303
- def list_external_data_shares():
308
+ def list_tenant_settings() -> pd.DataFrame:
304
309
  """
305
- Lists external data shares in the tenant. This function is for admins.
310
+ Lists all tenant settings.
306
311
 
307
- This is a wrapper function for the following API: `External Data Shares - List External Data Shares <https://learn.microsoft.com/rest/api/fabric/admin/external-data-shares/list-external-data-shares>`_.
312
+ This is a wrapper function for the following API: `Tenants - List Tenant Settings <https://learn.microsoft.com/rest/api/fabric/admin/tenants/list-tenant-settings>`_.
308
313
 
309
314
  Returns
310
315
  -------
311
316
  pandas.DataFrame
312
- A pandas dataframe showing a list of external data shares in the tenant.
317
+ A pandas dataframe showing the tenant settings.
313
318
  """
319
+ client = fabric.FabricRestClient()
320
+
321
+ response = client.get("/v1/admin/tenantsettings")
322
+
323
+ if response.status_code != 200:
324
+ raise FabricHTTPException(response)
314
325
 
315
326
  df = pd.DataFrame(
316
327
  columns=[
317
- "External Data Share Id",
318
- "Paths",
319
- "Creater Principal Id",
320
- "Creater Principal Name",
321
- "Creater Principal Type",
322
- "Creater Principal UPN",
323
- "Recipient UPN",
324
- "Status",
325
- "Expiration Time UTC",
326
- "Workspace Id",
327
- "Item Id",
328
- "Invitation URL",
328
+ "Setting Name",
329
+ "Title",
330
+ "Enabled",
331
+ "Can Specify Security Groups",
332
+ "Tenant Setting Group",
333
+ "Enabled Security Groups",
329
334
  ]
330
335
  )
331
336
 
332
- client = fabric.FabricRestClient()
333
- response = client.get("/v1/admin/items/externalDataShares")
334
-
335
- if response.status_code != 200:
336
- raise FabricHTTPException(response)
337
-
338
- for i in response.json().get("value", []):
339
- cp = i.get("creatorPrincipal", {})
337
+ for i in response.json().get("tenantSettings", []):
340
338
  new_data = {
341
- "External Data Share Id": i.get("id"),
342
- "Paths": [i.get("paths", [])],
343
- "Creater Principal Id": cp.get("id"),
344
- "Creater Principal Name": cp.get("displayName"),
345
- "Creater Principal Type": cp.get("type"),
346
- "Creater Principal UPN": cp.get("userDetails", {}).get("userPrincipalName"),
347
- "Recipient UPN": i.get("recipient", {}).get("userPrincipalName"),
348
- "Status": i.get("status"),
349
- "Expiration Time UTC": i.get("expirationTimeUtc"),
350
- "Workspace Id": i.get("workspaceId"),
351
- "Item Id": i.get("itemId"),
352
- "Invitation URL": i.get("invitationUrl"),
339
+ "Setting Name": i.get("settingName"),
340
+ "Title": i.get("title"),
341
+ "Enabled": i.get("enabled"),
342
+ "Can Specify Security Groups": i.get("canSpecifySecurityGroups"),
343
+ "Tenant Setting Group": i.get("tenantSettingGroup"),
344
+ "Enabled Security Groups": [i.get("enabledSecurityGroups", [])],
353
345
  }
354
-
355
346
  df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
356
347
 
357
- date_time_columns = ["Expiration Time UTC"]
358
- df[date_time_columns] = pd.to_datetime(df[date_time_columns])
348
+ bool_cols = ["Enabled", "Can Specify Security Groups"]
349
+ df[bool_cols] = df[bool_cols].astype(bool)
359
350
 
360
351
  return df
361
352
 
362
353
 
363
- def revoke_external_data_share(
364
- external_data_share_id: UUID, item_id: UUID, workspace: str
365
- ):
366
- """
367
- Revokes the specified external data share. Note: This action cannot be undone.
368
-
369
- This is a wrapper function for the following API: `External Data Shares - Revoke External Data Share <https://learn.microsoft.com/rest/api/fabric/admin/external-data-shares/revoke-external-data-share>`_.
370
-
371
- Parameters
372
- ----------
373
- external_data_share_id : UUID
374
- The external data share ID.
375
- item_id : int, default=None
376
- The Item ID
377
- workspace : str
378
- The Fabric workspace name.
379
- """
380
-
381
- (workspace, workspace_id) = resolve_workspace_name_and_id(workspace)
382
-
383
- client = fabric.FabricRestClient()
384
- response = client.post(
385
- f"/v1/admin/workspaces/{workspace_id}/items/{item_id}/externalDataShares/{external_data_share_id}/revoke"
386
- )
387
-
388
- if response.status_code != 200:
389
- raise FabricHTTPException(response)
390
-
391
- print(
392
- f"{icons.green_dot} The '{external_data_share_id}' external data share for the '{item_id}' item within the '{workspace}' workspace has been revoked."
393
- )
394
-
395
-
396
354
  def list_capacities_delegated_tenant_settings(
397
355
  return_dataframe: bool = True,
398
356
  ) -> pd.DataFrame | dict:
@@ -411,7 +369,6 @@ def list_capacities_delegated_tenant_settings(
411
369
  pandas.DataFrame | dict
412
370
  A pandas dataframe showing a list of tenant setting overrides that override at the capacities.
413
371
  """
414
-
415
372
  df = pd.DataFrame(
416
373
  columns=[
417
374
  "Capacity Id",
@@ -476,68 +433,93 @@ def list_capacities_delegated_tenant_settings(
476
433
  "continuationToken": "",
477
434
  }
478
435
  for r in responses:
479
- combined_response["overrides"].extend(r["overrides"])
436
+ combined_response["overrides"].extend(r["Overrides"])
480
437
  combined_response["continuationUri"] = r["continuationUri"]
481
438
  combined_response["continuationToken"] = r["continuationToken"]
482
439
 
483
440
  return combined_response
484
441
 
485
442
 
486
- def scan_workspaces(
487
- data_source_details: bool = False,
488
- dataset_schema: bool = False,
489
- dataset_expressions: bool = False,
490
- lineage: bool = False,
491
- artifact_users: bool = False,
492
- workspace: Optional[str | List[str]] = None,
493
- ) -> dict:
443
+ def list_modified_workspaces(
444
+ modified_since: Optional[str] = None,
445
+ exclude_inactive_workspaces: Optional[bool] = False,
446
+ exclude_personal_workspaces: Optional[bool] = False,
447
+ ) -> pd.DataFrame:
448
+ """
449
+ Gets a list of workspace IDs in the organization.
494
450
 
495
- if workspace is None:
496
- workspace = fabric.resolve_workspace_name()
451
+ This is a wrapper function for the following API: `Admin - WorkspaceInfo GetModifiedWorkspaces <https://learn.microsoft.com/rest/api/power-bi/admin/workspace-info-get-modified-workspaces>`_.
497
452
 
498
- if isinstance(workspace, str):
499
- workspace = [workspace]
453
+ Parameters
454
+ ----------
455
+ modified_since : str, default=None
456
+ Last modified date (must be in ISO compliant UTC format). Example: "2024-11-02T05:51:30" or "2024-11-02T05:51:30.0000000Z".
457
+ exclude_inactive_workspaces : bool, default=False
458
+ Whether to exclude inactive workspaces.
459
+ exclude_personal_workspaces : bool, default=False
460
+ Whether to exclude personal workspaces.
500
461
 
501
- workspace_list = []
462
+ Returns
463
+ -------
464
+ pandas.DataFrame
465
+ A pandas dataframe showing a list of workspace IDs in the organization.
466
+ """
467
+ client = fabric.PowerBIRestClient()
502
468
 
503
- for w in workspace:
504
- workspace_list.append(fabric.resolve_workspace_id(w))
469
+ params = {}
505
470
 
506
- client = fabric.PowerBIRestClient()
507
- request_body = {"workspaces": workspace_list}
471
+ url = "/v1.0/myorg/admin/workspaces/modified"
472
+
473
+ if modified_since is not None:
474
+ modified_since_dt = dtparser(modified_since)
475
+ params["modifiedSince"] = (
476
+ f"{modified_since_dt.isoformat(timespec='microseconds')}0Z"
477
+ )
508
478
 
509
- response_clause = f"/v1.0/myorg/admin/workspaces/getInfo?lineage={lineage}&datasourceDetails={data_source_details}&datasetSchema={dataset_schema}&datasetExpressions={dataset_expressions}&getArtifactUsers={artifact_users}"
510
- response = client.post(response_clause, json=request_body)
479
+ if exclude_inactive_workspaces is not None:
480
+ params["excludeInActiveWorkspaces"] = exclude_inactive_workspaces
481
+
482
+ if exclude_personal_workspaces is not None:
483
+ params["excludePersonalWorkspaces"] = exclude_personal_workspaces
484
+
485
+ url = _build_url(url, params)
486
+
487
+ response = client.get(url)
511
488
 
512
- if response.status_code != 202:
513
- raise FabricHTTPException(response)
514
- scan_id = response.json()["id"]
515
- scan_status = response.json().get("status")
516
- while scan_status not in ["Succeeded", "Failed"]:
517
- time.sleep(1)
518
- response = client.get(f"/v1.0/myorg/admin/workspaces/scanStatus/{scan_id}")
519
- scan_status = response.json().get("status")
520
- if scan_status == "Failed":
521
- raise FabricHTTPException(response)
522
- response = client.get(f"/v1.0/myorg/admin/workspaces/scanResult/{scan_id}")
523
489
  if response.status_code != 200:
524
490
  raise FabricHTTPException(response)
525
491
 
526
- return response.json()
492
+ df = pd.DataFrame(response.json()).rename(columns={"id": "Workspace Id"})
493
+
494
+ return df
527
495
 
528
496
 
529
- def list_datasets() -> pd.DataFrame:
497
+ def list_datasets(
498
+ top: Optional[int] = None,
499
+ filter: Optional[str] = None,
500
+ skip: Optional[int] = None,
501
+ ) -> pd.DataFrame:
530
502
  """
531
503
  Shows a list of datasets for the organization.
532
504
 
533
505
  This is a wrapper function for the following API: `Admin - Datasets GetDatasetsAsAdmin <https://learn.microsoft.com/rest/api/power-bi/admin/datasets-get-datasets-as-admin>`_.
534
506
 
507
+ Parameters
508
+ ----------
509
+ top : int, default=None
510
+ Returns only the first n results.
511
+ filter : str, default=None
512
+ Returns a subset of a results based on Odata filter query parameter condition.
513
+ skip : int, default=None
514
+ Skips the first n results.
515
+ token_provider : Optional[TokenProvider] = None,
516
+ Authentication provider used to be use in the request. Supports Service Principal.
517
+
535
518
  Returns
536
519
  -------
537
520
  pandas.DataFrame
538
521
  A pandas dataframe showing a list of datasets for the organization.
539
522
  """
540
-
541
523
  df = pd.DataFrame(
542
524
  columns=[
543
525
  "Dataset Id",
@@ -563,7 +545,22 @@ def list_datasets() -> pd.DataFrame:
563
545
  )
564
546
 
565
547
  client = fabric.PowerBIRestClient()
566
- response = client.get("/v1.0/myorg/admin/datasets")
548
+
549
+ params = {}
550
+ url = "/v1.0/myorg/admin/datasets"
551
+
552
+ if top is not None:
553
+ params["$top"] = top
554
+
555
+ if filter is not None:
556
+ params["$filter"] = filter
557
+
558
+ if skip is not None:
559
+ params["$skip"] = skip
560
+
561
+ url = _build_url(url, params)
562
+
563
+ response = client.get(url)
567
564
 
568
565
  if response.status_code != 200:
569
566
  raise FabricHTTPException(response)
@@ -614,74 +611,6 @@ def list_datasets() -> pd.DataFrame:
614
611
  return df
615
612
 
616
613
 
617
- def list_item_access_details(
618
- item_name: str, type: str, workspace: Optional[str] = None
619
- ) -> pd.DataFrame:
620
- """
621
- Returns a list of users (including groups and service principals) and lists their workspace roles.
622
-
623
- This is a wrapper function for the following API: `Items - List Item Access Details <https://learn.microsoft.com/rest/api/fabric/admin/items/list-item-access-details>`_.
624
-
625
- Parameters
626
- ----------
627
- item_name : str
628
- Name of the Fabric item.
629
- type : str
630
- Type of Fabric item.
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
- pandas.DataFrame
639
- A pandas dataframe showing a list of users (including groups and service principals) and lists their workspace roles.
640
- """
641
-
642
- workspace_name, workspace_id = _resolve_workspace_name_and_id(workspace)
643
- item_id = _resolve_item_id(item_name=item_name, type=type, workspace=workspace_name)
644
-
645
- df = pd.DataFrame(
646
- columns=[
647
- "User Id",
648
- "User Name",
649
- "User Type",
650
- "User Principal Name",
651
- "Item Name",
652
- "Item Type",
653
- "Item Id",
654
- "Permissions",
655
- "Additional Permissions",
656
- ]
657
- )
658
- client = fabric.FabricRestClient()
659
- response = client.get(f"/v1/admin/workspaces/{workspace_id}/items/{item_id}/users")
660
-
661
- if response.status_code != 200:
662
- raise FabricHTTPException(response)
663
-
664
- for v in response.json().get("accessDetails", []):
665
- new_data = {
666
- "User Id": v.get("principal", {}).get("id"),
667
- "User Name": v.get("principal", {}).get("displayName"),
668
- "User Type": v.get("principal", {}).get("type"),
669
- "User Principal Name": v.get("principal", {})
670
- .get("userDetails", {})
671
- .get("userPrincipalName"),
672
- "Item Type": v.get("itemAccessDetails", {}).get("type"),
673
- "Permissions": v.get("itemAccessDetails", {}).get("permissions"),
674
- "Additional Permissions": v.get("itemAccessDetails", {}).get(
675
- "additionalPermissions"
676
- ),
677
- "Item Name": item_name,
678
- "Item Id": item_id,
679
- }
680
- df = pd.concat([df, pd.DataFrame([new_data])], ignore_index=True)
681
-
682
- return df
683
-
684
-
685
614
  def list_access_entities(
686
615
  user_email_address: str,
687
616
  ) -> pd.DataFrame:
@@ -700,7 +629,6 @@ def list_access_entities(
700
629
  pandas.DataFrame
701
630
  A pandas dataframe showing a list of permission details for Fabric and Power BI items the specified user can access.
702
631
  """
703
-
704
632
  df = pd.DataFrame(
705
633
  columns=[
706
634
  "Item Id",
@@ -711,6 +639,7 @@ def list_access_entities(
711
639
  ]
712
640
  )
713
641
  client = fabric.FabricRestClient()
642
+
714
643
  response = client.get(f"/v1/admin/users/{user_email_address}/access")
715
644
 
716
645
  if response.status_code != 200:
@@ -744,8 +673,8 @@ def list_workspace_access_details(
744
673
 
745
674
  Parameters
746
675
  ----------
747
- workspace : str, default=None
748
- The Fabric workspace name.
676
+ workspace : str | UUID, default=None
677
+ The Fabric workspace name or id.
749
678
  Defaults to None which resolves to the workspace of the attached lakehouse
750
679
  or if no lakehouse attached, resolves to the workspace of the notebook.
751
680
 
@@ -754,7 +683,6 @@ def list_workspace_access_details(
754
683
  pandas.DataFrame
755
684
  A pandas dataframe showing a list of users (including groups and Service Principals) that have access to the specified workspace.
756
685
  """
757
-
758
686
  workspace_name, workspace_id = _resolve_workspace_name_and_id(workspace)
759
687
 
760
688
  df = pd.DataFrame(
@@ -767,7 +695,9 @@ def list_workspace_access_details(
767
695
  "Workspace Role",
768
696
  ]
769
697
  )
698
+
770
699
  client = fabric.FabricRestClient()
700
+
771
701
  response = client.get(f"/v1/admin/workspaces/{workspace_id}/users")
772
702
  if response.status_code != 200:
773
703
  raise FabricHTTPException(response)
@@ -786,140 +716,13 @@ def list_workspace_access_details(
786
716
  return df
787
717
 
788
718
 
789
- def _resolve_item_id(
790
- item_name: str, type: str, workspace: Optional[str] = None
791
- ) -> UUID:
792
-
793
- workspace_name, workspace_id = _resolve_workspace_name_and_id(workspace)
794
- dfI = list_items(workspace=workspace_name, type=type)
795
- dfI_filt = dfI[dfI["Item Name"] == item_name]
796
-
797
- if len(dfI_filt) == 0:
798
- raise ValueError(
799
- f"The '{item_name}' {type} does not exist within the '{workspace_name}' workspace."
800
- )
801
-
802
- return dfI_filt["Item Id"].iloc[0]
803
-
804
-
805
- def _resolve_workspace_name_and_id(workspace: str) -> Tuple[str, UUID]:
806
-
807
- filter_condition = urllib.parse.quote(workspace)
808
- dfW_filt = list_workspaces(filter=f"name eq '{filter_condition}'")
809
- workspace_name = dfW_filt["Name"].iloc[0]
810
- workspace_id = dfW_filt["Id"].iloc[0]
811
-
812
- return workspace_name, workspace_id
813
-
814
-
815
- def list_items(
816
- capacity_name: Optional[str] = None,
817
- workspace: Optional[str] = None,
818
- state: Optional[str] = None,
819
- type: Optional[str] = None,
820
- ) -> pd.DataFrame:
821
- """
822
- Shows a list of active Fabric and Power BI items.
823
-
824
- This is a wrapper function for the following API: `Items - List Items <https://learn.microsoft.com/rest/api/fabric/admin/items/list-items>`_.
825
-
826
- Parameters
827
- ----------
828
- capacity_name : str, default=None
829
- The capacity name.
830
- workspace : str, default=None
831
- The Fabric workspace name.
832
- Defaults to None which resolves to the workspace of the attached lakehouse
833
- or if no lakehouse attached, resolves to the workspace of the notebook.
834
- state : str, default=None
835
- The item state.
836
- type : str, default=None
837
- The item type.
838
-
839
- Returns
840
- -------
841
- pandas.DataFrame
842
- A pandas dataframe showing a list of active Fabric and Power BI items.
843
- """
844
-
845
- url = "/v1/admin/items?"
846
-
847
- df = pd.DataFrame(
848
- columns=[
849
- "Item Id",
850
- "Item Name",
851
- "Type",
852
- "Description",
853
- "State",
854
- "Last Updated Date",
855
- "Creator Principal Id",
856
- "Creator Principal Display Name",
857
- "Creator Principal Type",
858
- "Creator User Principal Name",
859
- "Workspace Id",
860
- "Capacity Id",
861
- ]
862
- )
863
-
864
- if workspace is not None:
865
- workspace_name, workspace_id = _resolve_workspace_name_and_id(workspace)
866
- url += f"workspaceId={workspace_id}&"
867
- if capacity_name is not None:
868
- dfC = list_capacities()
869
- dfC_filt = dfC[dfC["Capacity Name"] == capacity_name]
870
- if len(dfC_filt) == 0:
871
- raise ValueError(
872
- f"{icons.red_dot} The '{capacity_name}' capacity does not exist."
873
- )
874
- capacity_id = dfC_filt["Capacity Id"].iloc[0]
875
- url += f"capacityId={capacity_id}&"
876
- if state is not None:
877
- url += f"state={state}&"
878
- if type is not None:
879
- url += f"type={type}&"
880
-
881
- if url.endswith("?") or url.endswith("&"):
882
- url = url[:-1]
883
-
884
- client = fabric.FabricRestClient()
885
- response = client.get(url)
886
-
887
- if response.status_code != 200:
888
- raise FabricHTTPException(response)
889
-
890
- responses = pagination(client, response)
891
-
892
- for r in responses:
893
- for v in r.get("itemEntities", []):
894
- new_data = {
895
- "Item Id": v.get("id"),
896
- "Type": v.get("type"),
897
- "Item Name": v.get("name"),
898
- "Description": v.get("description"),
899
- "State": v.get("state"),
900
- "Last Updated Date": v.get("lastUpdatedDate"),
901
- "Creator Principal Id": v.get("creatorPrincipal", {}).get("id"),
902
- "Creator Principal Display Name": v.get("creatorPrincipal", {}).get(
903
- "displayName"
904
- ),
905
- "Creator Principal Type": v.get("creatorPrincipal", {}).get("type"),
906
- "Creator User Principal Name": v.get("creatorPrincipal", {})
907
- .get("userDetails", {})
908
- .get("userPrincipalName"),
909
- "Workspace Id": v.get("workspaceId"),
910
- "Capacity Id": v.get("capacityId"),
911
- }
912
- df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
913
-
914
- return df
915
-
916
-
917
719
  def list_activity_events(
918
720
  start_time: str,
919
721
  end_time: str,
920
722
  activity_filter: Optional[str] = None,
921
723
  user_id_filter: Optional[str] = None,
922
- ):
724
+ return_dataframe: Optional[bool] = True,
725
+ ) -> pd.DataFrame | dict:
923
726
  """
924
727
  Shows a list of audit activity events for a tenant.
925
728
 
@@ -935,15 +738,16 @@ def list_activity_events(
935
738
  Filter value for activities. Example: 'viewreport'.
936
739
  user_id_filter : str, default=None
937
740
  Email address of the user.
741
+ return_dataframe : bool, default=True
742
+ If True the response is a pandas.DataFrame. If False returns the original Json. Default True
938
743
 
939
744
  Returns
940
745
  -------
941
- pandas.DataFrame
942
- A pandas dataframe showing a list of audit activity events for a tenant.
746
+ pandas.DataFrame | dict
747
+ A pandas dataframe or json showing a list of audit activity events for a tenant.
943
748
  """
944
-
945
- start_dt = datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S")
946
- end_dt = datetime.strptime(end_time, "%Y-%m-%dT%H:%M:%S")
749
+ start_dt = dtparser(start_time)
750
+ end_dt = dtparser(end_time)
947
751
 
948
752
  if not start_dt.date() == end_dt.date():
949
753
  raise ValueError(
@@ -976,104 +780,227 @@ def list_activity_events(
976
780
  ]
977
781
  )
978
782
 
783
+ resposeJson = {"activityEventEntities": []}
784
+
979
785
  tic = "%27"
980
786
  space = "%20"
981
787
  client = fabric.PowerBIRestClient()
982
- base_url = "/v1.0/myorg/admin/activityevents"
788
+
789
+ params = {}
790
+ url = "/v1.0/myorg/admin/activityevents"
791
+
792
+ if start_dt is not None:
793
+ params["startDateTime"] = f"'{start_dt.isoformat(timespec='milliseconds')}'"
794
+
795
+ if end_dt is not None:
796
+ params["endDateTime"] = f"'{end_dt.isoformat(timespec='milliseconds')}'"
797
+
983
798
  conditions = []
984
799
 
985
800
  if activity_filter is not None:
986
801
  conditions.append(f"Activity{space}eq{space}{tic}{activity_filter}{tic}")
802
+
987
803
  if user_id_filter is not None:
988
804
  conditions.append(f"UserId{space}eq{space}{tic}{user_id_filter}{tic}")
989
805
 
990
- filter_value = (
991
- f"&$filter={f'{space}and{space}'.join(conditions)}" if conditions else ""
806
+ if conditions:
807
+ params["filder"] = f"{f'{space}and{space}'.join(conditions)}"
808
+
809
+ url_parts = list(urllib.parse.urlparse(url))
810
+ url_parts[4] = urllib.parse.urlencode(params)
811
+ url = urllib.parse.urlunparse(url_parts)
812
+
813
+ response = client.get(url)
814
+
815
+ if response.status_code != 200:
816
+ raise FabricHTTPException(response)
817
+
818
+ responses = pagination(client, response)
819
+
820
+ for r in responses:
821
+ if return_dataframe:
822
+ for i in r.get("activityEventEntities", []):
823
+ new_data = {
824
+ "Id": i.get("id"),
825
+ "Record Type": i.get("RecordType"),
826
+ "Creation Time": i.get("CreationTime"),
827
+ "Operation": i.get("Operation"),
828
+ "Organization Id": i.get("OrganizationId"),
829
+ "User Type": i.get("UserType"),
830
+ "User Key": i.get("UserKey"),
831
+ "Workload": i.get("Workload"),
832
+ "Result Status": i.get("ResultStatus"),
833
+ "User Id": i.get("UserId"),
834
+ "Client IP": i.get("ClientIP"),
835
+ "User Agent": i.get("UserAgent"),
836
+ "Activity": i.get("Activity"),
837
+ "Workspace Name": i.get("WorkSpaceName"),
838
+ "Workspace Id": i.get("WorkspaceId"),
839
+ "Object Id": i.get("ObjectId"),
840
+ "Request Id": i.get("RequestId"),
841
+ "Object Type": i.get("ObjectType"),
842
+ "Object Display Name": i.get("ObjectDisplayName"),
843
+ "Experience": i.get("Experience"),
844
+ "Refresh Enforcement Policy": i.get("RefreshEnforcementPolicy"),
845
+ }
846
+ df = pd.concat(
847
+ [df, pd.DataFrame(new_data, index=[0])],
848
+ ignore_index=True,
849
+ )
850
+ else:
851
+ resposeJson["activityEventEntities"].extend(r.get("activityEventEntities"))
852
+
853
+ if return_dataframe:
854
+ df["Creation Time"] = pd.to_datetime(df["Creation Time"])
855
+ activity_events = df
856
+ else:
857
+ activity_events = resposeJson
858
+
859
+ return activity_events
860
+
861
+
862
+ def _resolve_capacity_name_and_id(
863
+ capacity: str | UUID,
864
+ ) -> Tuple[str, UUID]:
865
+
866
+ dfC = list_capacities(capacity=capacity)
867
+ try:
868
+ capacity_name = dfC["Capacity Name"].iloc[0]
869
+ capacity_id = dfC["Capacity Id"].iloc[0]
870
+ except Exception:
871
+ raise ValueError(f"{icons.red_dot} The '{capacity}' capacity was not found.")
872
+
873
+ return capacity_name, capacity_id
874
+
875
+
876
+ def _list_capacities_meta() -> pd.DataFrame:
877
+ """
878
+ Shows the a list of capacities and their properties. This function is the admin version.
879
+
880
+ This is a wrapper function for the following API: `Admin - Get Capacities As Admin <https://learn.microsoft.com/rest/api/power-bi/admin/get-capacities-as-admin>`_.
881
+
882
+ Returns
883
+ -------
884
+ pandas.DataFrame
885
+ A pandas dataframe showing the capacities and their properties
886
+ """
887
+
888
+ client = fabric.FabricRestClient()
889
+
890
+ df = pd.DataFrame(
891
+ columns=["Capacity Id", "Capacity Name", "Sku", "Region", "State", "Admins"]
992
892
  )
993
893
 
994
- full_url = f"{base_url}?startDateTime={tic}{start_time}{tic}&endDateTime={tic}{end_time}{tic}{filter_value}"
995
- response = client.get(full_url)
894
+ response = client.get("/v1.0/myorg/admin/capacities")
895
+
996
896
  if response.status_code != 200:
997
897
  raise FabricHTTPException(response)
998
898
 
999
899
  responses = pagination(client, response)
1000
900
 
1001
901
  for r in responses:
1002
- for i in r.get("activityEventEntities", []):
902
+ for i in r.get("value", []):
1003
903
  new_data = {
1004
- "Id": i.get("id"),
1005
- "Record Type": i.get("RecordType"),
1006
- "Creation Time": i.get("CreationTime"),
1007
- "Operation": i.get("Operation"),
1008
- "Organization Id": i.get("OrganizationId"),
1009
- "User Type": i.get("UserType"),
1010
- "User Key": i.get("UserKey"),
1011
- "Workload": i.get("Workload"),
1012
- "Result Status": i.get("ResultStatus"),
1013
- "User Id": i.get("UserId"),
1014
- "Client IP": i.get("ClientIP"),
1015
- "User Agent": i.get("UserAgent"),
1016
- "Activity": i.get("Activity"),
1017
- "Workspace Name": i.get("WorkSpaceName"),
1018
- "Workspace Id": i.get("WorkspaceId"),
1019
- "Object Id": i.get("ObjectId"),
1020
- "Request Id": i.get("RequestId"),
1021
- "Object Type": i.get("ObjectType"),
1022
- "Object Display Name": i.get("ObjectDisplayName"),
1023
- "Experience": i.get("Experience"),
1024
- "Refresh Enforcement Policy": i.get("RefreshEnforcementPolicy"),
904
+ "Capacity Id": i.get("id").lower(),
905
+ "Capacity Name": i.get("displayName"),
906
+ "Sku": i.get("sku"),
907
+ "Region": i.get("region"),
908
+ "State": i.get("state"),
909
+ "Admins": [i.get("admins", [])],
1025
910
  }
1026
- df = pd.concat(
1027
- [df, pd.DataFrame(new_data, index=[0])],
1028
- ignore_index=True,
1029
- )
1030
-
1031
- df["Creation Time"] = pd.to_datetime(df["Creation Time"])
911
+ df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
1032
912
 
1033
913
  return df
1034
914
 
1035
915
 
1036
- def list_modified_workspaces(
1037
- modified_since: Optional[str] = None,
1038
- exclude_inactive_workspaces: bool = False,
1039
- exclude_personal_workspaces: bool = False,
916
+ def _resolve_workspace_name_and_id(
917
+ workspace: str | UUID,
918
+ ) -> Tuple[str, UUID]:
919
+
920
+ dfW = list_workspaces(workspace=workspace)
921
+ try:
922
+ workspace_name = dfW["Name"].iloc[0]
923
+ workspace_id = dfW["Id"].iloc[0]
924
+ except Exception:
925
+ raise ValueError(f"{icons.red_dot} The '{workspace}' workspace was not found.")
926
+
927
+ return workspace_name, workspace_id
928
+
929
+
930
+ def list_reports(
931
+ top: Optional[int] = None, skip: Optional[int] = None, filter: Optional[str] = None
1040
932
  ) -> pd.DataFrame:
1041
933
  """
1042
- Gets a list of workspace IDs in the organization.
934
+ Shows a list of reports for the organization.
1043
935
 
1044
- This is a wrapper function for the following API: `Admin - WorkspaceInfo GetModifiedWorkspaces <https://learn.microsoft.com/rest/api/power-bi/admin/workspace-info-get-modified-workspaces>`_.
1045
-
1046
- Parameters
1047
- ----------
1048
- modified_since : str
1049
- Last modified date (must be in ISO 8601 compliant UTC format). Example: "2024-11-02T05:51:30.0000000Z".
1050
- exclude_inactive_workspaces : bool, default=False
1051
- Whether to exclude inactive workspaces.
1052
- exclude_personal_workspaces : bool, default=False
1053
- Whether to exclude personal workspaces.
936
+ This is a wrapper function for the following API: `Admin - Reports GetReportsAsAdmin <https://learn.microsoft.com/rest/api/power-bi/admin/reports-get-reports-as-admin>`_.
1054
937
 
1055
938
  Returns
1056
939
  -------
1057
940
  pandas.DataFrame
1058
- A pandas dataframe showing a list of workspace IDs in the organization.
941
+ A pandas dataframe showing a list of reports for the organization.
1059
942
  """
1060
943
 
1061
- client = fabric.PowerBIRestClient()
1062
- url = "/v1.0/myorg/admin/workspaces/modified?"
944
+ df = pd.DataFrame(
945
+ columns=[
946
+ "Report Id",
947
+ "Report Name",
948
+ "Type",
949
+ "Web URL",
950
+ "Embed URL",
951
+ "Dataset Id",
952
+ "Created Date",
953
+ "Modified Date",
954
+ "Created By",
955
+ "Modified By",
956
+ "Sensitivity Label Id",
957
+ "Users",
958
+ "Subscriptions",
959
+ "Workspace Id",
960
+ "Report Flags",
961
+ ]
962
+ )
1063
963
 
1064
- if modified_since is not None:
1065
- url += f"modifiedSince={modified_since}&"
1066
- if exclude_inactive_workspaces:
1067
- url += f"excludeInActiveWorkspaces={exclude_inactive_workspaces}&"
1068
- if exclude_personal_workspaces:
1069
- url += f"excludePersonalWorkspaces={exclude_personal_workspaces}&"
964
+ url = "/v1.0/myorg/admin/reports?"
965
+ if top is not None:
966
+ url += f"$top={top}&"
967
+ if skip is not None:
968
+ url += f"$skip={skip}&"
969
+ if filter is not None:
970
+ url += f"$filter={filter}&"
1070
971
 
1071
- url = url.rstrip("&").rstrip("?")
972
+ url.rstrip("$").rstrip("?")
1072
973
 
974
+ client = fabric.PowerBIRestClient()
1073
975
  response = client.get(url)
976
+
1074
977
  if response.status_code != 200:
1075
978
  raise FabricHTTPException(response)
1076
979
 
1077
- df = pd.DataFrame(response.json()).rename(columns={"id": "Workspace Id"})
980
+ for v in response.json().get("value", []):
981
+ new_data = {
982
+ "Report Id": v.get("id"),
983
+ "Report Name": v.get("name"),
984
+ "Type": v.get("reportType"),
985
+ "Web URL": v.get("webUrl"),
986
+ "Embed URL": v.get("embedUrl"),
987
+ "Dataset Id": v.get("datasetId"),
988
+ "Created Date": v.get("createdDateTime"),
989
+ "Modified Date": v.get("modifiedDateTime"),
990
+ "Created By": v.get("createdBy"),
991
+ "Modified By": v.get("modifiedBy"),
992
+ "Sensitivity Label Id": v.get("sensitivityLabel", {}).get("labelId"),
993
+ "Users": v.get("users"),
994
+ "Subscriptions": v.get("subscriptions"),
995
+ "Workspace Id": v.get("workspaceId"),
996
+ "Report Flags": v.get("reportFlags"),
997
+ }
998
+ df = pd.concat([df, pd.DataFrame([new_data])], ignore_index=True)
999
+
1000
+ int_cols = ["Report Flags"]
1001
+ df[int_cols] = df[int_cols].astype(int)
1002
+
1003
+ df["Created Date"] = pd.to_datetime(df["Created Date"], errors="coerce")
1004
+ df["Modified Date"] = pd.to_datetime(df["Modified Date"], errors="coerce")
1078
1005
 
1079
1006
  return df