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
@@ -0,0 +1,378 @@
1
+ import sempy.fabric as fabric
2
+ import pandas as pd
3
+ import datetime
4
+ from sempy_labs._helper_functions import (
5
+ save_as_delta_table,
6
+ resolve_workspace_capacity,
7
+ retry,
8
+ _get_column_aggregate,
9
+ resolve_workspace_id,
10
+ resolve_lakehouse_name_and_id,
11
+ )
12
+ from sempy_labs.lakehouse import (
13
+ get_lakehouse_tables,
14
+ lakehouse_attached,
15
+ )
16
+ from sempy_labs._model_bpa import run_model_bpa
17
+ from typing import Optional, List
18
+ from sempy._utils._log import log
19
+ import sempy_labs._icons as icons
20
+ from uuid import UUID
21
+
22
+
23
+ @log
24
+ def run_model_bpa_bulk(
25
+ rules: Optional[pd.DataFrame] = None,
26
+ extended: bool = False,
27
+ language: Optional[str] = None,
28
+ workspace: Optional[str | UUID | List[str | UUID]] = None,
29
+ skip_models: Optional[str | List[str]] = ["ModelBPA", "Fabric Capacity Metrics"],
30
+ skip_models_in_workspace: Optional[dict] = None,
31
+ ):
32
+ """
33
+ Runs the semantic model Best Practice Analyzer across all semantic models in a workspace (or all accessible workspaces).
34
+ Saves (appends) the results to the 'modelbparesults' delta table in the lakehouse attached to the notebook.
35
+ Default semantic models are skipped in this analysis.
36
+
37
+ Parameters
38
+ ----------
39
+ rules : pandas.DataFrame, default=None
40
+ A pandas dataframe containing rules to be evaluated. Based on the format of the dataframe produced by the model_bpa_rules function.
41
+ extended : bool, default=False
42
+ If True, runs the set_vertipaq_annotations function to collect Vertipaq Analyzer statistics to be used in the analysis of the semantic model.
43
+ language : str, default=None
44
+ The language (code) in which the rules will appear. For example, specifying 'it-IT' will show the Rule Name, Category and Description in Italian.
45
+ Defaults to None which resolves to English.
46
+ workspace : str | uuid.UUID | List[str | uuid.UUID], default=None
47
+ The workspace or list of workspaces to scan. Supports both the workspace name and the workspace id.
48
+ Defaults to None which scans all accessible workspaces.
49
+ skip_models : str | List[str], default=['ModelBPA', 'Fabric Capacity Metrics']
50
+ The semantic models to always skip when running this analysis.
51
+ skip_models_in_workspace : dict, default=None
52
+ A dictionary showing specific semantic models within specific workspaces to skip. See the example below:
53
+ {
54
+ "Workspace A": ["Dataset1", "Dataset2"],
55
+ "Workspace B": ["Dataset5", "Dataset 8"],
56
+ }
57
+ """
58
+ from sempy_labs.lakehouse._schemas import is_schema_enabled
59
+
60
+ if not lakehouse_attached():
61
+ raise ValueError(
62
+ f"{icons.red_dot} No lakehouse is attached to this notebook. Must attach a lakehouse to the notebook."
63
+ )
64
+
65
+ if isinstance(skip_models, str):
66
+ skip_models = [skip_models]
67
+
68
+ skip_models.extend(["ModelBPA", "Fabric Capacity Metrics"])
69
+
70
+ now = datetime.datetime.now()
71
+ schema_enabled = is_schema_enabled()
72
+ output_table = "dbo/modelbparesults" if schema_enabled else "modelbparesults"
73
+
74
+ lakeT = get_lakehouse_tables()
75
+ lakeT_filt = lakeT[lakeT["Table Name"] == output_table]
76
+ if lakeT_filt.empty:
77
+ runId = 1
78
+ else:
79
+ max_run_id = _get_column_aggregate(table_name=output_table)
80
+ runId = max_run_id + 1
81
+
82
+ if isinstance(workspace, str):
83
+ workspace = [workspace]
84
+
85
+ dfW = fabric.list_workspaces("type ne 'AdminInsights'")
86
+ if workspace is None:
87
+ dfW_filt = dfW.copy()
88
+ else:
89
+ dfW_filt = dfW[(dfW["Name"].isin(workspace)) | (dfW["Id"].isin(workspace))]
90
+
91
+ if dfW_filt.empty:
92
+ raise ValueError(
93
+ f"{icons.red_dot} There are no valid workspaces to assess. This is likely due to not having proper permissions to the workspace(s) entered in the 'workspace' parameter."
94
+ )
95
+
96
+ for _, r in dfW_filt.iterrows():
97
+ wksp = r["Name"]
98
+ wksp_id = r["Id"]
99
+ capacity_id, capacity_name = resolve_workspace_capacity(workspace=wksp)
100
+ df = pd.DataFrame(columns=list(icons.bpa_schema.keys()))
101
+ dfD = fabric.list_datasets(workspace=wksp, mode="rest")
102
+
103
+ # Skip models in workspace
104
+ if skip_models_in_workspace is not None and isinstance(
105
+ skip_models_in_workspace, dict
106
+ ):
107
+ skip_models_wkspc = skip_models_in_workspace.get(wksp)
108
+ dfD = dfD[~dfD["Dataset Name"].isin(skip_models_wkspc)]
109
+
110
+ # Exclude default semantic models
111
+ if not dfD.empty:
112
+ dfI = fabric.list_items(workspace=wksp)
113
+ filtered_df = dfI.groupby("Display Name").filter(
114
+ lambda x: set(["Warehouse", "SemanticModel"]).issubset(set(x["Type"]))
115
+ or set(["Lakehouse", "SemanticModel"]).issubset(set(x["Type"]))
116
+ )
117
+ default_semantic_models = filtered_df["Display Name"].unique().tolist()
118
+ skip_models.extend(default_semantic_models)
119
+ dfD_filt = dfD[~dfD["Dataset Name"].isin(skip_models)]
120
+
121
+ if not dfD_filt.empty:
122
+ for _, r2 in dfD_filt.iterrows():
123
+ dataset_id = r2["Dataset Id"]
124
+ dataset_name = r2["Dataset Name"]
125
+ config_by = r2["Configured By"]
126
+ print(
127
+ f"{icons.in_progress} Collecting Model BPA stats for the '{dataset_name}' semantic model within the '{wksp}' workspace."
128
+ )
129
+ try:
130
+ bpa_df = run_model_bpa(
131
+ dataset=dataset_id,
132
+ workspace=wksp,
133
+ language=language,
134
+ return_dataframe=True,
135
+ rules=rules,
136
+ extended=extended,
137
+ )
138
+ bpa_df["Capacity Name"] = capacity_name
139
+ bpa_df["Capacity Id"] = capacity_id
140
+ bpa_df["Workspace Name"] = wksp
141
+ bpa_df["Workspace Id"] = wksp_id
142
+ bpa_df["Dataset Name"] = dataset_name
143
+ bpa_df["Dataset Id"] = dataset_id
144
+ bpa_df["Configured By"] = config_by
145
+ bpa_df["Timestamp"] = now
146
+ bpa_df["RunId"] = runId
147
+ bpa_df = bpa_df[list(icons.bpa_schema.keys())]
148
+
149
+ bpa_df["RunId"] = bpa_df["RunId"].astype("int")
150
+
151
+ if df.empty:
152
+ df = bpa_df
153
+ elif not bpa_df.empty:
154
+ df = pd.concat([df, bpa_df], ignore_index=True)
155
+ print(
156
+ f"{icons.green_dot} Collected Model BPA stats for the '{dataset_name}' semantic model within the '{wksp}' workspace."
157
+ )
158
+ except Exception as e:
159
+ print(
160
+ f"{icons.red_dot} Model BPA failed for the '{dataset_name}' semantic model within the '{wksp}' workspace."
161
+ )
162
+ print(e)
163
+
164
+ if df.empty:
165
+ print(
166
+ f"{icons.yellow_dot} No BPA results to save for the '{wksp}' workspace."
167
+ )
168
+ else:
169
+ df["Severity"].replace(icons.severity_mapping, inplace=True)
170
+
171
+ # Append save results individually for each workspace (so as not to create a giant dataframe)
172
+ print(
173
+ f"{icons.in_progress} Saving the Model BPA results of the '{wksp}' workspace to the '{output_table}' within the lakehouse attached to this notebook..."
174
+ )
175
+
176
+ schema = {
177
+ key.replace(" ", "_"): value
178
+ for key, value in icons.bpa_schema.items()
179
+ }
180
+
181
+ save_as_delta_table(
182
+ dataframe=df,
183
+ delta_table_name=output_table,
184
+ write_mode="append",
185
+ schema=schema,
186
+ merge_schema=True,
187
+ )
188
+ print(
189
+ f"{icons.green_dot} Saved BPA results to the '{output_table}' delta table."
190
+ )
191
+
192
+ print(f"{icons.green_dot} Bulk BPA scan complete.")
193
+
194
+
195
+ @log
196
+ def create_model_bpa_semantic_model(
197
+ dataset: Optional[str] = icons.model_bpa_name,
198
+ lakehouse: Optional[str | UUID] = None,
199
+ lakehouse_workspace: Optional[str | UUID] = None,
200
+ ):
201
+ """
202
+ Dynamically generates a Direct Lake semantic model based on the 'modelbparesults' delta table which contains the Best Practice Analyzer results.
203
+ This semantic model used in combination with the corresponding Best Practice Analyzer report can be used to analyze multiple semantic models
204
+ on multiple workspaces at once (and over time).
205
+
206
+ The semantic model is always created within the same workspace as the lakehouse.
207
+
208
+ Parameters
209
+ ----------
210
+ dataset : str, default='ModelBPA'
211
+ Name of the semantic model to be created.
212
+ lakehouse : str | uuid.UUID, default=None
213
+ Name of the Fabric lakehouse which contains the 'modelbparesults' delta table.
214
+ Defaults to None which resolves to the default lakehouse attached to the notebook.
215
+ lakehouse_workspace : str | uuid.UUID, default=None
216
+ The workspace in which the lakehouse resides.
217
+ Defaults to None which resolves to the workspace of the attached lakehouse
218
+ or if no lakehouse attached, resolves to the workspace of the notebook.
219
+ """
220
+
221
+ from sempy_labs.directlake import (
222
+ generate_shared_expression,
223
+ add_table_to_direct_lake_semantic_model,
224
+ )
225
+ from sempy_labs import create_blank_semantic_model, refresh_semantic_model
226
+ from sempy_labs.tom import connect_semantic_model
227
+
228
+ lakehouse_workspace_id = resolve_workspace_id(workspace=lakehouse_workspace)
229
+ (lakehouse_id, lakehouse_name) = resolve_lakehouse_name_and_id(
230
+ lakehouse=lakehouse, workspace=lakehouse_workspace_id
231
+ )
232
+
233
+ # Generate the shared expression based on the lakehouse and lakehouse workspace
234
+ expr = generate_shared_expression(
235
+ item_name=lakehouse_name,
236
+ item_type="Lakehouse",
237
+ workspace=lakehouse_workspace_id,
238
+ )
239
+
240
+ # Create blank model
241
+ create_blank_semantic_model(
242
+ dataset=dataset, workspace=lakehouse_workspace_id, overwrite=True
243
+ )
244
+
245
+ @retry(
246
+ sleep_time=1,
247
+ timeout_error_message=f"{icons.red_dot} Function timed out after 1 minute",
248
+ )
249
+ def dyn_connect():
250
+ with connect_semantic_model(
251
+ dataset=dataset, readonly=True, workspace=lakehouse_workspace_id
252
+ ) as tom:
253
+
254
+ tom.model
255
+
256
+ dyn_connect()
257
+ icons.sll_tags.append("ModelBPABulk")
258
+ table_exists = False
259
+ with connect_semantic_model(
260
+ dataset=dataset, readonly=False, workspace=lakehouse_workspace_id
261
+ ) as tom:
262
+ t_name = "BPAResults"
263
+ t_name_full = f"'{t_name}'"
264
+ # Create the shared expression
265
+ if not any(e.Name == "DatabaseQuery" for e in tom.model.Expressions):
266
+ tom.add_expression(name="DatabaseQuery", expression=expr)
267
+ # Add the table to the model
268
+ if any(t.Name == t_name for t in tom.model.Tables):
269
+ table_exists = True
270
+ if not table_exists:
271
+ add_table_to_direct_lake_semantic_model(
272
+ dataset=dataset,
273
+ table_name=t_name,
274
+ lakehouse_table_name="modelbparesults",
275
+ workspace=lakehouse_workspace_id,
276
+ refresh=False,
277
+ )
278
+ with connect_semantic_model(
279
+ dataset=dataset, readonly=False, workspace=lakehouse_workspace_id
280
+ ) as tom:
281
+ # Fix column names
282
+ for c in tom.all_columns():
283
+ if c.Name == "Dataset_Name":
284
+ c.Name = "Model"
285
+ elif c.Name == "Dataset_Id":
286
+ c.Name = "Model Id"
287
+ elif c.Name == "Workspace_Name":
288
+ c.Name = "Workspace"
289
+ elif c.Name == "Capacity_Name":
290
+ c.Name = "Capacity"
291
+ elif c.Name == "Configured_By":
292
+ c.Name = "Model Owner"
293
+ elif c.Name == "URL":
294
+ c.DataCategory = "WebURL"
295
+ elif c.Name == "RunId":
296
+ tom.set_summarize_by(
297
+ table_name=c.Parent.Name, column_name=c.Name, value="None"
298
+ )
299
+ c.Name = c.Name.replace("_", " ")
300
+
301
+ # Implement pattern for base measures
302
+ def get_expr(table_name, calculation):
303
+ return f"IF(HASONEFILTER({table_name}[RunId]),{calculation},CALCULATE({calculation},FILTER(VALUES({table_name}[RunId]),{table_name}[RunId] = [Max Run Id])))"
304
+
305
+ # Add measures
306
+ int_format = "#,0"
307
+ m_name = "Max Run Id"
308
+ if not any(m.Name == m_name for m in tom.all_measures()):
309
+ tom.add_measure(
310
+ table_name=t_name,
311
+ measure_name=m_name,
312
+ expression=f"CALCULATE(MAX({t_name_full}[RunId]),{t_name_full}[RunId])",
313
+ format_string=int_format,
314
+ )
315
+ m_name = "Capacities"
316
+ if not any(m.Name == m_name for m in tom.all_measures()):
317
+ calc = f"COUNTROWS(DISTINCT({t_name_full}[Capacity]))"
318
+ tom.add_measure(
319
+ table_name=t_name,
320
+ measure_name=m_name,
321
+ expression=get_expr(t_name_full, calc),
322
+ format_string=int_format,
323
+ )
324
+ m_name = "Models"
325
+ if not any(m.Name == m_name for m in tom.all_measures()):
326
+ calc = f"COUNTROWS(DISTINCT({t_name_full}[Model]))"
327
+ tom.add_measure(
328
+ table_name=t_name,
329
+ measure_name=m_name,
330
+ expression=get_expr(t_name_full, calc),
331
+ format_string=int_format,
332
+ )
333
+ m_name = "Workspaces"
334
+ if not any(m.Name == m_name for m in tom.all_measures()):
335
+ calc = f"COUNTROWS(DISTINCT({t_name_full}[Workspace]))"
336
+ tom.add_measure(
337
+ table_name=t_name,
338
+ measure_name=m_name,
339
+ expression=get_expr(t_name_full, calc),
340
+ format_string=int_format,
341
+ )
342
+ m_name = "Violations"
343
+ if not any(m.Name == m_name for m in tom.all_measures()):
344
+ calc = f"COUNTROWS({t_name_full})"
345
+ tom.add_measure(
346
+ table_name=t_name,
347
+ measure_name=m_name,
348
+ expression=get_expr(t_name_full, calc),
349
+ format_string=int_format,
350
+ )
351
+ m_name = "Error Violations"
352
+ if not any(m.Name == m_name for m in tom.all_measures()):
353
+ tom.add_measure(
354
+ table_name=t_name,
355
+ measure_name=m_name,
356
+ expression=f'CALCULATE([Violations],{t_name_full}[Severity]="Error")',
357
+ format_string=int_format,
358
+ )
359
+ m_name = "Rules Violated"
360
+ if not any(m.Name == m_name for m in tom.all_measures()):
361
+ calc = f"COUNTROWS(DISTINCT({t_name_full}[Rule Name]))"
362
+ tom.add_measure(
363
+ table_name=t_name,
364
+ measure_name=m_name,
365
+ expression=get_expr(t_name_full, calc),
366
+ format_string=int_format,
367
+ )
368
+ m_name = "Rule Severity"
369
+ if not any(m.Name == m_name for m in tom.all_measures()):
370
+ tom.add_measure(
371
+ table_name=t_name,
372
+ measure_name=m_name,
373
+ expression=f"IF(ISFILTERED({t_name_full}[Rule Name]),IF( HASONEVALUE({t_name_full}[Rule Name]),MIN({t_name_full}[Severity])))",
374
+ )
375
+ # tom.add_measure(table_name=t_name, measure_name='Rules Followed', expression="[Rules] - [Rules Violated]")
376
+
377
+ # Refresh the model
378
+ refresh_semantic_model(dataset=dataset, workspace=lakehouse_workspace_id)