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.
Files changed (243) hide show
  1. semantic_link_labs-0.12.8.dist-info/METADATA +354 -0
  2. semantic_link_labs-0.12.8.dist-info/RECORD +243 -0
  3. semantic_link_labs-0.12.8.dist-info/WHEEL +5 -0
  4. semantic_link_labs-0.12.8.dist-info/licenses/LICENSE +21 -0
  5. semantic_link_labs-0.12.8.dist-info/top_level.txt +1 -0
  6. sempy_labs/__init__.py +606 -0
  7. sempy_labs/_a_lib_info.py +2 -0
  8. sempy_labs/_ai.py +437 -0
  9. sempy_labs/_authentication.py +264 -0
  10. sempy_labs/_bpa_translation/_model/_translations_am-ET.po +869 -0
  11. sempy_labs/_bpa_translation/_model/_translations_ar-AE.po +908 -0
  12. sempy_labs/_bpa_translation/_model/_translations_bg-BG.po +968 -0
  13. sempy_labs/_bpa_translation/_model/_translations_ca-ES.po +963 -0
  14. sempy_labs/_bpa_translation/_model/_translations_cs-CZ.po +943 -0
  15. sempy_labs/_bpa_translation/_model/_translations_da-DK.po +945 -0
  16. sempy_labs/_bpa_translation/_model/_translations_de-DE.po +988 -0
  17. sempy_labs/_bpa_translation/_model/_translations_el-GR.po +993 -0
  18. sempy_labs/_bpa_translation/_model/_translations_es-ES.po +971 -0
  19. sempy_labs/_bpa_translation/_model/_translations_fa-IR.po +933 -0
  20. sempy_labs/_bpa_translation/_model/_translations_fi-FI.po +942 -0
  21. sempy_labs/_bpa_translation/_model/_translations_fr-FR.po +994 -0
  22. sempy_labs/_bpa_translation/_model/_translations_ga-IE.po +967 -0
  23. sempy_labs/_bpa_translation/_model/_translations_he-IL.po +902 -0
  24. sempy_labs/_bpa_translation/_model/_translations_hi-IN.po +944 -0
  25. sempy_labs/_bpa_translation/_model/_translations_hu-HU.po +963 -0
  26. sempy_labs/_bpa_translation/_model/_translations_id-ID.po +946 -0
  27. sempy_labs/_bpa_translation/_model/_translations_is-IS.po +939 -0
  28. sempy_labs/_bpa_translation/_model/_translations_it-IT.po +986 -0
  29. sempy_labs/_bpa_translation/_model/_translations_ja-JP.po +846 -0
  30. sempy_labs/_bpa_translation/_model/_translations_ko-KR.po +839 -0
  31. sempy_labs/_bpa_translation/_model/_translations_mt-MT.po +967 -0
  32. sempy_labs/_bpa_translation/_model/_translations_nl-NL.po +978 -0
  33. sempy_labs/_bpa_translation/_model/_translations_pl-PL.po +962 -0
  34. sempy_labs/_bpa_translation/_model/_translations_pt-BR.po +962 -0
  35. sempy_labs/_bpa_translation/_model/_translations_pt-PT.po +957 -0
  36. sempy_labs/_bpa_translation/_model/_translations_ro-RO.po +968 -0
  37. sempy_labs/_bpa_translation/_model/_translations_ru-RU.po +964 -0
  38. sempy_labs/_bpa_translation/_model/_translations_sk-SK.po +952 -0
  39. sempy_labs/_bpa_translation/_model/_translations_sl-SL.po +950 -0
  40. sempy_labs/_bpa_translation/_model/_translations_sv-SE.po +942 -0
  41. sempy_labs/_bpa_translation/_model/_translations_ta-IN.po +976 -0
  42. sempy_labs/_bpa_translation/_model/_translations_te-IN.po +947 -0
  43. sempy_labs/_bpa_translation/_model/_translations_th-TH.po +924 -0
  44. sempy_labs/_bpa_translation/_model/_translations_tr-TR.po +953 -0
  45. sempy_labs/_bpa_translation/_model/_translations_uk-UA.po +961 -0
  46. sempy_labs/_bpa_translation/_model/_translations_zh-CN.po +804 -0
  47. sempy_labs/_bpa_translation/_model/_translations_zu-ZA.po +969 -0
  48. sempy_labs/_capacities.py +1198 -0
  49. sempy_labs/_capacity_migration.py +660 -0
  50. sempy_labs/_clear_cache.py +351 -0
  51. sempy_labs/_connections.py +610 -0
  52. sempy_labs/_dashboards.py +69 -0
  53. sempy_labs/_data_access_security.py +98 -0
  54. sempy_labs/_data_pipelines.py +162 -0
  55. sempy_labs/_dataflows.py +668 -0
  56. sempy_labs/_dax.py +501 -0
  57. sempy_labs/_daxformatter.py +80 -0
  58. sempy_labs/_delta_analyzer.py +467 -0
  59. sempy_labs/_delta_analyzer_history.py +301 -0
  60. sempy_labs/_dictionary_diffs.py +221 -0
  61. sempy_labs/_documentation.py +147 -0
  62. sempy_labs/_domains.py +51 -0
  63. sempy_labs/_eventhouses.py +182 -0
  64. sempy_labs/_external_data_shares.py +230 -0
  65. sempy_labs/_gateways.py +521 -0
  66. sempy_labs/_generate_semantic_model.py +521 -0
  67. sempy_labs/_get_connection_string.py +84 -0
  68. sempy_labs/_git.py +543 -0
  69. sempy_labs/_graphQL.py +90 -0
  70. sempy_labs/_helper_functions.py +2833 -0
  71. sempy_labs/_icons.py +149 -0
  72. sempy_labs/_job_scheduler.py +609 -0
  73. sempy_labs/_kql_databases.py +149 -0
  74. sempy_labs/_kql_querysets.py +124 -0
  75. sempy_labs/_kusto.py +137 -0
  76. sempy_labs/_labels.py +124 -0
  77. sempy_labs/_list_functions.py +1720 -0
  78. sempy_labs/_managed_private_endpoints.py +253 -0
  79. sempy_labs/_mirrored_databases.py +416 -0
  80. sempy_labs/_mirrored_warehouses.py +60 -0
  81. sempy_labs/_ml_experiments.py +113 -0
  82. sempy_labs/_model_auto_build.py +140 -0
  83. sempy_labs/_model_bpa.py +557 -0
  84. sempy_labs/_model_bpa_bulk.py +378 -0
  85. sempy_labs/_model_bpa_rules.py +859 -0
  86. sempy_labs/_model_dependencies.py +343 -0
  87. sempy_labs/_mounted_data_factories.py +123 -0
  88. sempy_labs/_notebooks.py +441 -0
  89. sempy_labs/_one_lake_integration.py +151 -0
  90. sempy_labs/_onelake.py +131 -0
  91. sempy_labs/_query_scale_out.py +433 -0
  92. sempy_labs/_refresh_semantic_model.py +435 -0
  93. sempy_labs/_semantic_models.py +468 -0
  94. sempy_labs/_spark.py +455 -0
  95. sempy_labs/_sql.py +241 -0
  96. sempy_labs/_sql_audit_settings.py +207 -0
  97. sempy_labs/_sql_endpoints.py +214 -0
  98. sempy_labs/_tags.py +201 -0
  99. sempy_labs/_translations.py +43 -0
  100. sempy_labs/_user_delegation_key.py +44 -0
  101. sempy_labs/_utils.py +79 -0
  102. sempy_labs/_vertipaq.py +1021 -0
  103. sempy_labs/_vpax.py +388 -0
  104. sempy_labs/_warehouses.py +234 -0
  105. sempy_labs/_workloads.py +140 -0
  106. sempy_labs/_workspace_identity.py +72 -0
  107. sempy_labs/_workspaces.py +595 -0
  108. sempy_labs/admin/__init__.py +170 -0
  109. sempy_labs/admin/_activities.py +167 -0
  110. sempy_labs/admin/_apps.py +145 -0
  111. sempy_labs/admin/_artifacts.py +65 -0
  112. sempy_labs/admin/_basic_functions.py +463 -0
  113. sempy_labs/admin/_capacities.py +508 -0
  114. sempy_labs/admin/_dataflows.py +45 -0
  115. sempy_labs/admin/_datasets.py +186 -0
  116. sempy_labs/admin/_domains.py +522 -0
  117. sempy_labs/admin/_external_data_share.py +100 -0
  118. sempy_labs/admin/_git.py +72 -0
  119. sempy_labs/admin/_items.py +265 -0
  120. sempy_labs/admin/_labels.py +211 -0
  121. sempy_labs/admin/_reports.py +241 -0
  122. sempy_labs/admin/_scanner.py +118 -0
  123. sempy_labs/admin/_shared.py +82 -0
  124. sempy_labs/admin/_sharing_links.py +110 -0
  125. sempy_labs/admin/_tags.py +131 -0
  126. sempy_labs/admin/_tenant.py +503 -0
  127. sempy_labs/admin/_tenant_keys.py +89 -0
  128. sempy_labs/admin/_users.py +140 -0
  129. sempy_labs/admin/_workspaces.py +236 -0
  130. sempy_labs/deployment_pipeline/__init__.py +23 -0
  131. sempy_labs/deployment_pipeline/_items.py +580 -0
  132. sempy_labs/directlake/__init__.py +57 -0
  133. sempy_labs/directlake/_autosync.py +58 -0
  134. sempy_labs/directlake/_directlake_schema_compare.py +120 -0
  135. sempy_labs/directlake/_directlake_schema_sync.py +161 -0
  136. sempy_labs/directlake/_dl_helper.py +274 -0
  137. sempy_labs/directlake/_generate_shared_expression.py +94 -0
  138. sempy_labs/directlake/_get_directlake_lakehouse.py +62 -0
  139. sempy_labs/directlake/_get_shared_expression.py +34 -0
  140. sempy_labs/directlake/_guardrails.py +96 -0
  141. sempy_labs/directlake/_list_directlake_model_calc_tables.py +70 -0
  142. sempy_labs/directlake/_show_unsupported_directlake_objects.py +90 -0
  143. sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +239 -0
  144. sempy_labs/directlake/_update_directlake_partition_entity.py +259 -0
  145. sempy_labs/directlake/_warm_cache.py +236 -0
  146. sempy_labs/dotnet_lib/dotnet.runtime.config.json +10 -0
  147. sempy_labs/environment/__init__.py +23 -0
  148. sempy_labs/environment/_items.py +212 -0
  149. sempy_labs/environment/_pubstage.py +223 -0
  150. sempy_labs/eventstream/__init__.py +37 -0
  151. sempy_labs/eventstream/_items.py +263 -0
  152. sempy_labs/eventstream/_topology.py +652 -0
  153. sempy_labs/graph/__init__.py +59 -0
  154. sempy_labs/graph/_groups.py +651 -0
  155. sempy_labs/graph/_sensitivity_labels.py +120 -0
  156. sempy_labs/graph/_teams.py +125 -0
  157. sempy_labs/graph/_user_licenses.py +96 -0
  158. sempy_labs/graph/_users.py +516 -0
  159. sempy_labs/graph_model/__init__.py +15 -0
  160. sempy_labs/graph_model/_background_jobs.py +63 -0
  161. sempy_labs/graph_model/_items.py +149 -0
  162. sempy_labs/lakehouse/__init__.py +67 -0
  163. sempy_labs/lakehouse/_blobs.py +247 -0
  164. sempy_labs/lakehouse/_get_lakehouse_columns.py +102 -0
  165. sempy_labs/lakehouse/_get_lakehouse_tables.py +274 -0
  166. sempy_labs/lakehouse/_helper.py +250 -0
  167. sempy_labs/lakehouse/_lakehouse.py +351 -0
  168. sempy_labs/lakehouse/_livy_sessions.py +143 -0
  169. sempy_labs/lakehouse/_materialized_lake_views.py +157 -0
  170. sempy_labs/lakehouse/_partitioning.py +165 -0
  171. sempy_labs/lakehouse/_schemas.py +217 -0
  172. sempy_labs/lakehouse/_shortcuts.py +440 -0
  173. sempy_labs/migration/__init__.py +35 -0
  174. sempy_labs/migration/_create_pqt_file.py +238 -0
  175. sempy_labs/migration/_direct_lake_to_import.py +105 -0
  176. sempy_labs/migration/_migrate_calctables_to_lakehouse.py +398 -0
  177. sempy_labs/migration/_migrate_calctables_to_semantic_model.py +148 -0
  178. sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +533 -0
  179. sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +172 -0
  180. sempy_labs/migration/_migration_validation.py +71 -0
  181. sempy_labs/migration/_refresh_calc_tables.py +131 -0
  182. sempy_labs/mirrored_azure_databricks_catalog/__init__.py +15 -0
  183. sempy_labs/mirrored_azure_databricks_catalog/_discover.py +213 -0
  184. sempy_labs/mirrored_azure_databricks_catalog/_refresh_catalog_metadata.py +45 -0
  185. sempy_labs/ml_model/__init__.py +23 -0
  186. sempy_labs/ml_model/_functions.py +427 -0
  187. sempy_labs/report/_BPAReportTemplate.json +232 -0
  188. sempy_labs/report/__init__.py +55 -0
  189. sempy_labs/report/_bpareporttemplate/.pbi/localSettings.json +9 -0
  190. sempy_labs/report/_bpareporttemplate/.platform +11 -0
  191. sempy_labs/report/_bpareporttemplate/StaticResources/SharedResources/BaseThemes/CY24SU06.json +710 -0
  192. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/page.json +11 -0
  193. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/1b08bce3bebabb0a27a8/visual.json +191 -0
  194. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/2f22ddb70c301693c165/visual.json +438 -0
  195. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/3b1182230aa6c600b43a/visual.json +127 -0
  196. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/58577ba6380c69891500/visual.json +576 -0
  197. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/a2a8fa5028b3b776c96c/visual.json +207 -0
  198. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/adfd47ef30652707b987/visual.json +506 -0
  199. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/b6a80ee459e716e170b1/visual.json +127 -0
  200. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/ce3130a721c020cc3d81/visual.json +513 -0
  201. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/page.json +8 -0
  202. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/visuals/66e60dfb526437cd78d1/visual.json +112 -0
  203. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/page.json +11 -0
  204. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/07deb8bce824e1be37d7/visual.json +513 -0
  205. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0b1c68838818b32ad03b/visual.json +352 -0
  206. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0c171de9d2683d10b930/visual.json +37 -0
  207. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0efa01be0510e40a645e/visual.json +542 -0
  208. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/6bf2f0eb830ab53cc668/visual.json +221 -0
  209. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/88d8141cb8500b60030c/visual.json +127 -0
  210. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/a753273590beed656a03/visual.json +576 -0
  211. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/b8fdc82cddd61ac447bc/visual.json +127 -0
  212. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/page.json +9 -0
  213. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/visuals/ce8532a7e25020271077/visual.json +38 -0
  214. sempy_labs/report/_bpareporttemplate/definition/pages/pages.json +10 -0
  215. sempy_labs/report/_bpareporttemplate/definition/report.json +176 -0
  216. sempy_labs/report/_bpareporttemplate/definition/version.json +4 -0
  217. sempy_labs/report/_bpareporttemplate/definition.pbir +14 -0
  218. sempy_labs/report/_download_report.py +76 -0
  219. sempy_labs/report/_export_report.py +257 -0
  220. sempy_labs/report/_generate_report.py +427 -0
  221. sempy_labs/report/_paginated.py +76 -0
  222. sempy_labs/report/_report_bpa.py +354 -0
  223. sempy_labs/report/_report_bpa_rules.py +115 -0
  224. sempy_labs/report/_report_functions.py +581 -0
  225. sempy_labs/report/_report_helper.py +227 -0
  226. sempy_labs/report/_report_list_functions.py +110 -0
  227. sempy_labs/report/_report_rebind.py +149 -0
  228. sempy_labs/report/_reportwrapper.py +3100 -0
  229. sempy_labs/report/_save_report.py +147 -0
  230. sempy_labs/snowflake_database/__init__.py +10 -0
  231. sempy_labs/snowflake_database/_items.py +105 -0
  232. sempy_labs/sql_database/__init__.py +21 -0
  233. sempy_labs/sql_database/_items.py +201 -0
  234. sempy_labs/sql_database/_mirroring.py +79 -0
  235. sempy_labs/theme/__init__.py +12 -0
  236. sempy_labs/theme/_org_themes.py +129 -0
  237. sempy_labs/tom/__init__.py +3 -0
  238. sempy_labs/tom/_model.py +5977 -0
  239. sempy_labs/variable_library/__init__.py +19 -0
  240. sempy_labs/variable_library/_functions.py +403 -0
  241. sempy_labs/warehouse/__init__.py +28 -0
  242. sempy_labs/warehouse/_items.py +234 -0
  243. 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