semantic-link-labs 0.12.1__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.

@@ -301,3 +301,165 @@ def send_mail(
301
301
  if attachments:
302
302
  printout += f" with {len(attachments)} attachment(s)"
303
303
  print(f"{printout}.")
304
+
305
+
306
+ @log
307
+ def create_user(
308
+ display_name: str,
309
+ user_principal_name: str,
310
+ mail_nickname: str,
311
+ password: str,
312
+ account_enabled: bool = True,
313
+ force_change_password_next_sign_in: bool = True,
314
+ ):
315
+ """
316
+ Creates a new user.
317
+
318
+ This is a wrapper function for the following API: `Create User <https://learn.microsoft.com/graph/api/user-post-users>`_.
319
+
320
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
321
+
322
+ Parameters
323
+ ----------
324
+ display_name : str
325
+ The name to display in the address book for the user.
326
+ user_principal_name : str
327
+ The user principal name (someuser@contoso.com).
328
+ mail_nickname : str
329
+ The mail alias for the user.
330
+ password : str
331
+ The initial password for the user.
332
+ account_enabled : bool, default=True
333
+ Whether the account is enabled. Default is True.
334
+ force_change_password_next_sign_in : bool, default=True
335
+ Whether the user must change their password on next sign-in. Default is True.
336
+ """
337
+
338
+ payload = {
339
+ "accountEnabled": account_enabled,
340
+ "displayName": display_name,
341
+ "mailNickname": mail_nickname,
342
+ "userPrincipalName": user_principal_name,
343
+ "passwordProfile": {
344
+ "forceChangePasswordNextSignIn": force_change_password_next_sign_in,
345
+ "password": password,
346
+ },
347
+ }
348
+
349
+ _base_api(
350
+ request="users",
351
+ client="graph",
352
+ status_codes=201,
353
+ payload=payload,
354
+ method="post",
355
+ )
356
+
357
+ print(f"{icons.green_dot} The '{display_name}' user has been created successfully.")
358
+
359
+
360
+ @log
361
+ def delete_user(user: str | UUID):
362
+ """
363
+ Deletes a user.
364
+
365
+ This is a wrapper function for the following API: `Delete User <https://learn.microsoft.com/graph/api/user-delete>`_.
366
+
367
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
368
+
369
+ Parameters
370
+ ----------
371
+ user : str | uuid.UUID
372
+ The user name or ID.
373
+ """
374
+
375
+ user_id = resolve_user_id(user)
376
+
377
+ _base_api(
378
+ request=f"users/{user_id}",
379
+ client="graph",
380
+ status_codes=204,
381
+ method="delete",
382
+ )
383
+
384
+ print(f"{icons.green_dot} The '{user}' user has been deleted successfully.")
385
+
386
+
387
+ @log
388
+ def update_user(
389
+ user: str | UUID,
390
+ display_name: Optional[str] = None,
391
+ user_principal_name: Optional[str] = None,
392
+ given_name: Optional[str] = None,
393
+ surname: Optional[str] = None,
394
+ job_title: Optional[str] = None,
395
+ mail_nickname: Optional[str] = None,
396
+ my_site: Optional[str] = None,
397
+ office_location: Optional[str] = None,
398
+ account_enabled: Optional[bool] = None,
399
+ ):
400
+ """
401
+ Updates a user's properties.
402
+
403
+ This is a wrapper function for the following API: `Update user <https://learn.microsoft.com/graph/api/user-update>`_.
404
+
405
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
406
+
407
+ Parameters
408
+ ----------
409
+ user : str | uuid.UUID
410
+ The user name or ID.
411
+ display_name : str, default=None
412
+ The name displayed in the address book for the user.
413
+ user_principal_name : str, default=None
414
+ The user principal name (UPN) of the user.
415
+ given_name : str, default=None
416
+ The given name (first name) of the user.
417
+ surname : str, default=None
418
+ The user's surname (family name or last name).
419
+ job_title : str, default=None
420
+ The user's job title.
421
+ mail_nickname : str, default=None
422
+ The mail alias for the user. This property must be specified when a user is created.
423
+ my_site : str, default=None
424
+ The URL for the user's personal site.
425
+ office_location : str, default=None
426
+ The office location in the user's place of business.
427
+ account_enabled : bool, default=None
428
+ Whether the account is enabled. If None, the property will not be updated.
429
+ """
430
+
431
+ user_id = resolve_user_id(user)
432
+
433
+ payload = {}
434
+ if display_name is not None:
435
+ payload["displayName"] = display_name
436
+ if mail_nickname is not None:
437
+ payload["mailNickname"] = mail_nickname
438
+ if user_principal_name is not None:
439
+ payload["userPrincipalName"] = user_principal_name
440
+ if given_name is not None:
441
+ payload["givenName"] = given_name
442
+ if job_title is not None:
443
+ payload["jobTitle"] = job_title
444
+ if my_site is not None:
445
+ payload["mySite"] = my_site
446
+ if office_location is not None:
447
+ payload["officeLocation"] = office_location
448
+ if surname is not None:
449
+ payload["surname"] = surname
450
+ if account_enabled is not None and isinstance(account_enabled, bool):
451
+ payload["accountEnabled"] = account_enabled
452
+
453
+ if not payload:
454
+ print(f"{icons.info} No properties to update.")
455
+ return
456
+
457
+ _base_api(
458
+ request=f"users/{user_id}",
459
+ client="graph",
460
+ status_codes=204,
461
+ payload=payload,
462
+ method="patch",
463
+ )
464
+
465
+ print(f"{icons.green_dot} The '{user}' user has been updated successfully.")
@@ -396,17 +396,22 @@ def list_shortcuts(
396
396
  # Cache and use it to getitem type and name
397
397
  source_item_type = None
398
398
  source_item_name = None
399
- dfI = source_items_df[
400
- source_items_df["Workspace Id"] == source_workspace_id
401
- ]
402
- if dfI.empty:
403
- dfI = fabric.list_items(workspace=source_workspace_id)
404
- source_items_df = pd.concat([source_items_df, dfI], ignore_index=True)
405
-
406
- dfI_filt = dfI[dfI["Id"] == source_item_id]
407
- if not dfI_filt.empty:
408
- source_item_type = dfI_filt["Type"].iloc[0]
409
- source_item_name = dfI_filt["Display Name"].iloc[0]
399
+ try:
400
+ dfI = source_items_df[
401
+ source_items_df["Workspace Id"] == source_workspace_id
402
+ ]
403
+ if dfI.empty:
404
+ dfI = fabric.list_items(workspace=source_workspace_id)
405
+ source_items_df = pd.concat(
406
+ [source_items_df, dfI], ignore_index=True
407
+ )
408
+
409
+ dfI_filt = dfI[dfI["Id"] == source_item_id]
410
+ if not dfI_filt.empty:
411
+ source_item_type = dfI_filt["Type"].iloc[0]
412
+ source_item_name = dfI_filt["Display Name"].iloc[0]
413
+ except Exception:
414
+ pass
410
415
 
411
416
  rows.append(
412
417
  {
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": "1.0",
3
+ "remoteArtifacts": [
4
+ {
5
+ "reportId": "6a91c344-dba8-4ebf-bedb-e07134f2a204"
6
+ }
7
+ ],
8
+ "securityBindingsSignature": "AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAMAVu7l76YU6Sl11KOiJdgQAAAAACAAAAAAAQZgAAAAEAACAAAAD1Ty+c7tZLT9/Sjguxhn/5ivfLWfUMNtgudyJ3BKzzZgAAAAAOgAAAAAIAACAAAABAyGf+iKMwvmNtcoRczjgebeIm0nlc9SFYTBYv3N7yvVADAADQN3JsIsvJUcKKc9WMo2EhiE21odezpd35tb+yudHwA/RYhypMA3fwiCTwArLefBZQ3vZ7KYh4MjihXS07i9o1XVqxAmDoli83Yhs/Wei+0HIfYOT5HOVHLUEul5x41Yx/7Bdfhc881SK6IoaJogBdwsiJVxPne+niMYqJQA6qLEPyJ33g6ucUxLA40lwdbN2cMWFzRn6tymmicDPwH0hcGPDMWwseAU+OuUeidkneRWhUGs6lkiiXLiO6kmY5RKq+S4FdtR19/e1B6EjAd94zSw+M5jQzYxn4eCZzWYiB+8Zd/jy07lfyLoGwagNqiQzbcNONqQd5w0n+8/+n4zGkBi2UojfRXoGaYDirQeZMTbt3pfPx2PArxsJ8dF0iT634pHiCF1ZFdtY+79JaFLUUG+Yf7JJv8IxuuuF74tAp4NYmuOij4hTDaf8Jafa5IoRVh7ICkwrjJyVQ8dG7I3tr0VvR+toBPG3Zlbm9BijcaBxhh1AINhnRAIkENOnPFQVH7l3Ml7B60H8Tst6ic3ihCCMYjtmN+NNWqFrJKT2trilh5TAxN+ei4H5fPwM9S7zb2bH5jhExcYTtoe7iCzxOvBsoYoFM+7FMjn9R2FATNICktYdbKDo1Of+u4oZ1+RsvBHQBVaMhSCoZ7+K5T5pZayNK3V2UID3wOuLOYvouxXXr4NVFsdgiV2oMuxTWeqmd/4bLxeqe3uTkGFmQU4mumF2YVsNbdO3IcRXhhrCCZ27ffzXBsH+lE3EhusD37Z0dsVbVVlG8AHXCh7Atgd8n73/eSI5mvj36DCOSRBVauItIATIa2FXueKA7vU6lRDYBSX8FCC2qkeN6dWpMoN5uXXEBsb5Yot1Fgrovcyl5lk7rh772Xon4FaIYFHZpklsY3JK5EXp3bF8UOE6ByN1ZucmkGgYRcTT/up/Uc86TLN6env9XXL4FQYPlReiOGWKBLVi9OoXGRLDshspniULtV3EwQ6WsjF2AyQ+WdLj3bbWKzG5Mg9jvANLrjycZAGWskh4X5JDGiv4TiJmnYQ/xPZAKKiowpVIHikLeG76uXFI+bxtpihV9+DaEJy4UxisHQxwuvUsQs38u3SHgpJmT8CNssZl41+T/IJdoQwJFLUAAAACnUQZGV9DvcOyrj8HBpXBVB5PuOQDxLB4HZOevHqCB5dc5z787E93B51QmN7I15fF6GCdWwN5f94gv1er2dtN3"
9
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/fabric/gitIntegration/platformProperties/2.0.0/schema.json",
3
+ "metadata": {
4
+ "type": "Report",
5
+ "displayName": "BPAReport"
6
+ },
7
+ "config": {
8
+ "version": "2.0",
9
+ "logicalId": "a201f2cd-fd25-465f-bfbc-33b151e38b31"
10
+ }
11
+ }
@@ -935,9 +935,6 @@ class ReportWrapper:
935
935
  """
936
936
  Shows a list of all modified `visual interactions <https://learn.microsoft.com/power-bi/create-reports/service-reports-visual-interactions?tabs=powerbi-desktop>`_ used in the report.
937
937
 
938
- Parameters
939
- ----------
940
-
941
938
  Returns
942
939
  -------
943
940
  pandas.DataFrame
@@ -980,6 +977,56 @@ class ReportWrapper:
980
977
 
981
978
  return df
982
979
 
980
+ def list_visual_calculations(self) -> pd.DataFrame:
981
+ """
982
+ Shows a list of all `visual calculations <https://learn.microsoft.com/power-bi/transform-model/desktop-visual-calculations-overview>`_.
983
+
984
+ Returns
985
+ -------
986
+ pandas.DataFrame
987
+ A pandas dataframe containing a list of all visual calculations within the report.
988
+ """
989
+
990
+ self._ensure_pbir()
991
+
992
+ columns = {
993
+ "Page Display Name": "str",
994
+ "Visual Name": "str",
995
+ "Name": "str",
996
+ "Language": "str",
997
+ "Expression": "str",
998
+ }
999
+
1000
+ df = _create_dataframe(columns=columns)
1001
+ visual_mapping = self._visual_page_mapping()
1002
+
1003
+ rows = []
1004
+ for v in self.__all_visuals():
1005
+ path = v.get("path")
1006
+ payload = v.get("payload")
1007
+ page_name = visual_mapping.get(path)[0]
1008
+ page_display_name = visual_mapping.get(path)[1]
1009
+ visual_name = payload.get("name")
1010
+ matches = parse("$..field.NativeVisualCalculation").find(payload)
1011
+ if matches:
1012
+ for match in matches:
1013
+ m = match.value
1014
+ rows.append(
1015
+ {
1016
+ "Page Display Name": page_display_name,
1017
+ "Page Name": page_name,
1018
+ "Visual Name": visual_name,
1019
+ "Name": m.get("Name"),
1020
+ "Language": m.get("Language"),
1021
+ "Expression": m.get("Expression"),
1022
+ }
1023
+ )
1024
+
1025
+ if rows:
1026
+ df = pd.DataFrame(rows, columns=list(columns.keys()))
1027
+
1028
+ return df
1029
+
983
1030
  def list_pages(self) -> pd.DataFrame:
984
1031
  """
985
1032
  Shows a list of all pages in the report.
@@ -1651,7 +1698,7 @@ class ReportWrapper:
1651
1698
  bookmarks = [
1652
1699
  o
1653
1700
  for o in self._report_definition.get("parts")
1654
- if o.get("path").endswith("/bookmark.json")
1701
+ if o.get("path").endswith(".bookmark.json")
1655
1702
  ]
1656
1703
 
1657
1704
  rows = []
@@ -2069,8 +2116,8 @@ class ReportWrapper:
2069
2116
  df = dfCV[dfCV["Used in Report"] == False]
2070
2117
 
2071
2118
  if not df.empty:
2072
- cv_remove = df["Custom Visual Name"].values()
2073
- cv_remove_display = df["Custom Visual Display Name"].values()
2119
+ cv_remove = df["Custom Visual Name"].values
2120
+ cv_remove_display = df["Custom Visual Display Name"].values
2074
2121
  else:
2075
2122
  print(
2076
2123
  f"{icons.red_dot} There are no unnecessary custom visuals in the '{self._report_name}' report within the '{self._workspace_name}' workspace."
sempy_labs/tom/_model.py CHANGED
@@ -6,7 +6,7 @@ import os
6
6
  import json
7
7
  from datetime import datetime
8
8
  from decimal import Decimal
9
- from .._helper_functions import (
9
+ from sempy_labs._helper_functions import (
10
10
  format_dax_object_name,
11
11
  generate_guid,
12
12
  _make_list_unique,
@@ -17,10 +17,11 @@ from .._helper_functions import (
17
17
  resolve_item_id,
18
18
  resolve_lakehouse_id,
19
19
  _validate_weight,
20
+ _get_url_prefix,
20
21
  )
21
- from .._list_functions import list_relationships
22
- from .._refresh_semantic_model import refresh_semantic_model
23
- from ..directlake._dl_helper import check_fallback_reason
22
+ from sempy_labs._list_functions import list_relationships
23
+ from sempy_labs._refresh_semantic_model import refresh_semantic_model
24
+ from sempy_labs.directlake._dl_helper import check_fallback_reason
24
25
  from contextlib import contextmanager
25
26
  from typing import List, Iterator, Optional, Union, TYPE_CHECKING, Literal
26
27
  from sempy._utils._log import log
@@ -28,7 +29,9 @@ import sempy_labs._icons as icons
28
29
  import ast
29
30
  from uuid import UUID
30
31
  import sempy_labs._authentication as auth
31
- from ..lakehouse._lakehouse import lakehouse_attached
32
+ from sempy_labs.lakehouse._lakehouse import lakehouse_attached
33
+ import requests
34
+ from sempy.fabric.exceptions import FabricHTTPException
32
35
 
33
36
 
34
37
  if TYPE_CHECKING:
@@ -4601,8 +4604,12 @@ class TOMWrapper:
4601
4604
  pandas.DataFrame
4602
4605
  A pandas dataframe showing the updated measure(s) and their new description(s).
4603
4606
  """
4607
+ import notebookutils
4608
+
4604
4609
  icons.sll_tags.append("GenerateMeasureDescriptions")
4605
4610
 
4611
+ prefix = _get_url_prefix()
4612
+
4606
4613
  df = pd.DataFrame(
4607
4614
  columns=["Table Name", "Measure Name", "Expression", "Description"]
4608
4615
  )
@@ -4655,11 +4662,17 @@ class TOMWrapper:
4655
4662
  "modelItems"
4656
4663
  ].append(new_item)
4657
4664
 
4658
- response = _base_api(
4659
- request="/explore/v202304/nl2nl/completions",
4660
- method="post",
4661
- payload=payload,
4665
+ token = notebookutils.credentials.getToken("pbi")
4666
+ headers = {"Authorization": f"Bearer {token}"}
4667
+ response = requests.post(
4668
+ f"{prefix}/explore/v202304/nl2nl/completions",
4669
+ headers=headers,
4670
+ json=payload,
4662
4671
  )
4672
+ if response.status_code != 200:
4673
+ raise FabricHTTPException(
4674
+ f"Failed to retrieve descriptions: {response.text}"
4675
+ )
4663
4676
 
4664
4677
  for item in response.json().get("modelItems", []):
4665
4678
  ms_name = item["urn"]