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

@@ -236,6 +236,69 @@ def list_group_members(group: str | UUID) -> pd.DataFrame:
236
236
  return df
237
237
 
238
238
 
239
+ @log
240
+ def list_group_transitive_members(group: str | UUID) -> pd.DataFrame:
241
+ """
242
+ Shows a list of the members of a group. This operation is transitive and returns a flat list of all nested members.
243
+
244
+ This is a wrapper function for the following API: `List group transitive members <https://learn.microsoft.com/graph/api/group-list-transitivemembers>`_.
245
+
246
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
247
+
248
+ Parameters
249
+ ----------
250
+ group : str | uuid.UUID
251
+ The group name or ID.
252
+
253
+ Returns
254
+ -------
255
+ pandas.DataFrame
256
+ A pandas dataframe showing a list of the members of a group.
257
+ """
258
+
259
+ group_id = resolve_group_id(group)
260
+
261
+ result = _base_api(
262
+ request=f"groups/{group_id}/transitiveMembers",
263
+ client="graph",
264
+ uses_pagination=True,
265
+ )
266
+
267
+ columns = {
268
+ "Member Id": "string",
269
+ "Organization Id": "string",
270
+ "Description": "string",
271
+ "Member Name": "string",
272
+ "Group Types": "list",
273
+ "Mail": "string",
274
+ "Mail Enabled": "bool",
275
+ "Mail Nickname": "string",
276
+ }
277
+
278
+ df = _create_dataframe(columns=columns)
279
+
280
+ rows = []
281
+ for r in result:
282
+ for v in r.get("value", []):
283
+ rows.append(
284
+ {
285
+ "Member Id": v.get("id"),
286
+ "Organization Id": v.get("organizationId"),
287
+ "Description": v.get("description"),
288
+ "Member Name": v.get("displayName"),
289
+ "Group Types": v.get("groupTypes"),
290
+ "Mail": v.get("mail"),
291
+ "Mail Enabled": v.get("mailEnabled"),
292
+ "Mail Nickname": v.get("mailNickname"),
293
+ }
294
+ )
295
+
296
+ if rows:
297
+ df = pd.DataFrame(rows, columns=list(columns.keys()))
298
+
299
+ return df
300
+
301
+
239
302
  @log
240
303
  def list_group_owners(group: str | UUID) -> pd.DataFrame:
241
304
  """
@@ -40,7 +40,7 @@ def resolve_user_id(user: str | UUID) -> UUID:
40
40
 
41
41
 
42
42
  @log
43
- def get_user(user: str | UUID) -> pd.DataFrame:
43
+ def get_user(user: str | UUID, show_manager: bool = False) -> pd.DataFrame:
44
44
  """
45
45
  Shows properties of a given user.
46
46
 
@@ -52,6 +52,8 @@ def get_user(user: str | UUID) -> pd.DataFrame:
52
52
  ----------
53
53
  user : str | uuid.UUID
54
54
  The user ID or user principal name.
55
+ show_manager : bool, default=False
56
+ Whether to include the user's manager information.
55
57
 
56
58
  Returns
57
59
  -------
@@ -59,7 +61,11 @@ def get_user(user: str | UUID) -> pd.DataFrame:
59
61
  A pandas dataframe showing properties of a given user.
60
62
  """
61
63
 
62
- result = _base_api(request=f"users/{user}", client="graph").json()
64
+ url = f"users/{user}?$select=id,userPrincipalName,displayName,mail,jobTitle,officeLocation,mobilePhone,businessPhones,preferredLanguage,surname,department"
65
+ if show_manager:
66
+ url += "&$expand=manager($select=displayName,id,mail,jobTitle)"
67
+
68
+ result = _base_api(request=url, client="graph").json()
63
69
 
64
70
  new_data = {
65
71
  "User Id": result.get("id"),
@@ -72,13 +78,22 @@ def get_user(user: str | UUID) -> pd.DataFrame:
72
78
  "Business Phones": str(result.get("businessPhones")),
73
79
  "Preferred Language": result.get("preferredLanguage"),
74
80
  "Surname": result.get("surname"),
81
+ "Department": result.get("department"),
75
82
  }
83
+ if show_manager:
84
+ manager = result.get("manager", {})
85
+ new_data |= {
86
+ "Manager Id": manager.get("id"),
87
+ "Manager Name": manager.get("displayName"),
88
+ "Manager Mail": manager.get("mail"),
89
+ "Manager Job Title": manager.get("jobTitle"),
90
+ }
76
91
 
77
92
  return pd.DataFrame([new_data])
78
93
 
79
94
 
80
95
  @log
81
- def list_users() -> pd.DataFrame:
96
+ def list_users(show_manager: bool = False) -> pd.DataFrame:
82
97
  """
