semantic-link-labs 0.8.3__py3-none-any.whl → 0.8.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of semantic-link-labs might be problematic. Click here for more details.
- {semantic_link_labs-0.8.3.dist-info → semantic_link_labs-0.8.5.dist-info}/METADATA +38 -8
- {semantic_link_labs-0.8.3.dist-info → semantic_link_labs-0.8.5.dist-info}/RECORD +109 -104
- {semantic_link_labs-0.8.3.dist-info → semantic_link_labs-0.8.5.dist-info}/WHEEL +1 -1
- sempy_labs/__init__.py +53 -1
- sempy_labs/_bpa_translation/_model/_translations_am-ET.po +24 -5
- sempy_labs/_bpa_translation/_model/_translations_ar-AE.po +28 -4
- sempy_labs/_bpa_translation/_model/_translations_bg-BG.po +34 -4
- sempy_labs/_bpa_translation/_model/_translations_ca-ES.po +33 -4
- sempy_labs/_bpa_translation/_model/_translations_cs-CZ.po +31 -4
- sempy_labs/_bpa_translation/_model/_translations_da-DK.po +31 -4
- sempy_labs/_bpa_translation/_model/_translations_de-DE.po +34 -4
- sempy_labs/_bpa_translation/_model/_translations_el-GR.po +36 -4
- sempy_labs/_bpa_translation/_model/_translations_es-ES.po +90 -58
- sempy_labs/_bpa_translation/_model/_translations_fa-IR.po +31 -5
- sempy_labs/_bpa_translation/_model/_translations_fi-FI.po +31 -4
- sempy_labs/_bpa_translation/_model/_translations_fr-FR.po +34 -4
- sempy_labs/_bpa_translation/_model/_translations_ga-IE.po +34 -4
- sempy_labs/_bpa_translation/_model/_translations_he-IL.po +28 -4
- sempy_labs/_bpa_translation/_model/_translations_hi-IN.po +32 -4
- sempy_labs/_bpa_translation/_model/_translations_hu-HU.po +32 -4
- sempy_labs/_bpa_translation/_model/_translations_id-ID.po +32 -4
- sempy_labs/_bpa_translation/_model/_translations_is-IS.po +31 -4
- sempy_labs/_bpa_translation/_model/_translations_it-IT.po +34 -4
- sempy_labs/_bpa_translation/_model/_translations_ja-JP.po +24 -4
- sempy_labs/_bpa_translation/_model/_translations_ko-KR.po +72 -56
- sempy_labs/_bpa_translation/_model/_translations_mt-MT.po +34 -4
- sempy_labs/_bpa_translation/_model/_translations_nl-NL.po +34 -4
- sempy_labs/_bpa_translation/_model/_translations_pl-PL.po +95 -71
- sempy_labs/_bpa_translation/_model/_translations_pt-BR.po +32 -4
- sempy_labs/_bpa_translation/_model/_translations_pt-PT.po +32 -4
- sempy_labs/_bpa_translation/_model/_translations_ro-RO.po +33 -4
- sempy_labs/_bpa_translation/_model/_translations_ru-RU.po +34 -4
- sempy_labs/_bpa_translation/_model/_translations_sk-SK.po +31 -4
- sempy_labs/_bpa_translation/_model/_translations_sl-SL.po +32 -4
- sempy_labs/_bpa_translation/_model/_translations_sv-SE.po +32 -4
- sempy_labs/_bpa_translation/_model/_translations_ta-IN.po +32 -4
- sempy_labs/_bpa_translation/_model/_translations_te-IN.po +31 -4
- sempy_labs/_bpa_translation/_model/_translations_th-TH.po +31 -4
- sempy_labs/_bpa_translation/_model/_translations_tr-TR.po +32 -4
- sempy_labs/_bpa_translation/_model/_translations_uk-UA.po +100 -72
- sempy_labs/_bpa_translation/_model/_translations_zh-CN.po +23 -5
- sempy_labs/_bpa_translation/_model/_translations_zu-ZA.po +32 -4
- sempy_labs/_capacities.py +49 -14
- sempy_labs/_capacity_migration.py +1 -7
- sempy_labs/_data_pipelines.py +6 -0
- sempy_labs/_dataflows.py +118 -1
- sempy_labs/_dax.py +189 -3
- sempy_labs/_deployment_pipelines.py +13 -7
- sempy_labs/_environments.py +6 -0
- sempy_labs/_eventhouses.py +6 -0
- sempy_labs/_eventstreams.py +6 -0
- sempy_labs/_external_data_shares.py +6 -4
- sempy_labs/_generate_semantic_model.py +26 -3
- sempy_labs/_git.py +14 -14
- sempy_labs/_helper_functions.py +197 -1
- sempy_labs/_icons.py +55 -22
- sempy_labs/_kql_databases.py +6 -0
- sempy_labs/_kql_querysets.py +6 -0
- sempy_labs/_list_functions.py +1 -1
- sempy_labs/_managed_private_endpoints.py +166 -0
- sempy_labs/_mirrored_databases.py +428 -0
- sempy_labs/_mirrored_warehouses.py +2 -0
- sempy_labs/_ml_experiments.py +6 -0
- sempy_labs/_ml_models.py +7 -1
- sempy_labs/_model_bpa.py +215 -181
- sempy_labs/_model_bpa_bulk.py +46 -42
- sempy_labs/_model_bpa_rules.py +8 -3
- sempy_labs/_model_dependencies.py +41 -87
- sempy_labs/_notebooks.py +107 -12
- sempy_labs/_query_scale_out.py +8 -6
- sempy_labs/_refresh_semantic_model.py +299 -49
- sempy_labs/_spark.py +12 -5
- sempy_labs/_translations.py +2 -0
- sempy_labs/_vertipaq.py +89 -86
- sempy_labs/_warehouses.py +79 -0
- sempy_labs/_workloads.py +128 -0
- sempy_labs/_workspace_identity.py +4 -4
- sempy_labs/_workspaces.py +14 -1
- sempy_labs/admin/__init__.py +2 -0
- sempy_labs/admin/_basic_functions.py +131 -43
- sempy_labs/admin/_domains.py +18 -18
- sempy_labs/directlake/__init__.py +2 -0
- sempy_labs/directlake/_directlake_schema_sync.py +2 -1
- sempy_labs/directlake/_dl_helper.py +4 -1
- sempy_labs/directlake/_get_shared_expression.py +7 -1
- sempy_labs/directlake/_guardrails.py +2 -1
- sempy_labs/directlake/_show_unsupported_directlake_objects.py +1 -7
- sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +78 -0
- sempy_labs/directlake/_update_directlake_partition_entity.py +13 -32
- sempy_labs/directlake/_warm_cache.py +10 -9
- sempy_labs/lakehouse/_get_lakehouse_tables.py +6 -2
- sempy_labs/lakehouse/_shortcuts.py +4 -0
- sempy_labs/migration/_create_pqt_file.py +5 -2
- sempy_labs/migration/_migrate_calctables_to_lakehouse.py +3 -2
- sempy_labs/migration/_migrate_calctables_to_semantic_model.py +2 -0
- sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +2 -8
- sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +17 -0
- sempy_labs/migration/_migration_validation.py +2 -0
- sempy_labs/migration/_refresh_calc_tables.py +1 -0
- sempy_labs/report/__init__.py +6 -1
- sempy_labs/report/_download_report.py +75 -0
- sempy_labs/report/_generate_report.py +6 -0
- sempy_labs/report/_paginated.py +74 -0
- sempy_labs/report/_report_functions.py +6 -0
- sempy_labs/report/_report_rebind.py +2 -0
- sempy_labs/report/_reportwrapper.py +4 -2
- sempy_labs/tom/_model.py +135 -68
- {semantic_link_labs-0.8.3.dist-info → semantic_link_labs-0.8.5.dist-info}/LICENSE +0 -0
- {semantic_link_labs-0.8.3.dist-info → semantic_link_labs-0.8.5.dist-info}/top_level.txt +0 -0
sempy_labs/_model_bpa.py
CHANGED
|
@@ -56,7 +56,7 @@ def run_model_bpa(
|
|
|
56
56
|
extended : bool, default=False
|
|
57
57
|
If True, runs the set_vertipaq_annotations function to collect Vertipaq Analyzer statistics to be used in the analysis of the semantic model.
|
|
58
58
|
language : str, default=None
|
|
59
|
-
Specifying a language code (i.e. 'it-IT' for Italian) will auto-translate the Category, Rule Name and Description into the specified language.
|
|
59
|
+
Specifying a language name or code (i.e. 'it-IT' for Italian) will auto-translate the Category, Rule Name and Description into the specified language.
|
|
60
60
|
Defaults to None which resolves to English.
|
|
61
61
|
|
|
62
62
|
Returns
|
|
@@ -113,6 +113,7 @@ def run_model_bpa(
|
|
|
113
113
|
)
|
|
114
114
|
|
|
115
115
|
if extended:
|
|
116
|
+
icons.sll_tags.append("ModelBPAExtended")
|
|
116
117
|
with connect_semantic_model(
|
|
117
118
|
dataset=dataset, workspace=workspace, readonly=False
|
|
118
119
|
) as tom:
|
|
@@ -122,210 +123,239 @@ def run_model_bpa(
|
|
|
122
123
|
dataset=dataset, workspace=workspace, readonly=True
|
|
123
124
|
) as tom:
|
|
124
125
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
translation_file = (
|
|
130
|
-
f"{current_dir}/_bpa_translation/_model/_translations_{language}.po"
|
|
126
|
+
# Do not run BPA for models with no tables
|
|
127
|
+
if tom.model.Tables.Count == 0:
|
|
128
|
+
print(
|
|
129
|
+
f"{icons.warning} The '{dataset}' semantic model within the '{workspace}' workspace has no tables and therefore there are no valid BPA results."
|
|
131
130
|
)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
finalDF = pd.DataFrame(
|
|
132
|
+
columns=[
|
|
133
|
+
"Category",
|
|
134
|
+
"Rule Name",
|
|
135
|
+
"Severity",
|
|
136
|
+
"Object Type",
|
|
137
|
+
"Object Name",
|
|
138
|
+
"Description",
|
|
139
|
+
"URL",
|
|
140
|
+
]
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
dep = get_model_calc_dependencies(dataset=dataset, workspace=workspace)
|
|
139
144
|
|
|
140
|
-
|
|
145
|
+
def translate_using_po(rule_file):
|
|
146
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
147
|
+
translation_file = (
|
|
148
|
+
f"{current_dir}/_bpa_translation/_model/_translations_{language}.po"
|
|
149
|
+
)
|
|
150
|
+
for c in ["Category", "Description", "Rule Name"]:
|
|
151
|
+
po = polib.pofile(translation_file)
|
|
152
|
+
for entry in po:
|
|
153
|
+
if entry.tcomment == c.lower().replace(" ", "_"):
|
|
154
|
+
rule_file.loc[rule_file["Rule Name"] == entry.msgid, c] = (
|
|
155
|
+
entry.msgstr
|
|
156
|
+
)
|
|
141
157
|
|
|
142
|
-
|
|
143
|
-
if language is not None and rules is None and language in language_list:
|
|
144
|
-
rules = model_bpa_rules(dependencies=dep)
|
|
145
|
-
translate_using_po(rules)
|
|
146
|
-
translated = True
|
|
147
|
-
if rules is None:
|
|
148
|
-
rules = model_bpa_rules(dependencies=dep)
|
|
149
|
-
if language is not None and not translated:
|
|
158
|
+
translated = False
|
|
150
159
|
|
|
151
|
-
|
|
160
|
+
# Translations
|
|
161
|
+
if language is not None and rules is None and language in language_list:
|
|
162
|
+
rules = model_bpa_rules(dependencies=dep)
|
|
163
|
+
translate_using_po(rules)
|
|
164
|
+
translated = True
|
|
165
|
+
if rules is None:
|
|
166
|
+
rules = model_bpa_rules(dependencies=dep)
|
|
167
|
+
if language is not None and not translated:
|
|
152
168
|
|
|
153
|
-
|
|
154
|
-
from pyspark.sql import SparkSession
|
|
169
|
+
def translate_using_spark(rule_file):
|
|
155
170
|
|
|
156
|
-
|
|
157
|
-
|
|
171
|
+
from synapse.ml.services import Translate
|
|
172
|
+
from pyspark.sql import SparkSession
|
|
158
173
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
StructField("Rule Name", StringType(), True),
|
|
164
|
-
StructField("Description", StringType(), True),
|
|
165
|
-
]
|
|
166
|
-
)
|
|
174
|
+
rules_temp = rule_file.copy()
|
|
175
|
+
rules_temp = rules_temp.drop(
|
|
176
|
+
["Expression", "URL", "Severity"], axis=1
|
|
177
|
+
)
|
|
167
178
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
.setTextCol(clm)
|
|
176
|
-
.setToLanguage(language)
|
|
177
|
-
.setOutputCol("translation")
|
|
178
|
-
.setConcurrency(5)
|
|
179
|
+
schema = StructType(
|
|
180
|
+
[
|
|
181
|
+
StructField("Category", StringType(), True),
|
|
182
|
+
StructField("Scope", StringType(), True),
|
|
183
|
+
StructField("Rule Name", StringType(), True),
|
|
184
|
+
StructField("Description", StringType(), True),
|
|
185
|
+
]
|
|
179
186
|
)
|
|
180
187
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
.
|
|
188
|
+
spark = SparkSession.builder.getOrCreate()
|
|
189
|
+
dfRules = spark.createDataFrame(rules_temp, schema)
|
|
190
|
+
|
|
191
|
+
columns = ["Category", "Rule Name", "Description"]
|
|
192
|
+
for clm in columns:
|
|
193
|
+
translate = (
|
|
194
|
+
Translate()
|
|
195
|
+
.setTextCol(clm)
|
|
196
|
+
.setToLanguage(language)
|
|
197
|
+
.setOutputCol("translation")
|
|
198
|
+
.setConcurrency(5)
|
|
189
199
|
)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
200
|
+
|
|
201
|
+
if clm == "Rule Name":
|
|
202
|
+
transDF = (
|
|
203
|
+
translate.transform(dfRules)
|
|
204
|
+
.withColumn(
|
|
205
|
+
"translation",
|
|
206
|
+
flatten(col("translation.translations")),
|
|
207
|
+
)
|
|
208
|
+
.withColumn("translation", col("translation.text"))
|
|
209
|
+
.select(clm, "translation")
|
|
210
|
+
)
|
|
211
|
+
else:
|
|
212
|
+
transDF = (
|
|
213
|
+
translate.transform(dfRules)
|
|
214
|
+
.withColumn(
|
|
215
|
+
"translation",
|
|
216
|
+
flatten(col("translation.translations")),
|
|
217
|
+
)
|
|
218
|
+
.withColumn("translation", col("translation.text"))
|
|
219
|
+
.select("Rule Name", clm, "translation")
|
|
195
220
|
)
|
|
196
|
-
.withColumn("translation", col("translation.text"))
|
|
197
|
-
.select("Rule Name", clm, "translation")
|
|
198
|
-
)
|
|
199
221
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
222
|
+
df_panda = transDF.toPandas()
|
|
223
|
+
rule_file = pd.merge(
|
|
224
|
+
rule_file,
|
|
225
|
+
df_panda[["Rule Name", "translation"]],
|
|
226
|
+
on="Rule Name",
|
|
227
|
+
how="left",
|
|
228
|
+
)
|
|
207
229
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
230
|
+
rule_file = rule_file.rename(
|
|
231
|
+
columns={"translation": f"{clm}Translated"}
|
|
232
|
+
)
|
|
233
|
+
rule_file[f"{clm}Translated"] = rule_file[
|
|
234
|
+
f"{clm}Translated"
|
|
235
|
+
].apply(lambda x: x[0] if x is not None else None)
|
|
214
236
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
237
|
+
for clm in columns:
|
|
238
|
+
rule_file = rule_file.drop([clm], axis=1)
|
|
239
|
+
rule_file = rule_file.rename(columns={f"{clm}Translated": clm})
|
|
218
240
|
|
|
219
|
-
|
|
241
|
+
return rule_file
|
|
220
242
|
|
|
221
|
-
|
|
243
|
+
rules = translate_using_spark(rules)
|
|
222
244
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
245
|
+
rules.loc[rules["Severity"] == "Warning", "Severity"] = icons.warning
|
|
246
|
+
rules.loc[rules["Severity"] == "Error", "Severity"] = icons.error
|
|
247
|
+
rules.loc[rules["Severity"] == "Info", "Severity"] = icons.info
|
|
226
248
|
|
|
227
|
-
|
|
249
|
+
pd.set_option("display.max_colwidth", 1000)
|
|
228
250
|
|
|
229
|
-
|
|
251
|
+
violations = pd.DataFrame(columns=["Object Name", "Scope", "Rule Name"])
|
|
230
252
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
253
|
+
scope_to_dataframe = {
|
|
254
|
+
"Relationship": (
|
|
255
|
+
tom.model.Relationships,
|
|
256
|
+
lambda obj: create_relationship_name(
|
|
257
|
+
obj.FromTable.Name,
|
|
258
|
+
obj.FromColumn.Name,
|
|
259
|
+
obj.ToTable.Name,
|
|
260
|
+
obj.ToColumn.Name,
|
|
261
|
+
),
|
|
239
262
|
),
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
263
|
+
"Column": (
|
|
264
|
+
tom.all_columns(),
|
|
265
|
+
lambda obj: format_dax_object_name(obj.Parent.Name, obj.Name),
|
|
266
|
+
),
|
|
267
|
+
"Measure": (tom.all_measures(), lambda obj: obj.Name),
|
|
268
|
+
"Hierarchy": (
|
|
269
|
+
tom.all_hierarchies(),
|
|
270
|
+
lambda obj: format_dax_object_name(obj.Parent.Name, obj.Name),
|
|
271
|
+
),
|
|
272
|
+
"Table": (tom.model.Tables, lambda obj: obj.Name),
|
|
273
|
+
"Role": (tom.model.Roles, lambda obj: obj.Name),
|
|
274
|
+
"Model": (tom.model, lambda obj: obj.Model.Name),
|
|
275
|
+
"Calculation Item": (
|
|
276
|
+
tom.all_calculation_items(),
|
|
277
|
+
lambda obj: format_dax_object_name(obj.Parent.Table.Name, obj.Name),
|
|
278
|
+
),
|
|
279
|
+
"Row Level Security": (
|
|
280
|
+
tom.all_rls(),
|
|
281
|
+
lambda obj: format_dax_object_name(obj.Parent.Name, obj.Name),
|
|
282
|
+
),
|
|
283
|
+
"Partition": (
|
|
284
|
+
tom.all_partitions(),
|
|
285
|
+
lambda obj: format_dax_object_name(obj.Parent.Name, obj.Name),
|
|
286
|
+
),
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
for i, r in rules.iterrows():
|
|
290
|
+
ruleName = r["Rule Name"]
|
|
291
|
+
expr = r["Expression"]
|
|
292
|
+
scopes = r["Scope"]
|
|
293
|
+
|
|
294
|
+
if isinstance(scopes, str):
|
|
295
|
+
scopes = [scopes]
|
|
296
|
+
|
|
297
|
+
for scope in scopes:
|
|
298
|
+
func = scope_to_dataframe[scope][0]
|
|
299
|
+
nm = scope_to_dataframe[scope][1]
|
|
300
|
+
|
|
301
|
+
if scope == "Model":
|
|
302
|
+
x = []
|
|
303
|
+
if expr(func, tom):
|
|
304
|
+
x = ["Model"]
|
|
305
|
+
elif scope == "Measure":
|
|
306
|
+
x = [nm(obj) for obj in tom.all_measures() if expr(obj, tom)]
|
|
307
|
+
elif scope == "Column":
|
|
308
|
+
x = [nm(obj) for obj in tom.all_columns() if expr(obj, tom)]
|
|
309
|
+
elif scope == "Partition":
|
|
310
|
+
x = [nm(obj) for obj in tom.all_partitions() if expr(obj, tom)]
|
|
311
|
+
elif scope == "Hierarchy":
|
|
312
|
+
x = [nm(obj) for obj in tom.all_hierarchies() if expr(obj, tom)]
|
|
313
|
+
elif scope == "Table":
|
|
314
|
+
x = [nm(obj) for obj in tom.model.Tables if expr(obj, tom)]
|
|
315
|
+
elif scope == "Relationship":
|
|
316
|
+
x = [
|
|
317
|
+
nm(obj) for obj in tom.model.Relationships if expr(obj, tom)
|
|
318
|
+
]
|
|
319
|
+
elif scope == "Role":
|
|
320
|
+
x = [nm(obj) for obj in tom.model.Roles if expr(obj, tom)]
|
|
321
|
+
elif scope == "Row Level Security":
|
|
322
|
+
x = [nm(obj) for obj in tom.all_rls() if expr(obj, tom)]
|
|
323
|
+
elif scope == "Calculation Item":
|
|
324
|
+
x = [
|
|
325
|
+
nm(obj)
|
|
326
|
+
for obj in tom.all_calculation_items()
|
|
327
|
+
if expr(obj, tom)
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
if len(x) > 0:
|
|
331
|
+
new_data = {
|
|
332
|
+
"Object Name": x,
|
|
333
|
+
"Scope": scope,
|
|
334
|
+
"Rule Name": ruleName,
|
|
335
|
+
}
|
|
336
|
+
violations = pd.concat(
|
|
337
|
+
[violations, pd.DataFrame(new_data)], ignore_index=True
|
|
338
|
+
)
|
|
309
339
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
340
|
+
prepDF = pd.merge(
|
|
341
|
+
violations,
|
|
342
|
+
rules[["Rule Name", "Category", "Severity", "Description", "URL"]],
|
|
343
|
+
left_on="Rule Name",
|
|
344
|
+
right_on="Rule Name",
|
|
345
|
+
how="left",
|
|
346
|
+
)
|
|
347
|
+
prepDF.rename(columns={"Scope": "Object Type"}, inplace=True)
|
|
348
|
+
finalDF = prepDF[
|
|
349
|
+
[
|
|
350
|
+
"Category",
|
|
351
|
+
"Rule Name",
|
|
352
|
+
"Severity",
|
|
353
|
+
"Object Type",
|
|
354
|
+
"Object Name",
|
|
355
|
+
"Description",
|
|
356
|
+
"URL",
|
|
357
|
+
]
|
|
327
358
|
]
|
|
328
|
-
]
|
|
329
359
|
|
|
330
360
|
if export:
|
|
331
361
|
if not lakehouse_attached():
|
|
@@ -387,10 +417,14 @@ def run_model_bpa(
|
|
|
387
417
|
dfExport.insert(5, colName, dfExport.pop(colName))
|
|
388
418
|
|
|
389
419
|
dfExport.columns = dfExport.columns.str.replace(" ", "_")
|
|
420
|
+
schema = {
|
|
421
|
+
key.replace(" ", "_"): value for key, value in icons.bpa_schema.items()
|
|
422
|
+
}
|
|
390
423
|
save_as_delta_table(
|
|
391
424
|
dataframe=dfExport,
|
|
392
425
|
delta_table_name=delta_table_name,
|
|
393
426
|
write_mode="append",
|
|
427
|
+
schema=schema,
|
|
394
428
|
merge_schema=True,
|
|
395
429
|
)
|
|
396
430
|
|
sempy_labs/_model_bpa_bulk.py
CHANGED
|
@@ -25,6 +25,7 @@ def run_model_bpa_bulk(
|
|
|
25
25
|
language: Optional[str] = None,
|
|
26
26
|
workspace: Optional[str | List[str]] = None,
|
|
27
27
|
skip_models: Optional[str | List[str]] = ["ModelBPA", "Fabric Capacity Metrics"],
|
|
28
|
+
skip_models_in_workspace: Optional[dict] = None,
|
|
28
29
|
):
|
|
29
30
|
"""
|
|
30
31
|
Runs the semantic model Best Practice Analyzer across all semantic models in a workspace (or all accessible workspaces).
|
|
@@ -33,8 +34,6 @@ def run_model_bpa_bulk(
|
|
|
33
34
|
|
|
34
35
|
Parameters
|
|
35
36
|
----------
|
|
36
|
-
dataset : str
|
|
37
|
-
Name of the semantic model.
|
|
38
37
|
rules : pandas.DataFrame, default=None
|
|
39
38
|
A pandas dataframe containing rules to be evaluated. Based on the format of the dataframe produced by the model_bpa_rules function.
|
|
40
39
|
extended : bool, default=False
|
|
@@ -47,6 +46,12 @@ def run_model_bpa_bulk(
|
|
|
47
46
|
Defaults to None which scans all accessible workspaces.
|
|
48
47
|
skip_models : str | List[str], default=['ModelBPA', 'Fabric Capacity Metrics']
|
|
49
48
|
The semantic models to always skip when running this analysis.
|
|
49
|
+
skip_models_in_workspace : dict, default=None
|
|
50
|
+
A dictionary showing specific semantic models within specific workspaces to skip. See the example below:
|
|
51
|
+
{
|
|
52
|
+
"Workspace A": ["Dataset1", "Dataset2"],
|
|
53
|
+
"Workspace B": ["Dataset5", "Dataset 8"],
|
|
54
|
+
}
|
|
50
55
|
"""
|
|
51
56
|
|
|
52
57
|
if not lakehouse_attached():
|
|
@@ -59,24 +64,6 @@ def run_model_bpa_bulk(
|
|
|
59
64
|
|
|
60
65
|
skip_models.extend(["ModelBPA", "Fabric Capacity Metrics"])
|
|
61
66
|
|
|
62
|
-
cols = [
|
|
63
|
-
"Capacity Name",
|
|
64
|
-
"Capacity Id",
|
|
65
|
-
"Workspace Name",
|
|
66
|
-
"Workspace Id",
|
|
67
|
-
"Dataset Name",
|
|
68
|
-
"Dataset Id",
|
|
69
|
-
"Configured By",
|
|
70
|
-
"Rule Name",
|
|
71
|
-
"Category",
|
|
72
|
-
"Severity",
|
|
73
|
-
"Object Type",
|
|
74
|
-
"Object Name",
|
|
75
|
-
"Description",
|
|
76
|
-
"URL",
|
|
77
|
-
"RunId",
|
|
78
|
-
"Timestamp",
|
|
79
|
-
]
|
|
80
67
|
now = datetime.datetime.now()
|
|
81
68
|
output_table = "modelbparesults"
|
|
82
69
|
lakehouse_workspace = fabric.resolve_workspace_name()
|
|
@@ -86,7 +73,6 @@ def run_model_bpa_bulk(
|
|
|
86
73
|
)
|
|
87
74
|
lakeT = get_lakehouse_tables(lakehouse=lakehouse, workspace=lakehouse_workspace)
|
|
88
75
|
lakeT_filt = lakeT[lakeT["Table Name"] == output_table]
|
|
89
|
-
# query = f"SELECT MAX(RunId) FROM {lakehouse}.{output_table}"
|
|
90
76
|
if len(lakeT_filt) == 0:
|
|
91
77
|
runId = 1
|
|
92
78
|
else:
|
|
@@ -102,13 +88,22 @@ def run_model_bpa_bulk(
|
|
|
102
88
|
else:
|
|
103
89
|
dfW_filt = dfW[dfW["Name"].isin(workspace)]
|
|
104
90
|
|
|
91
|
+
if len(dfW_filt) == 0:
|
|
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
|
+
|
|
105
96
|
for i, r in dfW_filt.iterrows():
|
|
106
97
|
wksp = r["Name"]
|
|
107
98
|
wksp_id = r["Id"]
|
|
108
99
|
capacity_id, capacity_name = resolve_workspace_capacity(workspace=wksp)
|
|
109
|
-
df = pd.DataFrame(columns=
|
|
100
|
+
df = pd.DataFrame(columns=list(icons.bpa_schema.keys()))
|
|
110
101
|
dfD = fabric.list_datasets(workspace=wksp, mode="rest")
|
|
111
102
|
|
|
103
|
+
# Skip models in workspace
|
|
104
|
+
skip_models_wkspc = skip_models_in_workspace.get(wksp)
|
|
105
|
+
dfD = dfD[~dfD["Dataset Name"].isin(skip_models_wkspc)]
|
|
106
|
+
|
|
112
107
|
# Exclude default semantic models
|
|
113
108
|
if len(dfD) > 0:
|
|
114
109
|
dfI = fabric.list_items(workspace=wksp)
|
|
@@ -137,8 +132,8 @@ def run_model_bpa_bulk(
|
|
|
137
132
|
rules=rules,
|
|
138
133
|
extended=extended,
|
|
139
134
|
)
|
|
140
|
-
bpa_df["Capacity Id"] = capacity_id
|
|
141
135
|
bpa_df["Capacity Name"] = capacity_name
|
|
136
|
+
bpa_df["Capacity Id"] = capacity_id
|
|
142
137
|
bpa_df["Workspace Name"] = wksp
|
|
143
138
|
bpa_df["Workspace Id"] = wksp_id
|
|
144
139
|
bpa_df["Dataset Name"] = dataset_name
|
|
@@ -146,7 +141,7 @@ def run_model_bpa_bulk(
|
|
|
146
141
|
bpa_df["Configured By"] = config_by
|
|
147
142
|
bpa_df["Timestamp"] = now
|
|
148
143
|
bpa_df["RunId"] = runId
|
|
149
|
-
bpa_df = bpa_df[
|
|
144
|
+
bpa_df = bpa_df[list(icons.bpa_schema.keys())]
|
|
150
145
|
|
|
151
146
|
bpa_df["RunId"] = bpa_df["RunId"].astype("int")
|
|
152
147
|
|
|
@@ -160,21 +155,33 @@ def run_model_bpa_bulk(
|
|
|
160
155
|
)
|
|
161
156
|
print(e)
|
|
162
157
|
|
|
163
|
-
df
|
|
158
|
+
if len(df) == 0:
|
|
159
|
+
print(
|
|
160
|
+
f"{icons.yellow_dot} No BPA results to save for the '{wksp}' workspace."
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
df["Severity"].replace(icons.severity_mapping)
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
165
|
+
# Append save results individually for each workspace (so as not to create a giant dataframe)
|
|
166
|
+
print(
|
|
167
|
+
f"{icons.in_progress} Saving the Model BPA results of the '{wksp}' workspace to the '{output_table}' within the '{lakehouse}' lakehouse within the '{lakehouse_workspace}' workspace..."
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
schema = {
|
|
171
|
+
key.replace(" ", "_"): value
|
|
172
|
+
for key, value in icons.bpa_schema.items()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
save_as_delta_table(
|
|
176
|
+
dataframe=df,
|
|
177
|
+
delta_table_name=output_table,
|
|
178
|
+
write_mode="append",
|
|
179
|
+
schema=schema,
|
|
180
|
+
merge_schema=True,
|
|
181
|
+
)
|
|
182
|
+
print(
|
|
183
|
+
f"{icons.green_dot} Saved BPA results to the '{output_table}' delta table."
|
|
184
|
+
)
|
|
178
185
|
|
|
179
186
|
print(f"{icons.green_dot} Bulk BPA scan complete.")
|
|
180
187
|
|
|
@@ -203,9 +210,6 @@ def create_model_bpa_semantic_model(
|
|
|
203
210
|
The workspace in which the lakehouse resides.
|
|
204
211
|
Defaults to None which resolves to the workspace of the attached lakehouse
|
|
205
212
|
or if no lakehouse attached, resolves to the workspace of the notebook.
|
|
206
|
-
|
|
207
|
-
Returns
|
|
208
|
-
-------
|
|
209
213
|
"""
|
|
210
214
|
|
|
211
215
|
from sempy_labs._helper_functions import resolve_lakehouse_name
|
|
@@ -244,7 +248,7 @@ def create_model_bpa_semantic_model(
|
|
|
244
248
|
tom.model
|
|
245
249
|
|
|
246
250
|
dyn_connect()
|
|
247
|
-
|
|
251
|
+
icons.sll_tags.append("ModelBPABulk")
|
|
248
252
|
table_exists = False
|
|
249
253
|
with connect_semantic_model(
|
|
250
254
|
dataset=dataset, readonly=False, workspace=lakehouse_workspace
|