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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: semantic-link-labs
3
- Version: 0.8.7
3
+ Version: 0.8.9
4
4
  Summary: Semantic Link Labs for Microsoft Fabric
5
5
  Author: Microsoft Corporation
6
6
  License: MIT License
@@ -27,7 +27,7 @@ Requires-Dist: pytest>=8.2.1; extra == "test"
27
27
  # Semantic Link Labs
28
28
 
29
29
  [![PyPI version](https://badge.fury.io/py/semantic-link-labs.svg)](https://badge.fury.io/py/semantic-link-labs)
30
- [![Read The Docs](https://readthedocs.org/projects/semantic-link-labs/badge/?version=0.8.7&style=flat)](https://readthedocs.org/projects/semantic-link-labs/)
30
+ [![Read The Docs](https://readthedocs.org/projects/semantic-link-labs/badge/?version=0.8.9&style=flat)](https://readthedocs.org/projects/semantic-link-labs/)
31
31
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
32
32
  [![Downloads](https://static.pepy.tech/badge/semantic-link-labs)](https://pepy.tech/project/semantic-link-labs)
33
33
 
@@ -141,6 +141,8 @@ An even better way to ensure the semantic-link-labs library is available in your
141
141
  2. Select your newly created environment within the 'Environment' drop down in the navigation bar at the top of the notebook
142
142
 
143
143
  ## Version History
144
+ * [0.8.9](https://github.com/microsoft/semantic-link-labs/releases/tag/0.8.9) (December 4, 2024)
145
+ * [0.8.8](https://github.com/microsoft/semantic-link-labs/releases/tag/0.8.8) (November 28, 2024)
144
146
  * [0.8.7](https://github.com/microsoft/semantic-link-labs/releases/tag/0.8.7) (November 27, 2024)
145
147
  * [0.8.6](https://github.com/microsoft/semantic-link-labs/releases/tag/0.8.6) (November 14, 2024)
146
148
  * [0.8.5](https://github.com/microsoft/semantic-link-labs/releases/tag/0.8.5) (November 13, 2024)
@@ -1,13 +1,13 @@
1
- sempy_labs/__init__.py,sha256=YFVryT8HOZZ-A8gRG7A5f_R0TtQmtUoEJk0ZeoNSNJQ,12780
1
+ sempy_labs/__init__.py,sha256=Xi71QoSKFo8eexzMFaZordi1NE7TtqhiyXRZfltMHR4,12852
2
2
  sempy_labs/_ai.py,sha256=CzsNw6Wpd2B5Rd0RcY250-_p0L-0gFoMNLEc_KmrobU,16177
3
3
  sempy_labs/_authentication.py,sha256=LWkl0yoz63tYe5u9DL1js7g88vmkVbcTIqrsaxDHMT0,3851
4
4
  sempy_labs/_capacities.py,sha256=HWX1ivlWpyS7Ea_ny-39kUAQYFGMzo42kWMGdJMINos,25466
5
5
  sempy_labs/_capacity_migration.py,sha256=PCIodWXas9v7q93hqD2m8EanJHPJzke52jbCWOfnLZk,27764
6
6
  sempy_labs/_clear_cache.py,sha256=ttHsXXR6SRRw4eC0cS8I5h38UbWU9YJii1y-uR9R3KM,12493
7
- sempy_labs/_connections.py,sha256=ZG8Ozy6MJH2b_oHnfZyjDuLDj7nTHHo5ulI-Vvw1epo,17648
7
+ sempy_labs/_connections.py,sha256=-Z3rfLGlUKx5iIGmvwWOICIVZ478ydwvCjxusoJb1RI,17647
8
8
  sempy_labs/_data_pipelines.py,sha256=WdZjTELNuN_7suWj6NrZUxGnMTzAgIxFw8V6YMb8ags,5644
9
9
  sempy_labs/_dataflows.py,sha256=Wpx5jmTDewTkL2bsVZ5r_PTD0etIBdKXVigTjUF8HAg,8252
10
- sempy_labs/_dax.py,sha256=oYNwMaHusxoqQmfi_S6iF2X5o29dCM7cb2eIiLpFlas,8605
10
+ sempy_labs/_dax.py,sha256=5lY0p9bS0O2OWViaqTw_9K0WfZsyBW3gK4rIv1bqjjE,9411
11
11
  sempy_labs/_deployment_pipelines.py,sha256=WBBQM85-3-MkXb5OmRPF6U83xLyhKSlYUyhRlkvcl4k,6027
12
12
  sempy_labs/_documentation.py,sha256=yVA8VPEzx_fmljtcvSxtB7-BeupYsfdMXXjp6Fpnyo8,5007
13
13
  sempy_labs/_environments.py,sha256=avpLSfZyyQFdEDIIxWv2THLjPZwbs9XGXT7ob9l_-ao,5326
@@ -15,7 +15,7 @@ sempy_labs/_eventhouses.py,sha256=vgIFQkXcBPC4SnlrBzT7SRmembExxkm6n0gdKnc7Hlk,40
15
15
  sempy_labs/_eventstreams.py,sha256=Rht0eWoZbYF6XKtE3AOUvGgA21smxC9gdN599z-jY3s,4061
16
16
  sempy_labs/_external_data_shares.py,sha256=lUsKy1mexNSmhyFwxSeE2jZKNdDAWDP6iC6UPTXCvyU,6799
17
17
  sempy_labs/_gateways.py,sha256=CzTS95tLpG1bIG-0XkJWniNQEyNUtRq2go4QJAvjMr4,14617
18
- sempy_labs/_generate_semantic_model.py,sha256=au4amuYc35n0lyNAz3dYkJAfHfd2nkRpBybVY2ZQI90,15212
18
+ sempy_labs/_generate_semantic_model.py,sha256=ed5Wivz8ZqdKghpzkJD34onByttCaKVzwsjkndK-Qk4,17061
19
19
  sempy_labs/_git.py,sha256=C9TYKG4g8q35R1S7iTZyjMtRis033uUWkYJe_a0f3u0,13540
20
20
  sempy_labs/_helper_functions.py,sha256=Dk1cYfctxn_0RJHRKuqelcpaNJxyhoBZwWbvb0CGumU,38051
21
21
  sempy_labs/_icons.py,sha256=ez2dx_LCti71S_-eB6WYQ-kOMyiBL8ZJN12-ev5dcmA,3579
@@ -29,21 +29,21 @@ sempy_labs/_ml_experiments.py,sha256=UVh3cwNvpY-otCBIaKW-sgtzyjwAuu8qJDLhZGBHToE
29
29
  sempy_labs/_ml_models.py,sha256=phYLySjN7MO2YYfq7ZQKMS6w18G6L1-7DdNWB4fcLjQ,4044
30
30
  sempy_labs/_model_auto_build.py,sha256=PTQo3dufzLSFcQ5shFkmBWAVSdP7cTJgpUclrcXyNbg,5105
31
31
  sempy_labs/_model_bpa.py,sha256=Tyj7JV4L0rCmeWL896S58SOA8SHQKNxQ9QnmqunKTfM,21764
32
- sempy_labs/_model_bpa_bulk.py,sha256=WoJhN_A0X3qkaI624NS0PTq7r8x9uSdFtQEnOmiV7Dc,16003
32
+ sempy_labs/_model_bpa_bulk.py,sha256=wJe8KEMvbU3T--7vOBy3nXE2L6WZEolwrRH4WAxgsAM,16030
33
33
  sempy_labs/_model_bpa_rules.py,sha256=96_GkXQGhON-_uyUATgUibk4W9y7e9wl1QciUr96gIQ,45544
34
- sempy_labs/_model_dependencies.py,sha256=D_I3nwBMJ8YaxVz4x-W3P5qHjtukYdjH_FSFrvhsE94,11676
34
+ sempy_labs/_model_dependencies.py,sha256=AclITMJLRI33DvAqMJrgbnUhCubj7qpsHh1_lCWqcrg,11840
35
35
  sempy_labs/_notebooks.py,sha256=EUYVeRJrCL9IllQevwRxzkCUU-rzX6KEEH7x7mBYUqc,7422
36
36
  sempy_labs/_one_lake_integration.py,sha256=eIuLxlw8eXfUH2avKhsyLmXZbTllSwGsz2j_HMAikpQ,6234
37
37
  sempy_labs/_query_scale_out.py,sha256=xoHnuDUgPYsg-NlUplB9ieb0bClcBQeG4veJNo_4TNw,15708
38
38
  sempy_labs/_refresh_semantic_model.py,sha256=R781zVHTnoLw5mzzcSc39OPBmKpzoBxsOa1KhvqyEgw,17131
39
39
  sempy_labs/_spark.py,sha256=RIJt9b_l5Sp5XrebhvRD0DEBKDTQdA8Rh7fByV27ngQ,20109
40
40
  sempy_labs/_sql.py,sha256=KttKi95iGxTT8UA1QOpT9ygAdwCfHHlcQSQ5d9gml0E,5358
41
- sempy_labs/_translations.py,sha256=2DpP--U3d8Gp7o9LF-OnZa10onN2unvqSHVQHv3CBZg,19838
41
+ sempy_labs/_translations.py,sha256=CVRd_yJ1pjUzxY_6H8tSCLh67bHhxRyS7DICY20Lqlc,16112
42
42
  sempy_labs/_vertipaq.py,sha256=sS9wFPxZfr_5dsOIXd-oeQIeCyXkVeCHbp30Kd7raUU,37662
43
43
  sempy_labs/_warehouses.py,sha256=KI7Ww5Slw4jfhby4ensGVlDHLWq6u2SvdMCa2R9i778,7205
44
44
  sempy_labs/_workloads.py,sha256=x3dS2mOkrS9rA-p70z8849DZlMIvMbzTjMzO_YmnHRg,4449
45
45
  sempy_labs/_workspace_identity.py,sha256=d5cdiiqjyUVoSoDiqU8mzWYOvbt2oJrt7sm-ZGEEkDk,2261
46
- sempy_labs/_workspaces.py,sha256=YdLuKBWBqvzYoGZSkWeAce-XxardzjpnpiaCMiE1aGI,11200
46
+ sempy_labs/_workspaces.py,sha256=Ya_F-2b9LU08xN9bKKRS5LeGs_Ie2o4h26Xgy57IExk,11234
47
47
  sempy_labs/_bpa_translation/_model/_translations_am-ET.po,sha256=zQVjJ-t0vtgIYan-HaXtUVJLB_PJvB53Nf5BNoOReU4,39199
48
48
  sempy_labs/_bpa_translation/_model/_translations_ar-AE.po,sha256=QP1PjDLFccLDs9zq456crdAST57wrcWVk5rRiqqoCws,36959
49
49
  sempy_labs/_bpa_translation/_model/_translations_bg-BG.po,sha256=sqezjpS3wfk09WD7x27bHoCBtgmqeHtyHNKTwG7-bkI,44132
@@ -82,13 +82,13 @@ sempy_labs/_bpa_translation/_model/_translations_tr-TR.po,sha256=NdW-X4E0QmeLKM0
82
82
  sempy_labs/_bpa_translation/_model/_translations_uk-UA.po,sha256=3NsFN8hoor_5L6738FjpJ8o4spwp8FNFGbVItHD-_ec,43500
83
83
  sempy_labs/_bpa_translation/_model/_translations_zh-CN.po,sha256=ipMbnet7ZI5mZoC8KonYKVwGmFLHFB_9KIDOoBgSNfo,26815
84
84
  sempy_labs/_bpa_translation/_model/_translations_zu-ZA.po,sha256=5v6tVKGruqneAeMoa6F3tyg_JBL8qOpqOJofWpq2W3U,31518
85
- sempy_labs/admin/__init__.py,sha256=mWwIKFM2mTEMh4A7hhr27fnNSASrZ-4p5PzpCHn1FQY,1785
86
- sempy_labs/admin/_basic_functions.py,sha256=W21ZUjIMKG2FLCbh9QliT46E3R_SPRQmNljByAIZ7LY,33949
87
- sempy_labs/admin/_domains.py,sha256=tVjUiV4bLdVKl665ouYGfzYPFsRhPwYHYy7efCncvsE,12337
85
+ sempy_labs/admin/__init__.py,sha256=h5kLtAZMzqRRBbLX-l3YGzSZhUu_tMizOHRxeWivNEU,1859
86
+ sempy_labs/admin/_basic_functions.py,sha256=Wip4Q4TTFj5SuBpDDcnBaS4Iaqu_pnxqnmMQBo2y34c,35613
87
+ sempy_labs/admin/_domains.py,sha256=5mv2SzIZCibvHwd4tgm-Lelj0zi66A2KKzQjDQgT9ms,12385
88
88
  sempy_labs/admin/_external_data_share.py,sha256=ITsPDgRDfgvZn1cjzpUWyR6lpnoOP0-gJVxjRA3Mp8w,3489
89
89
  sempy_labs/admin/_git.py,sha256=OY2F5ICKBXrB1HhlYDWdXQPnhTwSrMfWzEa2xcutClc,2181
90
90
  sempy_labs/admin/_items.py,sha256=LqjBYWL3NZCX8f0H-zzjOzy9zlBC7XR4LiknJv_JLT0,8428
91
- sempy_labs/admin/_scanner.py,sha256=GjBJUQg5qoeF492MkEaJOY_vIB7guFXv6YL7WFoPQy0,4500
91
+ sempy_labs/admin/_scanner.py,sha256=Nnhi3DNv9LA6yieDcg4F5ykepPXN4UGXzTWMMDHiCq4,4494
92
92
  sempy_labs/directlake/__init__.py,sha256=etaj-3wqe5t93mu74tGYjEOQ6gtHWUogidOygiVvlq8,2131
93
93
  sempy_labs/directlake/_directlake_schema_compare.py,sha256=ocHFU6E6HSKgcNLywGM0dx0ie9AXYwk-E7o7EYcqiN4,4422
94
94
  sempy_labs/directlake/_directlake_schema_sync.py,sha256=fhh6Xjd42HjI5x_Ejwq1N4qqnXQsKpXmyPcYl7cNG6A,4151
@@ -126,7 +126,7 @@ sempy_labs/report/_report_functions.py,sha256=nKqsVsjGrv8TUXsBXpb5ejEopAaFELc7Yz
126
126
  sempy_labs/report/_report_helper.py,sha256=pKIsca-XWaioQd97FgbEfsGPWy4X_NxSyYm22W3C23E,10461
127
127
  sempy_labs/report/_report_list_functions.py,sha256=Y0fkoo1gGoVDnihveKruNXFgPJNSiEQ5Fus8bw0nqcU,3381
128
128
  sempy_labs/report/_report_rebind.py,sha256=GbOfEb9qz4SdXVGopiWSkGMDKnneJxd7wx4_OWKZ1Js,5188
129
- sempy_labs/report/_reportwrapper.py,sha256=LMc-cZP7DbFhLn5EmOdKHFWhaBUZ7u2gY7Sx7GZxxJs,82712
129
+ sempy_labs/report/_reportwrapper.py,sha256=ZFcWuZMo9SOzvZ8aPG-DvlZq_MkXRALUJusVIHn7dxE,83049
130
130
  sempy_labs/report/_bpareporttemplate/.platform,sha256=kWRa6B_KwSYLsvVFDx372mQriQO8v7dJ_YzQV_cfD-Q,303
131
131
  sempy_labs/report/_bpareporttemplate/definition.pbir,sha256=bttyHZYKqjA8OBb_cezGlX4H82cDvGZVCl1QB3fij4E,343
132
132
  sempy_labs/report/_bpareporttemplate/.pbi/localSettings.json,sha256=kzjBlNdjbsSBBSHBwbQc298AJCr9Vp6Ex0D5PemUuT0,1578
@@ -157,9 +157,9 @@ sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visua
157
157
  sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/page.json,sha256=wBVuNc8S2NaUA0FC708w6stmR2djNZp8nAsHMqesgsc,293
158
158
  sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/visuals/ce8532a7e25020271077/visual.json,sha256=mlY6t9OlSe-Y6_QmXJpS1vggU6Y3FjISUKECL8FVSg8,931
159
159
  sempy_labs/tom/__init__.py,sha256=Qbs8leW0fjzvWwOjyWK3Hjeehu7IvpB1beASGsi28bk,121
160
- sempy_labs/tom/_model.py,sha256=ebtKiXIP96BU8cK62OAQZq2PAB2TYM-4pIoOIA73_68,172073
161
- semantic_link_labs-0.8.7.dist-info/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
162
- semantic_link_labs-0.8.7.dist-info/METADATA,sha256=LtMPb9zMWgkkwT0xKKJ2B6dzrlHSnGaWd8gwRlQJcPY,20715
163
- semantic_link_labs-0.8.7.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
164
- semantic_link_labs-0.8.7.dist-info/top_level.txt,sha256=kiQX1y42Dbein1l3Q8jMUYyRulDjdlc2tMepvtrvixQ,11
165
- semantic_link_labs-0.8.7.dist-info/RECORD,,
160
+ sempy_labs/tom/_model.py,sha256=tAuj4f_7jPqGtnxq6zAwAeBOvR_29J8cSVm2ilGxIzo,172056
161
+ semantic_link_labs-0.8.9.dist-info/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
162
+ semantic_link_labs-0.8.9.dist-info/METADATA,sha256=syErH8junhZMiSn2M04yvVgU9VTVbIIdN5LX7gUqzmQ,20910
163
+ semantic_link_labs-0.8.9.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
164
+ semantic_link_labs-0.8.9.dist-info/top_level.txt,sha256=kiQX1y42Dbein1l3Q8jMUYyRulDjdlc2tMepvtrvixQ,11
165
+ semantic_link_labs-0.8.9.dist-info/RECORD,,
sempy_labs/__init__.py CHANGED
@@ -190,6 +190,7 @@ from sempy_labs._generate_semantic_model import (
190
190
  get_semantic_model_bim,
191
191
  get_semantic_model_size,
192
192
  update_semantic_model_from_bim,
193
+ get_semantic_model_definition,
193
194
  )
194
195
  from sempy_labs._list_functions import (
195
196
  list_reports_using_semantic_model,
@@ -456,4 +457,5 @@ __all__ = [
456
457
  "create_vnet_gateway",
457
458
  "update_vnet_gateway",
458
459
  "update_on_premises_gateway",
460
+ "get_semantic_model_definition",
459
461
  ]
@@ -237,7 +237,7 @@ def list_item_connections(
237
237
  )
238
238
 
239
239
  client = fabric.FabricRestClient()
240
- response = client.post(f"/v1/workspaces/{workspace_id}/items/{item_id}/connections")
240
+ response = client.get(f"/v1/workspaces/{workspace_id}/items/{item_id}/connections")
241
241
 
242
242
  df = pd.DataFrame(
243
243
  columns=[
sempy_labs/_dax.py CHANGED
@@ -6,7 +6,7 @@ from sempy_labs._helper_functions import (
6
6
  format_dax_object_name,
7
7
  )
8
8
  from sempy_labs._model_dependencies import get_model_calc_dependencies
9
- from typing import Optional
9
+ from typing import Optional, List
10
10
  from sempy._utils._log import log
11
11
  from tqdm.auto import tqdm
12
12
 
@@ -67,8 +67,9 @@ def evaluate_dax_impersonation(
67
67
  @log
68
68
  def get_dax_query_dependencies(
69
69
  dataset: str,
70
- dax_string: str,
70
+ dax_string: str | List[str],
71
71
  put_in_memory: bool = False,
72
+ show_vertipaq_stats: bool = True,
72
73
  workspace: Optional[str] = None,
73
74
  ) -> pd.DataFrame:
74
75
  """
@@ -78,10 +79,12 @@ def get_dax_query_dependencies(
78
79
  ----------
79
80
  dataset : str
80
81
  Name of the semantic model.
81
- dax_string : str
82
- The DAX query.
82
+ dax_string : str | List[str]
83
+ The DAX query or list of DAX queries.
83
84
  put_in_memory : bool, default=False
84
85
  If True, ensures that the dependent columns are put into memory in order to give realistic Vertipaq stats (i.e. Total Size etc.).
86
+ show_vertipaq_stats : bool, default=True
87
+ If True, shows vertipaq stats (i.e. Total Size, Data Size, Dictionary Size, Hierarchy Size)
85
88
  workspace : str, default=None
86
89
  The Fabric workspace name.
87
90
  Defaults to None which resolves to the workspace of the attached lakehouse
@@ -96,67 +99,79 @@ def get_dax_query_dependencies(
96
99
  if workspace is None:
97
100
  workspace = fabric.resolve_workspace_name(workspace)
98
101
 
99
- # Escape quotes in dax
100
- dax_string = dax_string.replace('"', '""')
101
- final_query = f"""
102
- EVALUATE
103
- VAR source_query = "{dax_string}"
104
- VAR all_dependencies = SELECTCOLUMNS(
105
- INFO.CALCDEPENDENCY("QUERY", source_query),
106
- "Referenced Object Type",[REFERENCED_OBJECT_TYPE],
107
- "Referenced Table", [REFERENCED_TABLE],
108
- "Referenced Object", [REFERENCED_OBJECT]
109
- )
110
- RETURN all_dependencies
111
- """
112
- dep = fabric.evaluate_dax(
113
- dataset=dataset, workspace=workspace, dax_string=final_query
114
- )
102
+ if isinstance(dax_string, str):
103
+ dax_string = [dax_string]
115
104
 
116
- # Clean up column names and values (remove outside square brackets, underscorees in object type)
117
- dep.columns = dep.columns.map(lambda x: x[1:-1])
118
- dep["Referenced Object Type"] = (
119
- dep["Referenced Object Type"].str.replace("_", " ").str.title()
120
- )
121
- dep
122
-
123
- # Dataframe df will contain the output of all dependencies of the objects used in the query
124
- df = dep.copy()
105
+ final_df = pd.DataFrame(columns=["Object Type", "Table", "Object"])
125
106
 
126
107
  cd = get_model_calc_dependencies(dataset=dataset, workspace=workspace)
127
108
 
128
- for _, r in dep.iterrows():
129
- ot = r["Referenced Object Type"]
130
- object_name = r["Referenced Object"]
131
- table_name = r["Referenced Table"]
132
- cd_filt = cd[
133
- (cd["Object Type"] == ot)
134
- & (cd["Object Name"] == object_name)
135
- & (cd["Table Name"] == table_name)
136
- ]
137
-
138
- # Adds in the dependencies of each object used in the query (i.e. relationship etc.)
139
- if len(cd_filt) > 0:
140
- subset = cd_filt[
141
- ["Referenced Object Type", "Referenced Table", "Referenced Object"]
109
+ for dax in dax_string:
110
+ # Escape quotes in dax
111
+ dax = dax.replace('"', '""')
112
+ final_query = f"""
113
+ EVALUATE
114
+ VAR source_query = "{dax}"
115
+ VAR all_dependencies = SELECTCOLUMNS(
116
+ INFO.CALCDEPENDENCY("QUERY", source_query),
117
+ "Referenced Object Type",[REFERENCED_OBJECT_TYPE],
118
+ "Referenced Table", [REFERENCED_TABLE],
119
+ "Referenced Object", [REFERENCED_OBJECT]
120
+ )
121
+ RETURN all_dependencies
122
+ """
123
+ dep = fabric.evaluate_dax(
124
+ dataset=dataset, workspace=workspace, dax_string=final_query
125
+ )
126
+
127
+ # Clean up column names and values (remove outside square brackets, underscorees in object type)
128
+ dep.columns = dep.columns.map(lambda x: x[1:-1])
129
+ dep["Referenced Object Type"] = (
130
+ dep["Referenced Object Type"].str.replace("_", " ").str.title()
131
+ )
132
+
133
+ # Dataframe df will contain the output of all dependencies of the objects used in the query
134
+ df = dep.copy()
135
+
136
+ for _, r in dep.iterrows():
137
+ ot = r["Referenced Object Type"]
138
+ object_name = r["Referenced Object"]
139
+ table_name = r["Referenced Table"]
140
+ cd_filt = cd[
141
+ (cd["Object Type"] == ot)
142
+ & (cd["Object Name"] == object_name)
143
+ & (cd["Table Name"] == table_name)
142
144
  ]
143
- df = pd.concat([df, subset], ignore_index=True)
144
-
145
- df.columns = df.columns.map(lambda x: x.replace("Referenced ", ""))
146
- # Remove duplicates
147
- df = df.drop_duplicates().reset_index(drop=True)
148
- # Only show columns and remove the rownumber column
149
- df = df[
150
- (df["Object Type"].isin(["Column", "Calc Column"]))
151
- & (~df["Object"].str.startswith("RowNumber-"))
145
+
146
+ # Adds in the dependencies of each object used in the query (i.e. relationship etc.)
147
+ if len(cd_filt) > 0:
148
+ subset = cd_filt[
149
+ ["Referenced Object Type", "Referenced Table", "Referenced Object"]
150
+ ]
151
+ df = pd.concat([df, subset], ignore_index=True)
152
+
153
+ df.columns = df.columns.map(lambda x: x.replace("Referenced ", ""))
154
+ final_df = pd.concat([df, final_df], ignore_index=True)
155
+
156
+ final_df = final_df[
157
+ (final_df["Object Type"].isin(["Column", "Calc Column"]))
158
+ & (~final_df["Object"].str.startswith("RowNumber-"))
152
159
  ]
160
+ final_df = final_df.drop_duplicates().reset_index(drop=True)
161
+ final_df = final_df.rename(columns={"Table": "Table Name", "Object": "Column Name"})
162
+ final_df.drop(columns=["Object Type"], inplace=True)
163
+
164
+ if not show_vertipaq_stats:
165
+ return final_df
153
166
 
154
167
  # Get vertipaq stats, filter to just the objects in the df dataframe
155
- df["Full Object"] = format_dax_object_name(df["Table"], df["Object"])
168
+ final_df["Full Object"] = format_dax_object_name(
169
+ final_df["Table Name"], final_df["Column Name"]
170
+ )
156
171
  dfC = fabric.list_columns(dataset=dataset, workspace=workspace, extended=True)
157
172
  dfC["Full Object"] = format_dax_object_name(dfC["Table Name"], dfC["Column Name"])
158
173
 
159
- dfC_filtered = dfC[dfC["Full Object"].isin(df["Full Object"].values)][
174
+ dfC_filtered = dfC[dfC["Full Object"].isin(final_df["Full Object"].values)][
160
175
  [
161
176
  "Table Name",
162
177
  "Column Name",
@@ -2,7 +2,7 @@ import sempy.fabric as fabric
2
2
  import pandas as pd
3
3
  import json
4
4
  import os
5
- from typing import Optional
5
+ from typing import Optional, List
6
6
  from sempy_labs._helper_functions import (
7
7
  resolve_lakehouse_name,
8
8
  resolve_workspace_name_and_id,
@@ -329,8 +329,6 @@ def get_semantic_model_bim(
329
329
  """
330
330
  Extracts the Model.bim file for a given semantic model.
331
331
 
332
- This is a wrapper function for the following API: `Items - Get Semantic Model Definition <https://learn.microsoft.com/rest/api/fabric/semanticmodel/items/get-semantic-model-definition>`_.
333
-
334
332
  Parameters
335
333
  ----------
336
334
  dataset : str
@@ -352,20 +350,9 @@ def get_semantic_model_bim(
352
350
  The Model.bim file for the semantic model.
353
351
  """
354
352
 
355
- (workspace, workspace_id) = resolve_workspace_name_and_id(workspace)
356
-
357
- fmt = "TMSL"
358
- client = fabric.FabricRestClient()
359
- dataset_id = resolve_dataset_id(dataset=dataset, workspace=workspace)
360
- response = client.post(
361
- f"/v1/workspaces/{workspace_id}/semanticModels/{dataset_id}/getDefinition?format={fmt}",
353
+ bimJson = get_semantic_model_definition(
354
+ dataset=dataset, workspace=workspace, format="TMSL", return_dataframe=False
362
355
  )
363
- result = lro(client, response).json()
364
- df_items = pd.json_normalize(result["definition"]["parts"])
365
- df_items_filt = df_items[df_items["path"] == "model.bim"]
366
- payload = df_items_filt["payload"].iloc[0]
367
- bimFile = _decode_b64(payload)
368
- bimJson = json.loads(bimFile)
369
356
 
370
357
  if save_to_file_name is not None:
371
358
  if not lakehouse_attached():
@@ -384,12 +371,80 @@ def get_semantic_model_bim(
384
371
  with open(filePath, "w") as json_file:
385
372
  json.dump(bimJson, json_file, indent=4)
386
373
  print(
387
- f"{icons.green_dot} The .bim file for the '{dataset}' semantic model has been saved to the '{lakehouse}' in this location: '{filePath}'.\n\n"
374
+ f"{icons.green_dot} The {fileExt} file for the '{dataset}' semantic model has been saved to the '{lakehouse}' in this location: '{filePath}'.\n\n"
388
375
  )
389
376
 
390
377
  return bimJson
391
378
 
392
379
 
380
+ def get_semantic_model_definition(
381
+ dataset: str,
382
+ format: str = "TMSL",
383
+ workspace: Optional[str] = None,
384
+ return_dataframe: bool = True,
385
+ ) -> pd.DataFrame | dict | List:
386
+ """
387
+ Extracts the semantic model definition.
388
+
389
+ This is a wrapper function for the following API: `Items - Get Semantic Model Definition <https://learn.microsoft.com/rest/api/fabric/semanticmodel/items/get-semantic-model-definition>`_.
390
+
391
+ Parameters
392
+ ----------
393
+ dataset : str
394
+ Name of the semantic model.
395
+ format : str, default="TMSL"
396
+ The output format. Valid options are "TMSL" or "TMDL". "TMSL" returns the .bim file whereas "TMDL" returns the collection of TMDL files. Can also enter 'bim' for the TMSL version.
397
+ workspace : str, default=None
398
+ The Fabric workspace name in which the semantic model resides.
399
+ Defaults to None which resolves to the workspace of the attached lakehouse
400
+ or if no lakehouse attached, resolves to the workspace of the notebook.
401
+ return_dataframe : bool, default=True
402
+ If True, returns a dataframe.
403
+ If False, returns the .bim file for TMSL format. Returns a list of the TMDL files (decoded) for TMDL format.
404
+
405
+ Returns
406
+ -------
407
+ pandas.DataFrame | dict | List
408
+ A pandas dataframe with the semantic model definition or the file or files comprising the semantic model definition.
409
+ """
410
+
411
+ valid_formats = ["TMSL", "TMDL"]
412
+
413
+ format = format.upper()
414
+ if format == "BIM":
415
+ format = "TMSL"
416
+ if format not in valid_formats:
417
+ raise ValueError(
418
+ f"{icons.red_dot} Invalid format. Valid options: {valid_formats}."
419
+ )
420
+
421
+ (workspace, workspace_id) = resolve_workspace_name_and_id(workspace)
422
+
423
+ client = fabric.FabricRestClient()
424
+ dataset_id = resolve_dataset_id(dataset=dataset, workspace=workspace)
425
+ response = client.post(
426
+ f"/v1/workspaces/{workspace_id}/semanticModels/{dataset_id}/getDefinition?format={format}",
427
+ )
428
+ result = lro(client, response).json()
429
+
430
+ files = result["definition"]["parts"]
431
+
432
+ if return_dataframe:
433
+ return pd.json_normalize(files)
434
+ elif format == "TMSL":
435
+ payload = next(
436
+ (part["payload"] for part in files if part["path"] == "model.bim"), None
437
+ )
438
+ return json.loads(_decode_b64(payload))
439
+ else:
440
+ decoded_parts = [
441
+ {"file_name": part["path"], "content": _decode_b64(part["payload"])}
442
+ for part in files
443
+ ]
444
+
445
+ return decoded_parts
446
+
447
+
393
448
  def get_semantic_model_size(dataset: str, workspace: Optional[str] = None):
394
449
 
395
450
  workspace = fabric.resolve_workspace_name(workspace)
@@ -82,7 +82,7 @@ def run_model_bpa_bulk(
82
82
  if isinstance(workspace, str):
83
83
  workspace = [workspace]
84
84
 
85
- dfW = fabric.list_workspaces()
85
+ dfW = fabric.list_workspaces("type ne 'AdminInsights'")
86
86
  if workspace is None:
87
87
  dfW_filt = dfW.copy()
88
88
  else:
@@ -150,7 +150,7 @@ def run_model_bpa_bulk(
150
150
 
151
151
  if df.empty:
152
152
  df = bpa_df
153
- if not bpa_df.empty:
153
+ elif not bpa_df.empty:
154
154
  df = pd.concat([df, bpa_df], ignore_index=True)
155
155
  print(
156
156
  f"{icons.green_dot} Collected Model BPA stats for the '{dataset_name}' semantic model within the '{wksp}' workspace."
@@ -198,7 +198,11 @@ def get_model_calc_dependencies(
198
198
  incomplete_rows = df[df["Done"] == False]
199
199
  for _, row in incomplete_rows.iterrows():
200
200
  referenced_full_name = row["Referenced Full Object Name"]
201
- dep_filt = dep[dep["Full Object Name"] == referenced_full_name]
201
+ referenced_object_type = row["Referenced Object Type"]
202
+ dep_filt = dep[
203
+ (dep["Full Object Name"] == referenced_full_name)
204
+ & (dep["Object Type"] == referenced_object_type)
205
+ ]
202
206
  # Expand dependencies and update 'Done' status as needed
203
207
  new_rows = []
204
208
  for _, dependency in dep_filt.iterrows():
@@ -40,6 +40,8 @@ def translate_semantic_model(
40
40
  from pyspark.sql import SparkSession
41
41
  from sempy_labs.tom import connect_semantic_model
42
42
 
43
+ icons.sll_tags.append("TranslateSemanticModel")
44
+
43
45
  def _clean_text(text, exclude_chars):
44
46
  if exclude_chars:
45
47
  for char in exclude_chars:
@@ -55,7 +57,7 @@ def translate_semantic_model(
55
57
  columns=["Object Type", "Name", "Description", "Display Folder"]
56
58
  )
57
59
 
58
- icons.sll_tags.append("TranslateSemanticModel")
60
+ final_df = pd.DataFrame(columns=["Value", "Translation"])
59
61
 
60
62
  with connect_semantic_model(
61
63
  dataset=dataset, readonly=False, workspace=workspace
@@ -65,9 +67,9 @@ def translate_semantic_model(
65
67
  oName = _clean_text(o.Name, exclude_characters)
66
68
  oDescription = _clean_text(o.Description, exclude_characters)
67
69
  new_data = {
68
- "Object Type": "Table",
69
70
  "Name": o.Name,
70
71
  "TName": oName,
72
+ "Object Type": "Table",
71
73
  "Description": o.Description,
72
74
  "TDescription": oDescription,
73
75
  "Display Folder": None,
@@ -81,9 +83,9 @@ def translate_semantic_model(
81
83
  oDescription = _clean_text(o.Description, exclude_characters)
82
84
  oDisplayFolder = _clean_text(o.DisplayFolder, exclude_characters)
83
85
  new_data = {
84
- "Object Type": "Column",
85
86
  "Name": o.Name,
86
87
  "TName": oName,
88
+ "Object Type": "Column",
87
89
  "Description": o.Description,
88
90
  "TDescription": oDescription,
89
91
  "Display Folder": o.DisplayFolder,
@@ -97,9 +99,9 @@ def translate_semantic_model(
97
99
  oDescription = _clean_text(o.Description, exclude_characters)
98
100
  oDisplayFolder = _clean_text(o.DisplayFolder, exclude_characters)
99
101
  new_data = {
100
- "Object Type": "Measure",
101
102
  "Name": o.Name,
102
103
  "TName": oName,
104
+ "Object Type": "Measure",
103
105
  "Description": o.Description,
104
106
  "TDescription": oDescription,
105
107
  "Display Folder": o.DisplayFolder,
@@ -113,9 +115,9 @@ def translate_semantic_model(
113
115
  oDescription = _clean_text(o.Description, exclude_characters)
114
116
  oDisplayFolder = _clean_text(o.DisplayFolder, exclude_characters)
115
117
  new_data = {
116
- "Object Type": "Hierarchy",
117
118
  "Name": o.Name,
118
119
  "TName": oName,
120
+ "Object Type": "Hierarchy",
119
121
  "Description": o.Description,
120
122
  "TDescription": oDescription,
121
123
  "Display Folder": o.DisplayFolder,
@@ -128,9 +130,9 @@ def translate_semantic_model(
128
130
  oName = _clean_text(o.Name, exclude_characters)
129
131
  oDescription = _clean_text(o.Description, exclude_characters)
130
132
  new_data = {
131
- "Object Type": "Level",
132
133
  "Name": o.Name,
133
134
  "TName": oName,
135
+ "Object Type": "Level",
134
136
  "Description": o.Description,
135
137
  "TDescription": oDescription,
136
138
  "Display Folder": None,
@@ -163,152 +165,82 @@ def translate_semantic_model(
163
165
  )
164
166
 
165
167
  df_panda = transDF.toPandas()
168
+ df_panda = df_panda[~df_panda[clm].isin([None, ""])][[clm, "translation"]]
169
+
170
+ df_panda = df_panda.rename(columns={clm: "value"})
171
+ final_df = pd.concat([final_df, df_panda], ignore_index=True)
166
172
 
167
- def set_translation_if_exists(
168
- obj, obj_type, property_name, property_value, df, lang, index
169
- ):
170
- if property_name in df.columns and len(property_value) > 0:
171
- df_filt = df[
172
- (df["Object Type"] == obj_type)
173
- & (df[property_name] == property_value)
174
- ]
175
- if len(df_filt) == 1:
176
- translation = df_filt["translation"].str[index].iloc[0]
177
- tom.set_translation(
178
- object=obj,
179
- language=lang,
180
- property=property_name,
181
- value=translation,
182
- )
173
+ def set_translation_if_exists(object, language, property, index):
183
174
 
184
- for lang in languages:
185
- i = languages.index(lang)
186
- tom.add_translation(language=lang)
187
- print(
188
- f"{icons.in_progress} Translating {clm.lower()}s into the '{lang}' language..."
175
+ if property == "Name":
176
+ trans = object.Name
177
+ elif property == "Description":
178
+ trans = object.Description
179
+ elif property == "Display Folder":
180
+ trans = object.DisplayFolder
181
+
182
+ df_filt = final_df[final_df["value"] == trans]
183
+ if not df_filt.empty:
184
+ translation_value = df_filt["translation"].str[index].iloc[0]
185
+ tom.set_translation(
186
+ object=object,
187
+ language=language,
188
+ property=property,
189
+ value=translation_value,
190
+ )
191
+
192
+ for language in languages:
193
+ index = languages.index(language)
194
+ tom.add_translation(language=language)
195
+ print(
196
+ f"{icons.in_progress} Translating {clm.lower()}s into the '{language}' language..."
197
+ )
198
+
199
+ for t in tom.model.Tables:
200
+ set_translation_if_exists(
201
+ object=t, language=language, property="Name", index=index
202
+ )
203
+ set_translation_if_exists(
204
+ object=t, language=language, property="Description", index=index
205
+ )
206
+ for c in tom.all_columns():
207
+ set_translation_if_exists(
208
+ object=c, language=language, property="Name", index=index
209
+ )
210
+ set_translation_if_exists(
211
+ object=c, language=language, property="Description", index=index
212
+ )
213
+ set_translation_if_exists(
214
+ object=c, language=language, property="Display Folder", index=index
215
+ )
216
+ for c in tom.all_measures():
217
+ set_translation_if_exists(
218
+ object=c, language=language, property="Name", index=index
219
+ )
220
+ set_translation_if_exists(
221
+ object=c, language=language, property="Description", index=index
222
+ )
223
+ set_translation_if_exists(
224
+ object=c, language=language, property="Display Folder", index=index
225
+ )
226
+ for c in tom.all_hierarchies():
227
+ set_translation_if_exists(
228
+ object=c, language=language, property="Name", index=index
229
+ )
230
+ set_translation_if_exists(
231
+ object=c, language=language, property="Description", index=index
232
+ )
233
+ set_translation_if_exists(
234
+ object=c, language=language, property="Display Folder", index=index
235
+ )
236
+ for c in tom.all_levels():
237
+ set_translation_if_exists(
238
+ object=c, language=language, property="Name", index=index
239
+ )
240
+ set_translation_if_exists(
241
+ object=c, language=language, property="Description", index=index
189
242
  )
190
243
 
191
- for t in tom.model.Tables:
192
- if t.IsHidden is False:
193
- if clm == "Name":
194
- set_translation_if_exists(
195
- t, "Table", "Name", t.Name, df_panda, lang, i
196
- )
197
- elif clm == "Description":
198
- set_translation_if_exists(
199
- t,
200
- "Table",
201
- "Description",
202
- t.Description,
203
- df_panda,
204
- lang,
205
- i,
206
- )
207
- for c in t.Columns:
208
- if c.IsHidden is False:
209
- if clm == "Name":
210
- set_translation_if_exists(
211
- c, "Column", "Name", c.Name, df_panda, lang, i
212
- )
213
- elif clm == "Description":
214
- set_translation_if_exists(
215
- c,
216
- "Column",
217
- "Description",
218
- c.Description,
219
- df_panda,
220
- lang,
221
- i,
222
- )
223
- elif clm == "Display Folder":
224
- set_translation_if_exists(
225
- c,
226
- "Column",
227
- "Display Folder",
228
- c.DisplayFolder,
229
- df_panda,
230
- lang,
231
- i,
232
- )
233
- for h in t.Hierarchies:
234
- if h.IsHidden is False:
235
- if clm == "Name":
236
- set_translation_if_exists(
237
- h,
238
- "Hierarchy",
239
- "Name",
240
- h.Name,
241
- df_panda,
242
- lang,
243
- i,
244
- )
245
- elif clm == "Description":
246
- set_translation_if_exists(
247
- h,
248
- "Hierarchy",
249
- "Description",
250
- h.Description,
251
- df_panda,
252
- lang,
253
- i,
254
- )
255
- elif clm == "Display Folder":
256
- set_translation_if_exists(
257
- h,
258
- "Hierarchy",
259
- "Display Folder",
260
- h.DisplayFolder,
261
- df_panda,
262
- lang,
263
- i,
264
- )
265
- for lev in h.Levels:
266
- if clm == "Name":
267
- set_translation_if_exists(
268
- lev,
269
- "Level",
270
- "Name",
271
- lev.Name,
272
- df_panda,
273
- lang,
274
- i,
275
- )
276
- elif clm == "Description":
277
- set_translation_if_exists(
278
- lev,
279
- "Level",
280
- "Description",
281
- lev.Description,
282
- df_panda,
283
- lang,
284
- i,
285
- )
286
- for ms in t.Measures:
287
- if ms.IsHidden is False:
288
- if clm == "Name":
289
- set_translation_if_exists(
290
- ms, "Measure", "Name", ms.Name, df_panda, lang, i
291
- )
292
- elif clm == "Description":
293
- set_translation_if_exists(
294
- ms,
295
- "Measure",
296
- "Description",
297
- ms.Description,
298
- df_panda,
299
- lang,
300
- i,
301
- )
302
- elif clm == "Display Folder":
303
- set_translation_if_exists(
304
- ms,
305
- "Measure",
306
- "Display Folder",
307
- ms.DisplayFolder,
308
- df_panda,
309
- lang,
310
- i,
311
- )
312
244
  result = pd.DataFrame(
313
245
  columns=[
314
246
  "Language",
sempy_labs/_workspaces.py CHANGED
@@ -153,7 +153,7 @@ def add_user_to_workspace(
153
153
  Parameters
154
154
  ----------
155
155
  email_address : str
156
- The email address of the user.
156
+ The email address of the user. Also accepts the user identifier.
157
157
  role_name : str
158
158
  The `role <https://learn.microsoft.com/rest/api/power-bi/groups/add-group-user#groupuseraccessright>`_ of the user within the workspace.
159
159
  principal_type : str, default='User'
@@ -11,6 +11,7 @@ from sempy_labs.admin._basic_functions import (
11
11
  list_capacities_delegated_tenant_settings,
12
12
  list_access_entities,
13
13
  list_activity_events,
14
+ get_capacity_assignment_status,
14
15
  )
15
16
  from sempy_labs.admin._domains import (
16
17
  list_domains,
@@ -64,4 +65,5 @@ __all__ = [
64
65
  "list_modified_workspaces",
65
66
  "list_git_connections",
66
67
  "list_reports",
68
+ "get_capacity_assignment_status",
67
69
  ]
@@ -11,7 +11,6 @@ from sempy_labs._helper_functions import (
11
11
  import numpy as np
12
12
  import pandas as pd
13
13
  from dateutil.parser import parse as dtparser
14
- import urllib.parse
15
14
 
16
15
 
17
16
  def list_workspaces(
@@ -126,7 +125,7 @@ def list_capacities(
126
125
  capacity: Optional[str | UUID] = None,
127
126
  ) -> pd.DataFrame:
128
127
  """
129
- Shows the a list of capacities and their properties. This function is the admin version.
128
+ Shows the a list of capacities and their properties.
130
129
 
131
130
  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>`_.
132
131
 
@@ -138,7 +137,7 @@ def list_capacities(
138
137
  Returns
139
138
  -------
140
139
  pandas.DataFrame
141
- A pandas dataframe showing the capacities and their properties
140
+ A pandas dataframe showing the capacities and their properties.
142
141
  """
143
142
  client = fabric.FabricRestClient()
144
143
 
@@ -214,6 +213,7 @@ def assign_workspaces_to_capacity(
214
213
  if source_capacity is None:
215
214
  dfW = list_workspaces()
216
215
  else:
216
+ source_capacity_id = _resolve_capacity_name_and_id(source_capacity)[1]
217
217
  dfW = list_workspaces(capacity=source_capacity_id)
218
218
 
219
219
  # Extract names and IDs that are mapped in dfW
@@ -230,10 +230,10 @@ def assign_workspaces_to_capacity(
230
230
  if item not in workspaces_names and item not in workspaces_ids
231
231
  ]
232
232
 
233
- if len(workspace) != len(workspaces):
234
- raise ValueError(
235
- f"{icons.red_dot} The following workspaces are invalid: {unmapped_workspaces}."
236
- )
233
+ if len(workspace) != len(workspaces):
234
+ raise ValueError(
235
+ f"{icons.red_dot} The following workspaces are invalid or not found in source capacity: {unmapped_workspaces}."
236
+ )
237
237
 
238
238
  target_capacity_id = _resolve_capacity_name_and_id(target_capacity)[1]
239
239
 
@@ -780,35 +780,18 @@ def list_activity_events(
780
780
  ]
781
781
  )
782
782
 
783
- resposeJson = {"activityEventEntities": []}
784
-
785
- tic = "%27"
786
- space = "%20"
783
+ response_json = {"activityEventEntities": []}
787
784
  client = fabric.PowerBIRestClient()
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')}'"
785
+ url = f"/v1.0/myorg/admin/activityevents?startDateTime='{start_time}'&endDateTime='{end_time}'"
797
786
 
798
787
  conditions = []
799
-
800
788
  if activity_filter is not None:
801
- conditions.append(f"Activity{space}eq{space}{tic}{activity_filter}{tic}")
802
-
789
+ conditions.append(f"Activity eq '{activity_filter}'")
803
790
  if user_id_filter is not None:
804
- conditions.append(f"UserId{space}eq{space}{tic}{user_id_filter}{tic}")
791
+ conditions.append(f"UserId eq '{user_id_filter}'")
805
792
 
806
793
  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)
794
+ url += f"&$filter={f' and '.join(conditions)}"
812
795
 
813
796
  response = client.get(url)
814
797
 
@@ -848,15 +831,15 @@ def list_activity_events(
848
831
  ignore_index=True,
849
832
  )
850
833
  else:
851
- resposeJson["activityEventEntities"].extend(r.get("activityEventEntities"))
834
+ response_json["activityEventEntities"].extend(
835
+ r.get("activityEventEntities")
836
+ )
852
837
 
853
838
  if return_dataframe:
854
839
  df["Creation Time"] = pd.to_datetime(df["Creation Time"])
855
- activity_events = df
840
+ return df
856
841
  else:
857
- activity_events = resposeJson
858
-
859
- return activity_events
842
+ return response_json
860
843
 
861
844
 
862
845
  def _resolve_capacity_name_and_id(
@@ -917,12 +900,18 @@ def _resolve_workspace_name_and_id(
917
900
  workspace: str | UUID,
918
901
  ) -> Tuple[str, UUID]:
919
902
 
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.")
903
+ if workspace is None:
904
+ workspace_id = fabric.get_workspace_id()
905
+ workspace_name = fabric.resolve_workspace_name(workspace_id)
906
+ else:
907
+ dfW = list_workspaces(workspace=workspace)
908
+ if not dfW.empty:
909
+ workspace_name = dfW["Name"].iloc[0]
910
+ workspace_id = dfW["Id"].iloc[0]
911
+ else:
912
+ raise ValueError(
913
+ f"{icons.red_dot} The '{workspace}' workspace was not found."
914
+ )
926
915
 
927
916
  return workspace_name, workspace_id
928
917
 
@@ -1004,3 +993,60 @@ def list_reports(
1004
993
  df["Modified Date"] = pd.to_datetime(df["Modified Date"], errors="coerce")
1005
994
 
1006
995
  return df
996
+
997
+
998
+ def get_capacity_assignment_status(workspace: Optional[str | UUID] = None):
999
+ """
1000
+ Gets the status of the assignment-to-capacity operation for the specified workspace.
1001
+
1002
+ This is a wrapper function for the following API: `Capacities - Groups CapacityAssignmentStatus <https://learn.microsoft.com/rest/api/power-bi/capacities/groups-capacity-assignment-status>`_.
1003
+
1004
+ Parameters
1005
+ ----------
1006
+ workspace : str | UUID, default=None
1007
+ The Fabric workspace name or id.
1008
+ Defaults to None which resolves to the workspace of the attached lakehouse
1009
+ or if no lakehouse attached, resolves to the workspace of the notebook.
1010
+
1011
+ Returns
1012
+ -------
1013
+ pandas.DataFrame
1014
+ A pandas dataframe showing the status of the assignment-to-capacity operation for the specified workspace.
1015
+ """
1016
+
1017
+ (workspace_name, workspace_id) = _resolve_workspace_name_and_id(workspace)
1018
+
1019
+ df = pd.DataFrame(
1020
+ columns=[
1021
+ "Status",
1022
+ "Activity Id",
1023
+ "Start Time",
1024
+ "End Time",
1025
+ "Capacity Id",
1026
+ "Capacity Name",
1027
+ ]
1028
+ )
1029
+
1030
+ client = fabric.FabricRestClient()
1031
+ response = client.get(f"/v1.0/myorg/groups/{workspace_id}/CapacityAssignmentStatus")
1032
+
1033
+ if response.status_code != 200:
1034
+ raise FabricHTTPException(response)
1035
+
1036
+ v = response.json()
1037
+ capacity_id = v.get("capacityId")
1038
+
1039
+ (capacity_name, capacity_id) = _resolve_capacity_name_and_id(capacity=capacity_id)
1040
+
1041
+ new_data = {
1042
+ "Status": v.get("status"),
1043
+ "Activity Id": v.get("activityId"),
1044
+ "Start Time": v.get("startTime"),
1045
+ "End Time": v.get("endTime"),
1046
+ "Capacity Id": capacity_id,
1047
+ "Capacity Name": capacity_name,
1048
+ }
1049
+
1050
+ df = pd.concat([df, pd.DataFrame([new_data])], ignore_index=True)
1051
+
1052
+ return df
@@ -5,6 +5,7 @@ from sempy_labs._helper_functions import lro
5
5
  from sempy.fabric.exceptions import FabricHTTPException
6
6
  import pandas as pd
7
7
  from uuid import UUID
8
+ from sempy_labs.admin._basic_functions import list_workspaces
8
9
 
9
10
 
10
11
  def resolve_domain_id(domain_name: str) -> UUID:
@@ -302,7 +303,7 @@ def assign_domain_workspaces(domain_name: str, workspace_names: str | List[str])
302
303
  if isinstance(workspace_names, str):
303
304
  workspace_names = [workspace_names]
304
305
 
305
- dfW = fabric.list_workspaces()
306
+ dfW = list_workspaces()
306
307
 
307
308
  # Check for invalid capacities
308
309
  invalid_workspaces = [
@@ -379,7 +380,7 @@ def unassign_domain_workspaces(domain_name: str, workspace_names: str | List[str
379
380
  if isinstance(workspace_names, str):
380
381
  workspace_names = [workspace_names]
381
382
 
382
- dfW = fabric.list_workspaces()
383
+ dfW = list_workspaces()
383
384
 
384
385
  # Check for invalid capacities
385
386
  invalid_workspaces = [
@@ -40,7 +40,7 @@ def scan_workspaces(
40
40
 
41
41
  Returns
42
42
  -------
43
- dictionary
43
+ dict
44
44
  A json object with the scan result.
45
45
  """
46
46
  scan_result = {
@@ -970,6 +970,7 @@ class ReportWrapper:
970
970
  "Sparkline",
971
971
  "Visual Calc",
972
972
  "Format",
973
+ "Object Display Name",
973
974
  ]
974
975
  )
975
976
 
@@ -1038,23 +1039,26 @@ class ReportWrapper:
1038
1039
 
1039
1040
  entity_property_pairs = find_entity_property_pairs(visual_json)
1040
1041
  query_state = (
1041
- visual_json.get("visual", {})
1042
- .get("query", {})
1043
- .get("queryState", {})
1044
- .get("Values", {})
1042
+ visual_json.get("visual", {}).get("query", {}).get("queryState", {})
1045
1043
  )
1044
+
1046
1045
  format_mapping = {}
1047
- for p in query_state.get("projections", []):
1048
- query_ref = p.get("queryRef")
1049
- fmt = p.get("format")
1050
- if fmt is not None:
1051
- format_mapping[query_ref] = fmt
1046
+ obj_display_mapping = {}
1047
+ for a, p in query_state.items():
1048
+ for proj in p.get("projections", []):
1049
+ query_ref = proj.get("queryRef")
1050
+ fmt = proj.get("format")
1051
+ obj_display_name = proj.get("displayName")
1052
+ if fmt is not None:
1053
+ format_mapping[query_ref] = fmt
1054
+ obj_display_mapping[query_ref] = obj_display_name
1052
1055
 
1053
1056
  for object_name, properties in entity_property_pairs.items():
1054
1057
  table_name = properties[0]
1055
1058
  obj_full = f"{table_name}.{object_name}"
1056
1059
  is_agg = properties[2]
1057
1060
  format_value = format_mapping.get(obj_full)
1061
+ obj_display = obj_display_mapping.get(obj_full)
1058
1062
 
1059
1063
  if is_agg:
1060
1064
  for k, v in format_mapping.items():
@@ -1071,6 +1075,7 @@ class ReportWrapper:
1071
1075
  "Sparkline": properties[4],
1072
1076
  "Visual Calc": properties[3],
1073
1077
  "Format": format_value,
1078
+ "Object Display Name": obj_display,
1074
1079
  }
1075
1080
 
1076
1081
  df = pd.concat(
sempy_labs/tom/_model.py CHANGED
@@ -1634,7 +1634,7 @@ class TOMWrapper:
1634
1634
  prop = mapping.get(property)
1635
1635
  if prop is None:
1636
1636
  raise ValueError(
1637
- f"{icons.red_dot} Invalid property value. Please choose from the following: ['Name', 'Description', Display Folder]."
1637
+ f"{icons.red_dot} Invalid property value. Please choose from the following: {list(mapping.keys())}."
1638
1638
  )
1639
1639
 
1640
1640
  if not any(c.Name == language for c in self.model.Cultures):