83
98
  Shows a list of users and their properties.
84
99
 
@@ -86,13 +101,21 @@ def list_users() -> pd.DataFrame:
86
101
 
87
102
  Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
88
103
 
104
+ Parameters
105
+ ----------
106
+ show_manager : bool, default=False
107
+ Whether to include the user's manager information.
108
+
89
109
  Returns
90
110
  -------
91
111
  pandas.DataFrame
92
112
  A pandas dataframe showing a list of users and their properties.
93
113
  """
94
114
 
95
- result = _base_api(request="users", client="graph", uses_pagination=True)
115
+ url = "users?$select=id,userPrincipalName,displayName,mail,jobTitle,officeLocation,mobilePhone,businessPhones,preferredLanguage,surname,department"
116
+ if show_manager:
117
+ url += "&$expand=manager($select=displayName,id,mail,jobTitle)"
118
+ result = _base_api(request=url, client="graph", uses_pagination=True)
96
119
 
97
120
  columns = {
98
121
  "User Id": "string",
@@ -105,27 +128,48 @@ def list_users() -> pd.DataFrame:
105
128
  "Business Phones": "string",
106
129
  "Preferred Language": "string",
107
130
  "Surname": "string",
131
+ "Department": "string",
108
132
  }
109
133
 
134
+ if show_manager:
135
+ columns.update(
136
+ {
137
+ "Manager Id": "string",
138
+ "Manager Name": "string",
139
+ "Manager Mail": "string",
140
+ "Manager Job Title": "string",
141
+ }
142
+ )
143
+
110
144
  df = _create_dataframe(columns=columns)
111
145
 
112
146
  rows = []
113
147
  for r in result:
114
148
  for v in r.get("value", []):
115
- rows.append(
116
- {
117
- "User Id": v.get("id"),
118
- "User Principal Name": v.get("userPrincipalName"),
119
- "User Name": v.get("displayName"),
120
- "Mail": v.get("mail"),
121
- "Job Title": v.get("jobTitle"),
122
- "Office Location": v.get("officeLocation"),
123
- "Mobile Phone": v.get("mobilePhone"),
124
- "Business Phones": str(v.get("businessPhones")),
125
- "Preferred Language": v.get("preferredLanguage"),
126
- "Surname": v.get("surname"),
149
+ user_data = {
150
+ "User Id": v.get("id"),
151
+ "User Principal Name": v.get("userPrincipalName"),
152
+ "User Name": v.get("displayName"),
153
+ "Mail": v.get("mail"),
154
+ "Job Title": v.get("jobTitle"),
155
+ "Office Location": v.get("officeLocation"),
156
+ "Mobile Phone": v.get("mobilePhone"),
157
+ "Business Phones": str(v.get("businessPhones")),
158
+ "Preferred Language": v.get("preferredLanguage"),
159
+ "Surname": v.get("surname"),
160
+ "Department": v.get("department"),
161
+ }
162
+
163
+ if show_manager:
164
+ manager = v.get("manager", {})
165
+ user_data |= {
166
+ "Manager Id": manager.get("id"),
167
+ "Manager Name": manager.get("displayName"),
168
+ "Manager Mail": manager.get("mail"),
169
+ "Manager Job Title": manager.get("jobTitle"),
127
170
  }
128
- )
171
+
172
+ rows.append(user_data)
129
173
 
130
174
  if rows:
131
175
  df = pd.DataFrame(rows, columns=list(columns.keys()))
@@ -12,10 +12,6 @@ from typing import Optional
12
12
  from sempy._utils._log import log
13
13
  import sempy_labs._icons as icons
14
14
  from uuid import UUID
15
- from sempy_labs.report._report_functions import (
16
- list_report_visuals,
17
- list_report_pages,
18
- )
19
15
 
20
16
 
21
17
  @log
@@ -187,15 +183,7 @@ def export_report(
187
183
  request_body = {"format": export_format, "powerBIReportConfiguration": {}}
188
184
 
189
185
  request_body["powerBIReportConfiguration"]["pages"] = []
190
- dfPage = list_report_pages(report=report, workspace=workspace_id)
191
-
192
186
  for page in page_name:
193
- dfPage_filt = dfPage[dfPage["Page ID"] == page]
194
- if len(dfPage_filt) == 0:
195
- raise ValueError(
196
- f"{icons.red_dot} The '{page}' page does not exist in the '{report}' report within the '{workspace_name}' workspace."
197
- )
198
-
199
187
  page_dict = {"pageName": page}
200
188
  request_body["powerBIReportConfiguration"]["pages"].append(page_dict)
201
189
 
@@ -209,19 +197,9 @@ def export_report(
209
197
  request_body = {"format": export_format, "powerBIReportConfiguration": {}}
210
198
 
211
199
  request_body["powerBIReportConfiguration"]["pages"] = []
212
- dfVisual = list_report_visuals(report=report, workspace=workspace_id)
213
200
  a = 0
214
201
  for page in page_name:
215
202
  visual = visual_name[a]
216
-
217
- dfVisual_filt = dfVisual[
218
- (dfVisual["Page ID"] == page) & (dfVisual["Visual ID"] == visual)
219
- ]
220
- if len(dfVisual_filt) == 0:
221
- raise ValueError(
222
- f"{icons.red_dot} The '{visual}' visual does not exist on the '{page}' in the '{report}' report within the '{workspace_name}' workspace."
223
- )
224
-
225
203
  page_dict = {"pageName": page, "visualName": visual}
226
204
  request_body["powerBIReportConfiguration"]["pages"].append(page_dict)
227
205
  a += 1
@@ -1,4 +1,6 @@
1
1
  from sempy_labs._helper_functions import (
2
+ resolve_item_id,
3
+ resolve_workspace_id,
2
4
  resolve_workspace_name_and_id,
3
5
  resolve_item_name_and_id,
4
6
  _base_api,
@@ -8,6 +10,7 @@ from typing import Optional, List
8
10
  from sempy._utils._log import log
9
11
  import sempy_labs._icons as icons
10
12
  from uuid import UUID
13
+ import sempy.fabric as fabric
11
14
 
12
15
 
13
16
  @log
@@ -108,57 +111,40 @@ def report_rebind_all(
108
111
  the new semantic model.
109
112
  """
