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
|
@@ -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)
|