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.
- {semantic_link_labs-0.8.7.dist-info → semantic_link_labs-0.8.9.dist-info}/METADATA +4 -2
- {semantic_link_labs-0.8.7.dist-info → semantic_link_labs-0.8.9.dist-info}/RECORD +19 -19
- sempy_labs/__init__.py +2 -0
- sempy_labs/_connections.py +1 -1
- sempy_labs/_dax.py +69 -54
- sempy_labs/_generate_semantic_model.py +72 -17
- sempy_labs/_model_bpa_bulk.py +2 -2
- sempy_labs/_model_dependencies.py +5 -1
- sempy_labs/_translations.py +80 -148
- sempy_labs/_workspaces.py +1 -1
- sempy_labs/admin/__init__.py +2 -0
- sempy_labs/admin/_basic_functions.py +86 -40
- sempy_labs/admin/_domains.py +3 -2
- sempy_labs/admin/_scanner.py +1 -1
- sempy_labs/report/_reportwrapper.py +14 -9
- sempy_labs/tom/_model.py +1 -1
- {semantic_link_labs-0.8.7.dist-info → semantic_link_labs-0.8.9.dist-info}/LICENSE +0 -0
- {semantic_link_labs-0.8.7.dist-info → semantic_link_labs-0.8.9.dist-info}/WHEEL +0 -0
- {semantic_link_labs-0.8.7.dist-info → semantic_link_labs-0.8.9.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: semantic-link-labs
|
|
3
|
-
Version: 0.8.
|
|
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
|
[](https://badge.fury.io/py/semantic-link-labs)
|
|
30
|
-
[](https://readthedocs.org/projects/semantic-link-labs/)
|
|
31
31
|
[](https://github.com/psf/black)
|
|
32
32
|
[](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=
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
86
|
-
sempy_labs/admin/_basic_functions.py,sha256=
|
|
87
|
-
sempy_labs/admin/_domains.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
161
|
-
semantic_link_labs-0.8.
|
|
162
|
-
semantic_link_labs-0.8.
|
|
163
|
-
semantic_link_labs-0.8.
|
|
164
|
-
semantic_link_labs-0.8.
|
|
165
|
-
semantic_link_labs-0.8.
|
|
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
|
]
|
sempy_labs/_connections.py
CHANGED
|
@@ -237,7 +237,7 @@ def list_item_connections(
|
|
|
237
237
|
)
|
|
238
238
|
|
|
239
239
|
client = fabric.FabricRestClient()
|
|
240
|
-
response = client.
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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)
|
sempy_labs/_model_bpa_bulk.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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():
|
sempy_labs/_translations.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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'
|
sempy_labs/admin/__init__.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
|
802
|
-
|
|
789
|
+
conditions.append(f"Activity eq '{activity_filter}'")
|
|
803
790
|
if user_id_filter is not None:
|
|
804
|
-
conditions.append(f"UserId
|
|
791
|
+
conditions.append(f"UserId eq '{user_id_filter}'")
|
|
805
792
|
|
|
806
793
|
if conditions:
|
|
807
|
-
|
|
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
|
-
|
|
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
|
-
|
|
840
|
+
return df
|
|
856
841
|
else:
|
|
857
|
-
|
|
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
|
-
|
|
921
|
-
|
|
922
|
-
workspace_name =
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
sempy_labs/admin/_domains.py
CHANGED
|
@@ -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 =
|
|
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 =
|
|
383
|
+
dfW = list_workspaces()
|
|
383
384
|
|
|
384
385
|
# Check for invalid capacities
|
|
385
386
|
invalid_workspaces = [
|
sempy_labs/admin/_scanner.py
CHANGED
|
@@ -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
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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:
|
|
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):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|