110
113
 
111
- from sempy_labs._list_functions import list_reports_using_semantic_model
112
-
113
- (dataset_name, dataset_id) = resolve_dataset_name_and_id(
114
- dataset=dataset, workspace=dataset_workspace
114
+ (dataset_name, dataset_id) = resolve_item_name_and_id(
115
+ item=dataset, type="SemanticModel", workspace=dataset_workspace
115
116
  )
116
- (new_dataset_name, new_dataset_id) = resolve_dataset_name_and_id(
117
- dataset=new_dataset, workspace=new_dataset_workspace
117
+ new_dataset_id = resolve_item_id(
118
+ item=new_dataset, type="SemanticModel", workspace=new_dataset_workspace
118
119
  )
119
120
 
120
121
  if dataset_id == new_dataset_id:
121
122
  raise ValueError(
122
- f"{icons.red_dot} The 'dataset' and 'new_dataset' parameters are both set to '{dataset}'. These parameters must be set to different values."
123
+ f"{icons.red_dot} The 'dataset' and 'new_dataset' parameters are both set to the same semantic model within the same workspace. These parameters must be set to different values."
123
124
  )
124
- (dataset_workspace_name, dataset_workspace_id) = resolve_workspace_name_and_id(
125
- workspace=dataset_workspace
126
- )
125
+ dataset_workspace_id = resolve_workspace_id(workspace=dataset_workspace)
127
126
 
128
- if isinstance(report_workspace, str):
127
+ if isinstance(report_workspace, str) or report_workspace is None:
129
128
  report_workspace = [report_workspace]
130
129
 
131
- dfR = list_reports_using_semantic_model(
132
- dataset=dataset, workspace=dataset_workspace
133
- )
134
-
135
- if dfR.empty:
136
- print(
137
- f"{icons.info} The '{dataset_name}' semantic model within the '{dataset_workspace_name}' workspace has no dependent reports."
138
- )
139
- return
140
-
141
- if report_workspace is None:
142
- dfR_filt = dfR.copy()
143
- else:
130
+ for w in report_workspace:
131
+ dfR = fabric.list_reports(workspace=w)
144
132
  dfR_filt = dfR[
145
- (dfR["Report Workspace Name"].isin(report_workspace))
146
- | (dfR["Report Workspace ID"].isin(report_workspace))
133
+ (dfR["Dataset ID"] == dataset_id)
134
+ & (dfR["Dataset Workspace Id"] == dataset_workspace_id)
147
135
  ]
148
-
149
- if dfR_filt.empty:
150
- print(
151
- f"{icons.info} No reports found for the '{dataset_name}' semantic model within the '{dataset_workspace_name}' workspace."
152
- )
153
- return
154
-
155
- for _, r in dfR_filt.iterrows():
156
- rpt_name = r["Report Name"]
157
- rpt_wksp = r["Report Workspace Name"]
158
-
159
- report_rebind(
160
- report=rpt_name,
161
- dataset=new_dataset,
162
- report_workspace=rpt_wksp,
163
- dataset_workspace=new_dataset_workspace,
164
- )
136
+ if dfR_filt.empty:
137
+ (wksp_name, _) = resolve_workspace_name_and_id(workspace=w)
138
+ print(
139
+ f"{icons.info} No reports found for the '{dataset_name}' semantic model within the '{wksp_name}' workspace."
140
+ )
141
+ else:
142
+ # Rebind reports to new dataset
143
+ for _, r in dfR_filt.iterrows():
144
+ rpt_name = r["Name"]
145
+ report_rebind(
146
+ report=rpt_name,
147
+ dataset=new_dataset,
148
+ report_workspace=w,
149
+ dataset_workspace=new_dataset_workspace,
150
+ )
@@ -171,6 +171,7 @@ class ReportWrapper:
171
171
  self,
172
172
  file_path: str,
173
173
  json_path: Optional[str] = None,
174
+ verbose: bool = True,
174
175
  ) -> dict | List[Tuple[str, dict]]:
175
176
  """
176
177
  Get the json content of the specified report definition file.
@@ -181,6 +182,8 @@ class ReportWrapper:
181
182
  The path of the report definition file. For example: "definition/pages/pages.json". You may also use wildcards. For example: "definition/pages/*/page.json".
182
183
  json_path : str, default=None
183
184
  The json path to the specific part of the file to be retrieved. If None, the entire file content is returned.
185
+ verbose : bool, default=True
186
+ If True, prints messages about the retrieval process. If False, suppresses these messages.
184
187
 
185
188
  Returns
186
189
  -------
@@ -192,6 +195,7 @@ class ReportWrapper:
192
195
 
193
196
  # Find matching parts
194
197
  if "*" in file_path:
198
+ results = []
195
199
  matching_parts = [
196
200
  (part.get("path"), part.get("payload"))
197
201
  for part in parts
@@ -199,9 +203,11 @@ class ReportWrapper:
199
203
  ]
200
204
 
201
205
  if not matching_parts:
202
- raise ValueError(
203
- f"{icons.red_dot} No files match the wildcard path '{file_path}'."
204
- )
206
+ if verbose:
207
+ print(
208
+ f"{icons.red_dot} No files match the wildcard path '{file_path}'."
209
+ )
210
+ return results
205
211
 
206
212
  results = []
207
213
  for path, payload in matching_parts:
@@ -220,8 +226,8 @@ class ReportWrapper:
220
226
  # raise ValueError(
221
227
  # f"{icons.red_dot} No match found for '{json_path}' in '{path}'."
222
228
  # )
223
- if not results:
224
- raise ValueError(
229
+ if not results and verbose:
230
+ print(
225
231
  f"{icons.red_dot} No match found for '{json_path}' in any of the files matching the wildcard path '{file_path}'."
226
232
  )
227
233
  return results
@@ -241,14 +247,11 @@ class ReportWrapper:
241
247
  matches = jsonpath_expr.find(payload)
242
248
  if matches:
243
249
  return matches[0].value
244
- else:
245
- raise ValueError(
246
- f"{icons.red_dot} No match found for '{json_path}'."
247
- )
250
+ elif verbose:
251
+ print(f"{icons.red_dot} No match found for '{json_path}'.")
248
252
 
249
- raise ValueError(
250
- f"{icons.red_dot} File '{file_path}' not found in report definition."
251
- )
253
+ if verbose:
254
+ print(f"{icons.red_dot} File '{file_path}' not found in report definition.")
252
255
 
253
256
  def add(self, file_path: str, payload: dict | bytes):
254
257
  """
@@ -674,33 +677,65 @@ class ReportWrapper:
674
677
  columns = {
675
678
  "Custom Visual Name": "str",
676
679
  "Custom Visual Display Name": "str",
680
+ "Is Public": "bool",
677
681
  "Used in Report": "bool",
678
682
  }
679
683
 
680
684
  df = _create_dataframe(columns=columns)
681
685
 
682
- report_file = self.get(file_path=self._report_file_path)
686
+ visuals = []
687
+ rp = self.get(
688
+ file_path=self._report_file_path,
689
+ json_path="$.resourcePackages",
690
+ verbose=False,
691
+ )
692
+
693
+ if rp:
694
+ visuals += [
695
+ {"Custom Visual Name": item.get("name"), "Is Public": False}
696
+ for item in rp
697
+ if item.get("type") == "CustomVisual"
698
+ ]
683
699
 
684
- df["Custom Visual Name"] = report_file.get("publicCustomVisuals")
685
- df["Custom Visual Display Name"] = df["Custom Visual Name"].apply(
686
- lambda x: helper.vis_type_mapping.get(x, x)
700
+ # Load public custom visuals
701
+ public_custom_visuals = (
702
+ self.get(
703
+ file_path=self._report_file_path,
704
+ json_path="$.publicCustomVisuals",
705
+ verbose=False,
706
+ )
707
+ or []
687
708
  )
688
709
 
689
- visual_types = set()
690
- for v in self.__all_visuals():
691
- payload = v.get("payload", {})
692
- visual = payload.get("visual", {})
693
- visual_type = visual.get("visualType")
694
- if visual_type:
695
- visual_types.add(visual_type)
696
-
697
- for _, r in df.iterrows():
698
- if r["Custom Visual Name"] in visual_types:
699
- df.at[_, "Used in Report"] = True
700
- else:
701
- df.at[_, "Used in Report"] = False
710
+ visuals += [
711
+ {
712
+ "Custom Visual Name": (
713
+ item.get("name") if isinstance(item, dict) else item
714
+ ),
715
+ "Is Public": True,
716
+ }
717
+ for item in public_custom_visuals
718
+ ]
702
719
 
703
- _update_dataframe_datatypes(dataframe=df, column_map=columns)
720
+ if visuals:
721
+ df = pd.DataFrame(visuals, columns=list(columns.keys()))
722
+
723
+ # df["Custom Visual Name"] = report_file.get("publicCustomVisuals")
724
+ df["Custom Visual Display Name"] = df["Custom Visual Name"].apply(
725
+ lambda x: helper.vis_type_mapping.get(x, x)
726
+ )
727
+
728
+ visual_types = set()
729
+ for v in self.__all_visuals():
730
+ payload = v.get("payload", {})
731
+ visual = payload.get("visual", {})
732
+ visual_type = visual.get("visualType")
733
+ if visual_type:
734
+ visual_types.add(visual_type)
735
+
736
+ df["Used in Report"] = df["Custom Visual Name"].isin(visual_types)
737
+
738
+ _update_dataframe_datatypes(dataframe=df, column_map=columns)
704
739
 
705
740
  return df
706
741