semantic-link-labs 0.12.8__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.
- semantic_link_labs-0.12.8.dist-info/METADATA +354 -0
- semantic_link_labs-0.12.8.dist-info/RECORD +243 -0
- semantic_link_labs-0.12.8.dist-info/WHEEL +5 -0
- semantic_link_labs-0.12.8.dist-info/licenses/LICENSE +21 -0
- semantic_link_labs-0.12.8.dist-info/top_level.txt +1 -0
- sempy_labs/__init__.py +606 -0
- sempy_labs/_a_lib_info.py +2 -0
- sempy_labs/_ai.py +437 -0
- sempy_labs/_authentication.py +264 -0
- sempy_labs/_bpa_translation/_model/_translations_am-ET.po +869 -0
- sempy_labs/_bpa_translation/_model/_translations_ar-AE.po +908 -0
- sempy_labs/_bpa_translation/_model/_translations_bg-BG.po +968 -0
- sempy_labs/_bpa_translation/_model/_translations_ca-ES.po +963 -0
- sempy_labs/_bpa_translation/_model/_translations_cs-CZ.po +943 -0
- sempy_labs/_bpa_translation/_model/_translations_da-DK.po +945 -0
- sempy_labs/_bpa_translation/_model/_translations_de-DE.po +988 -0
- sempy_labs/_bpa_translation/_model/_translations_el-GR.po +993 -0
- sempy_labs/_bpa_translation/_model/_translations_es-ES.po +971 -0
- sempy_labs/_bpa_translation/_model/_translations_fa-IR.po +933 -0
- sempy_labs/_bpa_translation/_model/_translations_fi-FI.po +942 -0
- sempy_labs/_bpa_translation/_model/_translations_fr-FR.po +994 -0
- sempy_labs/_bpa_translation/_model/_translations_ga-IE.po +967 -0
- sempy_labs/_bpa_translation/_model/_translations_he-IL.po +902 -0
- sempy_labs/_bpa_translation/_model/_translations_hi-IN.po +944 -0
- sempy_labs/_bpa_translation/_model/_translations_hu-HU.po +963 -0
- sempy_labs/_bpa_translation/_model/_translations_id-ID.po +946 -0
- sempy_labs/_bpa_translation/_model/_translations_is-IS.po +939 -0
- sempy_labs/_bpa_translation/_model/_translations_it-IT.po +986 -0
- sempy_labs/_bpa_translation/_model/_translations_ja-JP.po +846 -0
- sempy_labs/_bpa_translation/_model/_translations_ko-KR.po +839 -0
- sempy_labs/_bpa_translation/_model/_translations_mt-MT.po +967 -0
- sempy_labs/_bpa_translation/_model/_translations_nl-NL.po +978 -0
- sempy_labs/_bpa_translation/_model/_translations_pl-PL.po +962 -0
- sempy_labs/_bpa_translation/_model/_translations_pt-BR.po +962 -0
- sempy_labs/_bpa_translation/_model/_translations_pt-PT.po +957 -0
- sempy_labs/_bpa_translation/_model/_translations_ro-RO.po +968 -0
- sempy_labs/_bpa_translation/_model/_translations_ru-RU.po +964 -0
- sempy_labs/_bpa_translation/_model/_translations_sk-SK.po +952 -0
- sempy_labs/_bpa_translation/_model/_translations_sl-SL.po +950 -0
- sempy_labs/_bpa_translation/_model/_translations_sv-SE.po +942 -0
- sempy_labs/_bpa_translation/_model/_translations_ta-IN.po +976 -0
- sempy_labs/_bpa_translation/_model/_translations_te-IN.po +947 -0
- sempy_labs/_bpa_translation/_model/_translations_th-TH.po +924 -0
- sempy_labs/_bpa_translation/_model/_translations_tr-TR.po +953 -0
- sempy_labs/_bpa_translation/_model/_translations_uk-UA.po +961 -0
- sempy_labs/_bpa_translation/_model/_translations_zh-CN.po +804 -0
- sempy_labs/_bpa_translation/_model/_translations_zu-ZA.po +969 -0
- sempy_labs/_capacities.py +1198 -0
- sempy_labs/_capacity_migration.py +660 -0
- sempy_labs/_clear_cache.py +351 -0
- sempy_labs/_connections.py +610 -0
- sempy_labs/_dashboards.py +69 -0
- sempy_labs/_data_access_security.py +98 -0
- sempy_labs/_data_pipelines.py +162 -0
- sempy_labs/_dataflows.py +668 -0
- sempy_labs/_dax.py +501 -0
- sempy_labs/_daxformatter.py +80 -0
- sempy_labs/_delta_analyzer.py +467 -0
- sempy_labs/_delta_analyzer_history.py +301 -0
- sempy_labs/_dictionary_diffs.py +221 -0
- sempy_labs/_documentation.py +147 -0
- sempy_labs/_domains.py +51 -0
- sempy_labs/_eventhouses.py +182 -0
- sempy_labs/_external_data_shares.py +230 -0
- sempy_labs/_gateways.py +521 -0
- sempy_labs/_generate_semantic_model.py +521 -0
- sempy_labs/_get_connection_string.py +84 -0
- sempy_labs/_git.py +543 -0
- sempy_labs/_graphQL.py +90 -0
- sempy_labs/_helper_functions.py +2833 -0
- sempy_labs/_icons.py +149 -0
- sempy_labs/_job_scheduler.py +609 -0
- sempy_labs/_kql_databases.py +149 -0
- sempy_labs/_kql_querysets.py +124 -0
- sempy_labs/_kusto.py +137 -0
- sempy_labs/_labels.py +124 -0
- sempy_labs/_list_functions.py +1720 -0
- sempy_labs/_managed_private_endpoints.py +253 -0
- sempy_labs/_mirrored_databases.py +416 -0
- sempy_labs/_mirrored_warehouses.py +60 -0
- sempy_labs/_ml_experiments.py +113 -0
- sempy_labs/_model_auto_build.py +140 -0
- sempy_labs/_model_bpa.py +557 -0
- sempy_labs/_model_bpa_bulk.py +378 -0
- sempy_labs/_model_bpa_rules.py +859 -0
- sempy_labs/_model_dependencies.py +343 -0
- sempy_labs/_mounted_data_factories.py +123 -0
- sempy_labs/_notebooks.py +441 -0
- sempy_labs/_one_lake_integration.py +151 -0
- sempy_labs/_onelake.py +131 -0
- sempy_labs/_query_scale_out.py +433 -0
- sempy_labs/_refresh_semantic_model.py +435 -0
- sempy_labs/_semantic_models.py +468 -0
- sempy_labs/_spark.py +455 -0
- sempy_labs/_sql.py +241 -0
- sempy_labs/_sql_audit_settings.py +207 -0
- sempy_labs/_sql_endpoints.py +214 -0
- sempy_labs/_tags.py +201 -0
- sempy_labs/_translations.py +43 -0
- sempy_labs/_user_delegation_key.py +44 -0
- sempy_labs/_utils.py +79 -0
- sempy_labs/_vertipaq.py +1021 -0
- sempy_labs/_vpax.py +388 -0
- sempy_labs/_warehouses.py +234 -0
- sempy_labs/_workloads.py +140 -0
- sempy_labs/_workspace_identity.py +72 -0
- sempy_labs/_workspaces.py +595 -0
- sempy_labs/admin/__init__.py +170 -0
- sempy_labs/admin/_activities.py +167 -0
- sempy_labs/admin/_apps.py +145 -0
- sempy_labs/admin/_artifacts.py +65 -0
- sempy_labs/admin/_basic_functions.py +463 -0
- sempy_labs/admin/_capacities.py +508 -0
- sempy_labs/admin/_dataflows.py +45 -0
- sempy_labs/admin/_datasets.py +186 -0
- sempy_labs/admin/_domains.py +522 -0
- sempy_labs/admin/_external_data_share.py +100 -0
- sempy_labs/admin/_git.py +72 -0
- sempy_labs/admin/_items.py +265 -0
- sempy_labs/admin/_labels.py +211 -0
- sempy_labs/admin/_reports.py +241 -0
- sempy_labs/admin/_scanner.py +118 -0
- sempy_labs/admin/_shared.py +82 -0
- sempy_labs/admin/_sharing_links.py +110 -0
- sempy_labs/admin/_tags.py +131 -0
- sempy_labs/admin/_tenant.py +503 -0
- sempy_labs/admin/_tenant_keys.py +89 -0
- sempy_labs/admin/_users.py +140 -0
- sempy_labs/admin/_workspaces.py +236 -0
- sempy_labs/deployment_pipeline/__init__.py +23 -0
- sempy_labs/deployment_pipeline/_items.py +580 -0
- sempy_labs/directlake/__init__.py +57 -0
- sempy_labs/directlake/_autosync.py +58 -0
- sempy_labs/directlake/_directlake_schema_compare.py +120 -0
- sempy_labs/directlake/_directlake_schema_sync.py +161 -0
- sempy_labs/directlake/_dl_helper.py +274 -0
- sempy_labs/directlake/_generate_shared_expression.py +94 -0
- sempy_labs/directlake/_get_directlake_lakehouse.py +62 -0
- sempy_labs/directlake/_get_shared_expression.py +34 -0
- sempy_labs/directlake/_guardrails.py +96 -0
- sempy_labs/directlake/_list_directlake_model_calc_tables.py +70 -0
- sempy_labs/directlake/_show_unsupported_directlake_objects.py +90 -0
- sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +239 -0
- sempy_labs/directlake/_update_directlake_partition_entity.py +259 -0
- sempy_labs/directlake/_warm_cache.py +236 -0
- sempy_labs/dotnet_lib/dotnet.runtime.config.json +10 -0
- sempy_labs/environment/__init__.py +23 -0
- sempy_labs/environment/_items.py +212 -0
- sempy_labs/environment/_pubstage.py +223 -0
- sempy_labs/eventstream/__init__.py +37 -0
- sempy_labs/eventstream/_items.py +263 -0
- sempy_labs/eventstream/_topology.py +652 -0
- sempy_labs/graph/__init__.py +59 -0
- sempy_labs/graph/_groups.py +651 -0
- sempy_labs/graph/_sensitivity_labels.py +120 -0
- sempy_labs/graph/_teams.py +125 -0
- sempy_labs/graph/_user_licenses.py +96 -0
- sempy_labs/graph/_users.py +516 -0
- sempy_labs/graph_model/__init__.py +15 -0
- sempy_labs/graph_model/_background_jobs.py +63 -0
- sempy_labs/graph_model/_items.py +149 -0
- sempy_labs/lakehouse/__init__.py +67 -0
- sempy_labs/lakehouse/_blobs.py +247 -0
- sempy_labs/lakehouse/_get_lakehouse_columns.py +102 -0
- sempy_labs/lakehouse/_get_lakehouse_tables.py +274 -0
- sempy_labs/lakehouse/_helper.py +250 -0
- sempy_labs/lakehouse/_lakehouse.py +351 -0
- sempy_labs/lakehouse/_livy_sessions.py +143 -0
- sempy_labs/lakehouse/_materialized_lake_views.py +157 -0
- sempy_labs/lakehouse/_partitioning.py +165 -0
- sempy_labs/lakehouse/_schemas.py +217 -0
- sempy_labs/lakehouse/_shortcuts.py +440 -0
- sempy_labs/migration/__init__.py +35 -0
- sempy_labs/migration/_create_pqt_file.py +238 -0
- sempy_labs/migration/_direct_lake_to_import.py +105 -0
- sempy_labs/migration/_migrate_calctables_to_lakehouse.py +398 -0
- sempy_labs/migration/_migrate_calctables_to_semantic_model.py +148 -0
- sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +533 -0
- sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +172 -0
- sempy_labs/migration/_migration_validation.py +71 -0
- sempy_labs/migration/_refresh_calc_tables.py +131 -0
- sempy_labs/mirrored_azure_databricks_catalog/__init__.py +15 -0
- sempy_labs/mirrored_azure_databricks_catalog/_discover.py +213 -0
- sempy_labs/mirrored_azure_databricks_catalog/_refresh_catalog_metadata.py +45 -0
- sempy_labs/ml_model/__init__.py +23 -0
- sempy_labs/ml_model/_functions.py +427 -0
- sempy_labs/report/_BPAReportTemplate.json +232 -0
- sempy_labs/report/__init__.py +55 -0
- sempy_labs/report/_bpareporttemplate/.pbi/localSettings.json +9 -0
- sempy_labs/report/_bpareporttemplate/.platform +11 -0
- sempy_labs/report/_bpareporttemplate/StaticResources/SharedResources/BaseThemes/CY24SU06.json +710 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/page.json +11 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/1b08bce3bebabb0a27a8/visual.json +191 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/2f22ddb70c301693c165/visual.json +438 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/3b1182230aa6c600b43a/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/58577ba6380c69891500/visual.json +576 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/a2a8fa5028b3b776c96c/visual.json +207 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/adfd47ef30652707b987/visual.json +506 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/b6a80ee459e716e170b1/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/ce3130a721c020cc3d81/visual.json +513 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/page.json +8 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/visuals/66e60dfb526437cd78d1/visual.json +112 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/page.json +11 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/07deb8bce824e1be37d7/visual.json +513 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0b1c68838818b32ad03b/visual.json +352 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0c171de9d2683d10b930/visual.json +37 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0efa01be0510e40a645e/visual.json +542 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/6bf2f0eb830ab53cc668/visual.json +221 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/88d8141cb8500b60030c/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/a753273590beed656a03/visual.json +576 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/b8fdc82cddd61ac447bc/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/page.json +9 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/visuals/ce8532a7e25020271077/visual.json +38 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/pages.json +10 -0
- sempy_labs/report/_bpareporttemplate/definition/report.json +176 -0
- sempy_labs/report/_bpareporttemplate/definition/version.json +4 -0
- sempy_labs/report/_bpareporttemplate/definition.pbir +14 -0
- sempy_labs/report/_download_report.py +76 -0
- sempy_labs/report/_export_report.py +257 -0
- sempy_labs/report/_generate_report.py +427 -0
- sempy_labs/report/_paginated.py +76 -0
- sempy_labs/report/_report_bpa.py +354 -0
- sempy_labs/report/_report_bpa_rules.py +115 -0
- sempy_labs/report/_report_functions.py +581 -0
- sempy_labs/report/_report_helper.py +227 -0
- sempy_labs/report/_report_list_functions.py +110 -0
- sempy_labs/report/_report_rebind.py +149 -0
- sempy_labs/report/_reportwrapper.py +3100 -0
- sempy_labs/report/_save_report.py +147 -0
- sempy_labs/snowflake_database/__init__.py +10 -0
- sempy_labs/snowflake_database/_items.py +105 -0
- sempy_labs/sql_database/__init__.py +21 -0
- sempy_labs/sql_database/_items.py +201 -0
- sempy_labs/sql_database/_mirroring.py +79 -0
- sempy_labs/theme/__init__.py +12 -0
- sempy_labs/theme/_org_themes.py +129 -0
- sempy_labs/tom/__init__.py +3 -0
- sempy_labs/tom/_model.py +5977 -0
- sempy_labs/variable_library/__init__.py +19 -0
- sempy_labs/variable_library/_functions.py +403 -0
- sempy_labs/warehouse/__init__.py +28 -0
- sempy_labs/warehouse/_items.py +234 -0
- sempy_labs/warehouse/_restore_points.py +309 -0
sempy_labs/_dax.py
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import sempy.fabric as fabric
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from sempy_labs._helper_functions import (
|
|
4
|
+
resolve_workspace_name_and_id,
|
|
5
|
+
format_dax_object_name,
|
|
6
|
+
resolve_dataset_name_and_id,
|
|
7
|
+
_base_api,
|
|
8
|
+
generate_guid,
|
|
9
|
+
)
|
|
10
|
+
from sempy_labs._model_dependencies import get_model_calc_dependencies
|
|
11
|
+
from typing import Optional, List, Tuple
|
|
12
|
+
from sempy._utils._log import log
|
|
13
|
+
from uuid import UUID
|
|
14
|
+
from sempy_labs.directlake._warm_cache import _put_columns_into_memory
|
|
15
|
+
import sempy_labs._icons as icons
|
|
16
|
+
import time
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@log
|
|
20
|
+
def evaluate_dax_impersonation(
|
|
21
|
+
dataset: str | UUID,
|
|
22
|
+
dax_query: str,
|
|
23
|
+
user_name: Optional[str] = None,
|
|
24
|
+
workspace: Optional[str | UUID] = None,
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Runs a DAX query against a semantic model using the `REST API <https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/execute-queries-in-group>`_.
|
|
28
|
+
|
|
29
|
+
Compared to evaluate_dax this allows passing the user name for impersonation.
|
|
30
|
+
Note that the REST API has significant limitations compared to the XMLA endpoint.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
dataset : str | uuid.UUID
|
|
35
|
+
Name or ID of the semantic model.
|
|
36
|
+
dax_query : str
|
|
37
|
+
The DAX query.
|
|
38
|
+
user_name : str
|
|
39
|
+
The user name (i.e. hello@goodbye.com).
|
|
40
|
+
workspace : str | uuid.UUID, default=None
|
|
41
|
+
The Fabric workspace name or ID.
|
|
42
|
+
Defaults to None which resolves to the workspace of the attached lakehouse
|
|
43
|
+
or if no lakehouse attached, resolves to the workspace of the notebook.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
pandas.DataFrame
|
|
48
|
+
A pandas dataframe holding the result of the DAX query.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
(workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace)
|
|
52
|
+
(dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id)
|
|
53
|
+
|
|
54
|
+
payload = {
|
|
55
|
+
"queries": [{"query": dax_query}],
|
|
56
|
+
"impersonatedUserName": user_name,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
response = _base_api(
|
|
60
|
+
request=f"/v1.0/myorg/groups/{workspace_id}/datasets/{dataset_id}/executeQueries",
|
|
61
|
+
method="post",
|
|
62
|
+
payload=payload,
|
|
63
|
+
)
|
|
64
|
+
data = response.json()["results"][0]["tables"]
|
|
65
|
+
|
|
66
|
+
# Get all possible column names from all rows because null columns aren't returned
|
|
67
|
+
all_columns = set()
|
|
68
|
+
for item in data:
|
|
69
|
+
for row in item["rows"]:
|
|
70
|
+
all_columns.update(row.keys())
|
|
71
|
+
|
|
72
|
+
# Create rows with all columns, filling missing values with None
|
|
73
|
+
rows = []
|
|
74
|
+
for item in data:
|
|
75
|
+
for row in item["rows"]:
|
|
76
|
+
# Create a new row with all columns, defaulting to None
|
|
77
|
+
new_row = {col: row.get(col) for col in all_columns}
|
|
78
|
+
rows.append(new_row)
|
|
79
|
+
|
|
80
|
+
# Create DataFrame from the processed rows
|
|
81
|
+
df = pd.DataFrame(rows)
|
|
82
|
+
|
|
83
|
+
return df
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@log
|
|
87
|
+
def get_dax_query_dependencies(
|
|
88
|
+
dataset: str | UUID,
|
|
89
|
+
dax_string: str | List[str],
|
|
90
|
+
put_in_memory: bool = False,
|
|
91
|
+
show_vertipaq_stats: bool = True,
|
|
92
|
+
workspace: Optional[str | UUID] = None,
|
|
93
|
+
) -> pd.DataFrame:
|
|
94
|
+
"""
|
|
95
|
+
Obtains the columns on which a DAX query depends, including model dependencies. Shows Vertipaq statistics (i.e. Total Size, Data Size, Dictionary Size, Hierarchy Size) for easy prioritizing.
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
dataset : str | uuid.UUID
|
|
100
|
+
Name or ID of the semantic model.
|
|
101
|
+
dax_string : str | List[str]
|
|
102
|
+
The DAX query or list of DAX queries.
|
|
103
|
+
put_in_memory : bool, default=False
|
|
104
|
+
If True, ensures that the dependent columns are put into memory in order to give realistic Vertipaq stats (i.e. Total Size etc.).
|
|
105
|
+
show_vertipaq_stats : bool, default=True
|
|
106
|
+
If True, shows vertipaq stats (i.e. Total Size, Data Size, Dictionary Size, Hierarchy Size)
|
|
107
|
+
workspace : str | uuid.UUID, default=None
|
|
108
|
+
The Fabric workspace name or ID.
|
|
109
|
+
Defaults to None which resolves to the workspace of the attached lakehouse
|
|
110
|
+
or if no lakehouse attached, resolves to the workspace of the notebook.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
pandas.DataFrame
|
|
115
|
+
A pandas dataframe showing the dependent columns of a given DAX query including model dependencies.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
(workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace)
|
|
119
|
+
(dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id)
|
|
120
|
+
|
|
121
|
+
fabric.refresh_tom_cache(workspace=workspace)
|
|
122
|
+
|
|
123
|
+
if isinstance(dax_string, str):
|
|
124
|
+
dax_string = [dax_string]
|
|
125
|
+
|
|
126
|
+
final_df = pd.DataFrame(columns=["Object Type", "Table", "Object"])
|
|
127
|
+
|
|
128
|
+
cd = get_model_calc_dependencies(dataset=dataset_id, workspace=workspace_id)
|
|
129
|
+
|
|
130
|
+
for dax in dax_string:
|
|
131
|
+
# Escape quotes in dax
|
|
132
|
+
dax = dax.replace('"', '""')
|
|
133
|
+
final_query = f"""
|
|
134
|
+
EVALUATE
|
|
135
|
+
VAR source_query = "{dax}"
|
|
136
|
+
VAR all_dependencies = SELECTCOLUMNS(
|
|
137
|
+
INFO.CALCDEPENDENCY("QUERY", source_query),
|
|
138
|
+
"Referenced Object Type",[REFERENCED_OBJECT_TYPE],
|
|
139
|
+
"Referenced Table", [REFERENCED_TABLE],
|
|
140
|
+
"Referenced Object", [REFERENCED_OBJECT]
|
|
141
|
+
)
|
|
142
|
+
RETURN all_dependencies
|
|
143
|
+
"""
|
|
144
|
+
dep = fabric.evaluate_dax(
|
|
145
|
+
dataset=dataset_id, workspace=workspace_id, dax_string=final_query
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Clean up column names and values (remove outside square brackets, underscorees in object type)
|
|
149
|
+
dep.columns = dep.columns.map(lambda x: x[1:-1])
|
|
150
|
+
dep["Referenced Object Type"] = (
|
|
151
|
+
dep["Referenced Object Type"].str.replace("_", " ").str.title()
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Dataframe df will contain the output of all dependencies of the objects used in the query
|
|
155
|
+
df = dep.copy()
|
|
156
|
+
|
|
157
|
+
for _, r in dep.iterrows():
|
|
158
|
+
ot = r["Referenced Object Type"]
|
|
159
|
+
object_name = r["Referenced Object"]
|
|
160
|
+
table_name = r["Referenced Table"]
|
|
161
|
+
cd_filt = cd[
|
|
162
|
+
(cd["Object Type"] == ot)
|
|
163
|
+
& (cd["Object Name"] == object_name)
|
|
164
|
+
& (cd["Table Name"] == table_name)
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
# Adds in the dependencies of each object used in the query (i.e. relationship etc.)
|
|
168
|
+
if len(cd_filt) > 0:
|
|
169
|
+
subset = cd_filt[
|
|
170
|
+
["Referenced Object Type", "Referenced Table", "Referenced Object"]
|
|
171
|
+
]
|
|
172
|
+
df = pd.concat([df, subset], ignore_index=True)
|
|
173
|
+
|
|
174
|
+
df.columns = df.columns.map(lambda x: x.replace("Referenced ", ""))
|
|
175
|
+
final_df = pd.concat([df, final_df], ignore_index=True)
|
|
176
|
+
|
|
177
|
+
final_df = final_df[
|
|
178
|
+
(final_df["Object Type"].isin(["Column", "Calc Column"]))
|
|
179
|
+
& (~final_df["Object"].str.startswith("RowNumber-"))
|
|
180
|
+
]
|
|
181
|
+
final_df = final_df.drop_duplicates().reset_index(drop=True)
|
|
182
|
+
final_df = final_df.rename(columns={"Table": "Table Name", "Object": "Column Name"})
|
|
183
|
+
final_df.drop(columns=["Object Type"], inplace=True)
|
|
184
|
+
|
|
185
|
+
if not show_vertipaq_stats:
|
|
186
|
+
return final_df
|
|
187
|
+
|
|
188
|
+
# Get vertipaq stats, filter to just the objects in the df dataframe
|
|
189
|
+
final_df["Full Object"] = format_dax_object_name(
|
|
190
|
+
final_df["Table Name"], final_df["Column Name"]
|
|
191
|
+
)
|
|
192
|
+
dfC = fabric.list_columns(dataset=dataset_id, workspace=workspace_id, extended=True)
|
|
193
|
+
dfC["Full Object"] = format_dax_object_name(dfC["Table Name"], dfC["Column Name"])
|
|
194
|
+
|
|
195
|
+
dfC_filtered = dfC[dfC["Full Object"].isin(final_df["Full Object"].values)][
|
|
196
|
+
[
|
|
197
|
+
"Table Name",
|
|
198
|
+
"Column Name",
|
|
199
|
+
"Total Size",
|
|
200
|
+
"Data Size",
|
|
201
|
+
"Dictionary Size",
|
|
202
|
+
"Hierarchy Size",
|
|
203
|
+
"Is Resident",
|
|
204
|
+
"Full Object",
|
|
205
|
+
]
|
|
206
|
+
].reset_index(drop=True)
|
|
207
|
+
|
|
208
|
+
if put_in_memory:
|
|
209
|
+
# Only put columns in memory if they are in a Direct Lake table (and are not already in memory)
|
|
210
|
+
dfP = fabric.list_partitions(dataset=dataset, workspace=workspace)
|
|
211
|
+
dl_tables = dfP[dfP["Mode"] == "DirectLake"]["Table Name"].unique().tolist()
|
|
212
|
+
not_in_memory = dfC_filtered[
|
|
213
|
+
(dfC_filtered["Table Name"].isin(dl_tables))
|
|
214
|
+
& (dfC_filtered["Is Resident"] == False)
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
if not not_in_memory.empty:
|
|
218
|
+
_put_columns_into_memory(
|
|
219
|
+
dataset=dataset,
|
|
220
|
+
workspace=workspace,
|
|
221
|
+
col_df=dfC_filtered,
|
|
222
|
+
return_dataframe=False,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Get column stats again
|
|
226
|
+
dfC = fabric.list_columns(
|
|
227
|
+
dataset=dataset_id, workspace=workspace_id, extended=True
|
|
228
|
+
)
|
|
229
|
+
dfC["Full Object"] = format_dax_object_name(
|
|
230
|
+
dfC["Table Name"], dfC["Column Name"]
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
dfC_filtered = dfC[dfC["Full Object"].isin(final_df["Full Object"].values)][
|
|
234
|
+
[
|
|
235
|
+
"Table Name",
|
|
236
|
+
"Column Name",
|
|
237
|
+
"Total Size",
|
|
238
|
+
"Data Size",
|
|
239
|
+
"Dictionary Size",
|
|
240
|
+
"Hierarchy Size",
|
|
241
|
+
"Is Resident",
|
|
242
|
+
"Full Object",
|
|
243
|
+
]
|
|
244
|
+
].reset_index(drop=True)
|
|
245
|
+
|
|
246
|
+
dfC_filtered.drop(["Full Object"], axis=1, inplace=True)
|
|
247
|
+
|
|
248
|
+
return dfC_filtered
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@log
|
|
252
|
+
def get_dax_query_memory_size(
|
|
253
|
+
dataset: str | UUID, dax_string: str, workspace: Optional[str | UUID] = None
|
|
254
|
+
) -> int:
|
|
255
|
+
"""
|
|
256
|
+
Obtains the total size, in bytes, used by all columns that a DAX query depends on.
|
|
257
|
+
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
dataset : str | uuid.UUID
|
|
261
|
+
Name or ID of the semantic model.
|
|
262
|
+
dax_string : str
|
|
263
|
+
The DAX query.
|
|
264
|
+
workspace : str | uuid.UUID, default=None
|
|
265
|
+
The Fabric workspace name or ID.
|
|
266
|
+
Defaults to None which resolves to the workspace of the attached lakehouse
|
|
267
|
+
or if no lakehouse attached, resolves to the workspace of the notebook.
|
|
268
|
+
|
|
269
|
+
Returns
|
|
270
|
+
-------
|
|
271
|
+
int
|
|
272
|
+
The total size, in bytes, used by all columns that the DAX query depends on.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
(workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace)
|
|
276
|
+
(dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id)
|
|
277
|
+
|
|
278
|
+
df = get_dax_query_dependencies(
|
|
279
|
+
dataset=dataset_id,
|
|
280
|
+
workspace=workspace_id,
|
|
281
|
+
dax_string=dax_string,
|
|
282
|
+
put_in_memory=True,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
return df["Total Size"].sum()
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@log
|
|
289
|
+
def _dax_perf_test(
|
|
290
|
+
dataset: str,
|
|
291
|
+
dax_queries: dict,
|
|
292
|
+
clear_cache_before_run: bool = False,
|
|
293
|
+
refresh_type: Optional[str] = None,
|
|
294
|
+
rest_time: int = 2,
|
|
295
|
+
workspace: Optional[str] = None,
|
|
296
|
+
) -> Tuple[pd.DataFrame, dict]:
|
|
297
|
+
"""
|
|
298
|
+
Runs a performance test on a set of DAX queries.
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
dataset : str
|
|
303
|
+
Name of the semantic model.
|
|
304
|
+
dax_queries : dict
|
|
305
|
+
The dax queries to run in a dictionary format. Here is an example:
|
|
306
|
+
{
|
|
307
|
+
"Sales Amount Test", """ """ EVALUATE SUMMARIZECOLUMNS("Sales Amount", [Sales Amount]) """ """,
|
|
308
|
+
"Order Quantity with Product", """ """ EVALUATE SUMMARIZECOLUMNS('Product'[Color], "Order Qty", [Order Qty]) """ """,
|
|
309
|
+
}
|
|
310
|
+
clear_cache_before_run : bool, default=False
|
|
311
|
+
refresh_type : str, default=None
|
|
312
|
+
rest_time : int, default=2
|
|
313
|
+
Rest time (in seconds) between the execution of each DAX query.
|
|
314
|
+
workspace : str, default=None
|
|
315
|
+
The Fabric workspace name.
|
|
316
|
+
Defaults to None which resolves to the workspace of the attached lakehouse
|
|
317
|
+
or if no lakehouse attached, resolves to the workspace of the notebook.
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
Tuple[pandas.DataFrame, dict]
|
|
322
|
+
A pandas dataframe showing the SQL profiler trace results of the DAX queries.
|
|
323
|
+
A dictionary of the query results in pandas dataframes.
|
|
324
|
+
"""
|
|
325
|
+
from sempy_labs._refresh_semantic_model import refresh_semantic_model
|
|
326
|
+
from sempy_labs._clear_cache import clear_cache
|
|
327
|
+
|
|
328
|
+
event_schema = {
|
|
329
|
+
"QueryBegin": [
|
|
330
|
+
"EventClass",
|
|
331
|
+
"EventSubclass",
|
|
332
|
+
"CurrentTime",
|
|
333
|
+
"NTUserName",
|
|
334
|
+
"TextData",
|
|
335
|
+
"StartTime",
|
|
336
|
+
"ApplicationName",
|
|
337
|
+
],
|
|
338
|
+
"QueryEnd": [
|
|
339
|
+
"EventClass",
|
|
340
|
+
"EventSubclass",
|
|
341
|
+
"CurrentTime",
|
|
342
|
+
"NTUserName",
|
|
343
|
+
"TextData",
|
|
344
|
+
"StartTime",
|
|
345
|
+
"EndTime",
|
|
346
|
+
"Duration",
|
|
347
|
+
"CpuTime",
|
|
348
|
+
"Success",
|
|
349
|
+
"ApplicationName",
|
|
350
|
+
],
|
|
351
|
+
"VertiPaqSEQueryBegin": [
|
|
352
|
+
"EventClass",
|
|
353
|
+
"EventSubclass",
|
|
354
|
+
"CurrentTime",
|
|
355
|
+
"NTUserName",
|
|
356
|
+
"TextData",
|
|
357
|
+
"StartTime",
|
|
358
|
+
],
|
|
359
|
+
"VertiPaqSEQueryEnd": [
|
|
360
|
+
"EventClass",
|
|
361
|
+
"EventSubclass",
|
|
362
|
+
"CurrentTime",
|
|
363
|
+
"NTUserName",
|
|
364
|
+
"TextData",
|
|
365
|
+
"StartTime",
|
|
366
|
+
"EndTime",
|
|
367
|
+
"Duration",
|
|
368
|
+
"CpuTime",
|
|
369
|
+
"Success",
|
|
370
|
+
],
|
|
371
|
+
"VertiPaqSEQueryCacheMatch": [
|
|
372
|
+
"EventClass",
|
|
373
|
+
"EventSubclass",
|
|
374
|
+
"CurrentTime",
|
|
375
|
+
"NTUserName",
|
|
376
|
+
"TextData",
|
|
377
|
+
],
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
# Add Execution Metrics
|
|
381
|
+
event_schema["ExecutionMetrics"] = ["EventClass", "ApplicationName", "TextData"]
|
|
382
|
+
# Add DAX Query Plan
|
|
383
|
+
# event_schema["DAXQueryPlan"] = ["EventClass", "EventSubclass", "CurrentTime", "StartTime", "EndTime", "Duration", "CpuTime", "ApplicationName", "TextData"]
|
|
384
|
+
|
|
385
|
+
query_results = {}
|
|
386
|
+
|
|
387
|
+
# Establish trace connection
|
|
388
|
+
with fabric.create_trace_connection(
|
|
389
|
+
dataset=dataset, workspace=workspace
|
|
390
|
+
) as trace_connection:
|
|
391
|
+
with trace_connection.create_trace(event_schema) as trace:
|
|
392
|
+
trace.start()
|
|
393
|
+
print(f"{icons.in_progress} Starting performance testing...")
|
|
394
|
+
# Loop through DAX queries
|
|
395
|
+
for name, dax in dax_queries.items():
|
|
396
|
+
|
|
397
|
+
if clear_cache_before_run:
|
|
398
|
+
clear_cache(dataset=dataset, workspace=workspace)
|
|
399
|
+
if refresh_type is not None:
|
|
400
|
+
refresh_semantic_model(
|
|
401
|
+
dataset=dataset, workspace=workspace, refresh_type=refresh_type
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# EVALUATE {1} is used to initate a warm cache
|
|
405
|
+
fabric.evaluate_dax(
|
|
406
|
+
dataset=dataset, workspace=workspace, dax_string="""EVALUATE {1}"""
|
|
407
|
+
)
|
|
408
|
+
# Run DAX Query
|
|
409
|
+
result = fabric.evaluate_dax(
|
|
410
|
+
dataset=dataset, workspace=workspace, dax_string=dax
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# Add results to output
|
|
414
|
+
query_results[name] = result
|
|
415
|
+
|
|
416
|
+
time.sleep(rest_time)
|
|
417
|
+
print(f"{icons.green_dot} The '{name}' query has completed.")
|
|
418
|
+
|
|
419
|
+
df = trace.stop()
|
|
420
|
+
# Allow time to collect trace results
|
|
421
|
+
time.sleep(5)
|
|
422
|
+
|
|
423
|
+
# Step 1: Filter out unnecessary operations
|
|
424
|
+
query_names = list(dax_queries.keys())
|
|
425
|
+
df = df[
|
|
426
|
+
~df["Application Name"].isin(["PowerBI", "PowerBIEIM"])
|
|
427
|
+
& (~df["Text Data"].str.startswith("EVALUATE {1}"))
|
|
428
|
+
]
|
|
429
|
+
query_begin = df["Event Class"] == "QueryBegin"
|
|
430
|
+
temp_column_name = "QueryName_INT"
|
|
431
|
+
df = df.copy()
|
|
432
|
+
df[temp_column_name] = query_begin.cumsum()
|
|
433
|
+
df[temp_column_name] = (
|
|
434
|
+
df[temp_column_name]
|
|
435
|
+
.where(query_begin, None) # Assign None to non-query begin rows
|
|
436
|
+
.ffill() # Forward fill None values
|
|
437
|
+
.astype("Int64") # Use pandas nullable integer type for numeric indices
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
df.loc[df[temp_column_name].notna(), "Query Name"] = (
|
|
441
|
+
df[temp_column_name]
|
|
442
|
+
.dropna()
|
|
443
|
+
.astype(int)
|
|
444
|
+
.map(lambda x: query_names[x - 1])
|
|
445
|
+
)
|
|
446
|
+
df = df[df[temp_column_name] != None]
|
|
447
|
+
df = df.drop(columns=[temp_column_name])
|
|
448
|
+
|
|
449
|
+
query_to_guid = {
|
|
450
|
+
name: generate_guid() for name in df["Query Name"].unique()
|
|
451
|
+
}
|
|
452
|
+
df["Query ID"] = df["Query Name"].map(query_to_guid)
|
|
453
|
+
|
|
454
|
+
df = df.reset_index(drop=True)
|
|
455
|
+
|
|
456
|
+
return df, query_results
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _dax_perf_test_bulk(
|
|
460
|
+
mapping: dict,
|
|
461
|
+
clear_cache_before_run: bool = False,
|
|
462
|
+
refresh_type: Optional[str] = None,
|
|
463
|
+
rest_time: int = 2,
|
|
464
|
+
):
|
|
465
|
+
"""
|
|
466
|
+
mapping is something like this:
|
|
467
|
+
|
|
468
|
+
mapping = {
|
|
469
|
+
"Workspace1": {
|
|
470
|
+
"Dataset1": {
|
|
471
|
+
"Query1": "EVALUATE ...",
|
|
472
|
+
"Query2": "EVALUATE ...",
|
|
473
|
+
},
|
|
474
|
+
"Dataset2": {
|
|
475
|
+
"Query3": "EVALUATE ...",
|
|
476
|
+
"Query4": "EVALUATE ...",
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
"Workspace2": {
|
|
480
|
+
"Dataset3": {
|
|
481
|
+
"Query5": "EVALUATE ...",
|
|
482
|
+
"Query6": "EVALUATE ...",
|
|
483
|
+
},
|
|
484
|
+
"Dataset4": {
|
|
485
|
+
"Query7": "EVALUATE ...",
|
|
486
|
+
"Query8": "EVALUATE ...",
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
for workspace, datasets in mapping.items():
|
|
493
|
+
for dataset, queries in datasets.items():
|
|
494
|
+
_dax_perf_test(
|
|
495
|
+
dataset=dataset,
|
|
496
|
+
dax_queries=queries,
|
|
497
|
+
clear_cache_before_run=clear_cache_before_run,
|
|
498
|
+
refresh_type=refresh_type,
|
|
499
|
+
rest_time=rest_time,
|
|
500
|
+
workspace=workspace,
|
|
501
|
+
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
from sempy_labs._a_lib_info import lib_name, lib_version
|
|
4
|
+
from sempy._utils._log import log
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@log
|
|
8
|
+
def _format_dax(
|
|
9
|
+
expressions: str | List[str],
|
|
10
|
+
skip_space_after_function_name: bool = False,
|
|
11
|
+
metadata: Optional[List[dict]] = None,
|
|
12
|
+
) -> List[str]:
|
|
13
|
+
|
|
14
|
+
if isinstance(expressions, str):
|
|
15
|
+
expressions = [expressions]
|
|
16
|
+
metadata = [metadata] if metadata else [{}]
|
|
17
|
+
|
|
18
|
+
# Add variable assignment to each expression
|
|
19
|
+
expressions = [f"x :={item}" for item in expressions]
|
|
20
|
+
|
|
21
|
+
url = "https://daxformatter.azurewebsites.net/api/daxformatter/daxtextformatmulti"
|
|
22
|
+
|
|
23
|
+
payload = {
|
|
24
|
+
"Dax": expressions,
|
|
25
|
+
"MaxLineLength": 0,
|
|
26
|
+
"SkipSpaceAfterFunctionName": skip_space_after_function_name,
|
|
27
|
+
"ListSeparator": ",",
|
|
28
|
+
"DecimalSeparator": ".",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
headers = {
|
|
32
|
+
"Accept": "application/json, text/javascript, */*; q=0.01",
|
|
33
|
+
"Accept-Encoding": "gzip,deflate",
|
|
34
|
+
"Accept-Language": "en-US,en;q=0.8",
|
|
35
|
+
"Content-Type": "application/json; charset=UTF-8",
|
|
36
|
+
"Host": "daxformatter.azurewebsites.net",
|
|
37
|
+
"Expect": "100-continue",
|
|
38
|
+
"Connection": "Keep-Alive",
|
|
39
|
+
"CallerApp": lib_name,
|
|
40
|
+
"CallerVersion": lib_version,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
response = requests.post(url, json=payload, headers=headers)
|
|
44
|
+
result = []
|
|
45
|
+
for idx, dax in enumerate(response.json()):
|
|
46
|
+
formatted_dax = dax.get("formatted")
|
|
47
|
+
errors = dax.get("errors")
|
|
48
|
+
if errors:
|
|
49
|
+
meta = metadata[idx] if metadata and idx < len(metadata) else {}
|
|
50
|
+
obj_name = meta.get("name", "Unknown")
|
|
51
|
+
table_name = meta.get("table", "Unknown")
|
|
52
|
+
obj_type = meta.get("type", "Unknown")
|
|
53
|
+
if obj_type == "calculated_tables":
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"DAX formatting failed for the '{obj_name}' calculated table: {errors}"
|
|
56
|
+
)
|
|
57
|
+
elif obj_type == "calculated_columns":
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"DAX formatting failed for the '{table_name}'[{obj_name}] calculated column: {errors}"
|
|
60
|
+
)
|
|
61
|
+
elif obj_type == "calculation_items":
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"DAX formatting failed for the '{table_name}'[{obj_name}] calculation item: {errors}"
|
|
64
|
+
)
|
|
65
|
+
elif obj_type == "measures":
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"DAX formatting failed for the '{obj_name}' measure: {errors}"
|
|
68
|
+
)
|
|
69
|
+
elif obj_type == "rls":
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"DAX formatting failed for the row level security expression on the '{table_name}' table within the '{obj_name}' role: {errors}"
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
NotImplementedError()
|
|
75
|
+
else:
|
|
76
|
+
if formatted_dax.startswith("x :="):
|
|
77
|
+
formatted_dax = formatted_dax[4:]
|
|
78
|
+
formatted_dax = formatted_dax.strip()
|
|
79
|
+
result.append(formatted_dax)
|
|
80
|
+
return result
|