Skip to content

Commit 7d302c4

Browse files
committed
Merge branch 'main' into m-kovalsky/listfunctionfixes
2 parents a8d7a07 + 04d9d64 commit 7d302c4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1169
-617
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
# [semantic-link-labs](https://semantic-link-labs.readthedocs.io/en/0.5.0/)
1+
# Semantic Link Labs
22

33
[![PyPI version](https://badge.fury.io/py/semantic-link-labs.svg)](https://badge.fury.io/py/semantic-link-labs)
44
[![Read The Docs](https://readthedocs.org/projects/semantic-link-labs/badge/?version=0.5.0&style=flat)](https://readthedocs.org/projects/semantic-link-labs/)
55
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
66
[![Downloads](https://static.pepy.tech/badge/semantic-link-labs)](https://pepy.tech/project/semantic-link-labs)
77

8-
All functions in this library are documented [here](https://semantic-link-labs.readthedocs.io/en/0.5.0/)!
8+
---
9+
[Read the documentation on ReadTheDocs!](https://semantic-link-labs.readthedocs.io/en/stable/)
10+
---
911

10-
This is a python library intended to be used in [Microsoft Fabric notebooks](https://learn.microsoft.com/fabric/data-engineering/how-to-use-notebook). This library was originally intended to contain functions used for [migrating semantic models to Direct Lake mode](https://github.com/microsoft/semantic-link-labs?tab=readme-ov-file#direct-lake-migration). However, it quickly became apparent that functions within such a library could support many other useful activities in the realm of semantic models, reports, lakehouses and really anything Fabric-related. As such, this library contains a variety of functions ranging from running [Vertipaq Analyzer](https://semantic-link-labs.readthedocs.io/en/0.5.0/sempy_labs.html#sempy_labs.import_vertipaq_analyzer) or the [Best Practice Analyzer](https://semantic-link-labs.readthedocs.io/en/0.5.0/sempy_labs.html#sempy_labs.run_model_bpa) against a semantic model to seeing if any [lakehouse tables hit Direct Lake guardrails](https://semantic-link-labs.readthedocs.io/en/0.5.0/sempy_labs.lakehouse.html#sempy_labs.lakehouse.get_lakehouse_tables) or accessing the [Tabular Object Model](https://semantic-link-labs.readthedocs.io/en/0.5.0/sempy_labs.tom.html) and more!
12+
This is a python library intended to be used in [Microsoft Fabric notebooks](https://learn.microsoft.com/fabric/data-engineering/how-to-use-notebook). This library was originally intended to solely contain functions used for [migrating semantic models to Direct Lake mode](https://github.com/microsoft/semantic-link-labs?tab=readme-ov-file#direct-lake-migration). However, it quickly became apparent that functions within such a library could support many other useful activities in the realm of semantic models, reports, lakehouses and really anything Fabric-related. As such, this library contains a variety of functions ranging from running [Vertipaq Analyzer](https://semantic-link-labs.readthedocs.io/en/stable/sempy_labs.html#sempy_labs.import_vertipaq_analyzer) or the [Best Practice Analyzer](https://semantic-link-labs.readthedocs.io/en/stable/sempy_labs.html#sempy_labs.run_model_bpa) against a semantic model to seeing if any [lakehouse tables hit Direct Lake guardrails](https://semantic-link-labs.readthedocs.io/en/stable/sempy_labs.lakehouse.html#sempy_labs.lakehouse.get_lakehouse_tables) or accessing the [Tabular Object Model](https://semantic-link-labs.readthedocs.io/en/stable/sempy_labs.tom.html) and more!
1113

1214
Instructions for migrating import/DirectQuery semantic models to Direct Lake mode can be found [here](https://github.com/microsoft/semantic-link-labs?tab=readme-ov-file#direct-lake-migration).
1315

1416
If you encounter any issues, please [raise a bug](https://github.com/microsoft/semantic-link-labs/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=).
1517

1618
If you have ideas for new features/functions, please [request a feature](https://github.com/microsoft/semantic-link-labs/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=).
1719

18-
## [Function documentation](https://semantic-link-labs.readthedocs.io/en/0.5.0/)
19-
2020
## Install the library in a Fabric notebook
2121
```python
2222
%pip install semantic-link-labs

notebooks/Tabular Object Model.ipynb

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/sempy_labs/__init__.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
resolve_report_name,
6767
# language_validate
6868
)
69+
6970
# from sempy_labs._model_auto_build import model_auto_build
7071
from sempy_labs._model_bpa import model_bpa_rules, run_model_bpa
7172
from sempy_labs._model_dependencies import (
@@ -125,7 +126,7 @@
125126
#'list_sqlendpoints',
126127
#'list_tables',
127128
"list_warehouses",
128-
'list_workspace_role_assignments',
129+
"list_workspace_role_assignments",
129130
"create_warehouse",
130131
"update_item",
131132
"create_abfss_path",
@@ -141,20 +142,20 @@
141142
"resolve_report_id",
142143
"resolve_report_name",
143144
#'language_validate',
144-
#"model_auto_build",
145+
# "model_auto_build",
145146
"model_bpa_rules",
146147
"run_model_bpa",
147148
"measure_dependency_tree",
148149
"get_measure_dependencies",
149150
"get_model_calc_dependencies",
150151
"export_model_to_onelake",
151-
'qso_sync',
152-
'qso_sync_status',
153-
'set_qso',
154-
'list_qso_settings',
155-
'disable_qso',
156-
'set_semantic_model_storage_format',
157-
'set_workspace_default_storage_format',
152+
"qso_sync",
153+
"qso_sync_status",
154+
"set_qso",
155+
"list_qso_settings",
156+
"disable_qso",
157+
"set_semantic_model_storage_format",
158+
"set_workspace_default_storage_format",
158159
"refresh_semantic_model",
159160
"cancel_dataset_refresh",
160161
"translate_semantic_model",
@@ -174,5 +175,5 @@
174175
"delete_user_from_workspace",
175176
"update_workspace_user",
176177
"list_workspace_users",
177-
"assign_workspace_to_dataflow_storage"
178+
"assign_workspace_to_dataflow_storage",
178179
]

src/sempy_labs/_ai.py

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ def optimize_semantic_model(dataset: str, workspace: Optional[str] = None):
1414
from ._model_bpa import run_model_bpa
1515
from .directlake._fallback import check_fallback_reason
1616
from ._helper_functions import format_dax_object_name
17-
from sempy_labs.tom import connect_semantic_model
1817

1918
modelBPA = run_model_bpa(
2019
dataset=dataset, workspace=workspace, return_dataframe=True
@@ -41,7 +40,8 @@ def optimize_semantic_model(dataset: str, workspace: Optional[str] = None):
4140

4241
if len(fallback_filt) > 0:
4342
print(
44-
f"{icons.yellow_dot} The '{dataset}' semantic model is a Direct Lake semantic model which contains views. Since views always fall back to DirectQuery, it is recommended to only use lakehouse tables and not views."
43+
f"{icons.yellow_dot} The '{dataset}' semantic model is a Direct Lake semantic model which contains views. "
44+
"Since views always fall back to DirectQuery, it is recommended to only use lakehouse tables and not views."
4545
)
4646

4747
# Potential model reduction estimate
@@ -79,7 +79,9 @@ def generate_measure_descriptions(
7979

8080
validModels = ["gpt-35-turbo", "gpt-35-turbo-16k", "gpt-4"]
8181
if gpt_model not in validModels:
82-
raise ValueError(f"{icons.red_dot} The '{gpt_model}' model is not a valid model. Enter a gpt_model from this list: {validModels}.")
82+
raise ValueError(
83+
f"{icons.red_dot} The '{gpt_model}' model is not a valid model. Enter a gpt_model from this list: {validModels}."
84+
)
8385

8486
dfM = fabric.list_measures(dataset=dataset, workspace=workspace)
8587

@@ -114,8 +116,7 @@ def generate_measure_descriptions(
114116
)
115117

116118
# Update the model to use the new descriptions
117-
#with connect_semantic_model(dataset=dataset, workspace=workspace, readonly=False) as tom:
118-
119+
# with connect_semantic_model(dataset=dataset, workspace=workspace, readonly=False) as tom:
119120

120121
# for t in m.Tables:
121122
# tName = t.Name
@@ -146,10 +147,10 @@ def generate_aggs(
146147
import System
147148

148149
# columns = {
149-
#'SalesAmount': 'Sum',
150-
#'ProductKey': 'GroupBy',
151-
#'OrderDateKey': 'GroupBy'
152-
# }
150+
# 'SalesAmount': 'Sum',
151+
# 'ProductKey': 'GroupBy',
152+
# 'OrderDateKey': 'GroupBy'
153+
# }
153154

154155
if workspace is None:
155156
workspace_id = fabric.get_workspace_id()
@@ -171,33 +172,44 @@ def generate_aggs(
171172
numericTypes = ["Int64", "Double", "Decimal"]
172173

173174
if any(value not in aggTypes for value in columns.values()):
174-
raise ValueError(f"{icons.red_dot} Invalid aggregation type(s) have been specified in the 'columns' parameter. Valid aggregation types: {aggTypes}.")
175+
raise ValueError(
176+
f"{icons.red_dot} Invalid aggregation type(s) have been specified in the 'columns' parameter. Valid aggregation types: {aggTypes}."
177+
)
175178

176179
dfC = fabric.list_columns(dataset=dataset, workspace=workspace)
177180
dfP = fabric.list_partitions(dataset=dataset, workspace=workspace)
178181
dfM = fabric.list_measures(dataset=dataset, workspace=workspace)
179182
dfR = fabric.list_relationships(dataset=dataset, workspace=workspace)
180183
if not any(r["Mode"] == "DirectLake" for i, r in dfP.iterrows()):
181-
raise ValueError(f"{icons.red_dot} The '{dataset}' semantic model within the '{workspace}' workspace is not in Direct Lake mode. This function is only relevant for Direct Lake semantic models.")
182-
184+
raise ValueError(
185+
f"{icons.red_dot} The '{dataset}' semantic model within the '{workspace}' workspace is not in Direct Lake mode. This function is only relevant for Direct Lake semantic models."
186+
)
187+
183188
dfC_filtT = dfC[dfC["Table Name"] == table_name]
184189

185190
if len(dfC_filtT) == 0:
186-
raise ValueError(f"{icons.red_dot} The '{table_name}' table does not exist in the '{dataset}' semantic model within the '{workspace}' workspace.")
191+
raise ValueError(
192+
f"{icons.red_dot} The '{table_name}' table does not exist in the '{dataset}' semantic model within the '{workspace}' workspace."
193+
)
187194

188195
dfC_filt = dfC[
189196
(dfC["Table Name"] == table_name) & (dfC["Column Name"].isin(columnValues))
190197
]
191198

192199
if len(columns) != len(dfC_filt):
193-
raise ValueError(f"{icons.red_dot} Columns listed in '{columnValues}' do not exist in the '{table_name}' table in the '{dataset}' semantic model within the '{workspace}' workspace.")
200+
raise ValueError(
201+
f"{icons.red_dot} Columns listed in '{columnValues}' do not exist in the '{table_name}' table in the '{dataset}' semantic model within the '{workspace}' workspace."
202+
)
194203

195204
# Check if doing sum/count/min/max etc. on a non-number column
196-
for col, agg in columns.items():
197-
dfC_col = dfC_filt[dfC_filt["Column Name"] == col]
205+
for cm, agg in columns.items():
206+
dfC_col = dfC_filt[dfC_filt["Column Name"] == cm]
198207
dataType = dfC_col["Data Type"].iloc[0]
199208
if agg in aggTypesAggregate and dataType not in numericTypes:
200-
raise ValueError(f"{icons.red_dot} The '{col}' column in the '{table_name}' table is of '{dataType}' data type. Only columns of '{numericTypes}' data types can be aggregated as '{aggTypesAggregate}' aggregation types.")
209+
raise ValueError(
210+
f"{icons.red_dot} The '{cm}' column in the '{table_name}' table is of '{dataType}' data type. Only columns of '{numericTypes}' data types"
211+
f" can be aggregated as '{aggTypesAggregate}' aggregation types."
212+
)
201213

202214
# Create/update lakehouse delta agg table
203215
aggSuffix = "_agg"
@@ -213,7 +225,10 @@ def generate_aggs(
213225
dfI_filt = dfI[(dfI["Id"] == sqlEndpointId)]
214226

215227
if len(dfI_filt) == 0:
216-
raise ValueError(f"{icons.red_dot} The lakehouse (SQL Endpoint) used by the '{dataset}' semantic model does not reside in the '{lakehouse_workspace}' workspace. Please update the lakehouse_workspace parameter.")
228+
raise ValueError(
229+
f"{icons.red_dot} The lakehouse (SQL Endpoint) used by the '{dataset}' semantic model does not reside in"
230+
f" the '{lakehouse_workspace}' workspace. Please update the lakehouse_workspace parameter."
231+
)
217232

218233
lakehouseName = dfI_filt["Display Name"].iloc[0]
219234
lakehouse_id = resolve_lakehouse_id(
@@ -223,8 +238,8 @@ def generate_aggs(
223238
# Generate SQL query
224239
query = "SELECT"
225240
groupBy = "\nGROUP BY"
226-
for col, agg in columns.items():
227-
colFilt = dfC_filt[dfC_filt["Column Name"] == col]
241+
for cm, agg in columns.items():
242+
colFilt = dfC_filt[dfC_filt["Column Name"] == cm]
228243
sourceCol = colFilt["Source"].iloc[0]
229244

230245
if agg == "GroupBy":
@@ -328,7 +343,9 @@ def generate_aggs(
328343
col.DataType = System.Enum.Parse(TOM.DataType, dType)
329344

330345
m.Tables[aggTableName].Columns.Add(col)
331-
print(f"{icons.green_dot} The '{aggTableName}'[{cName}] column has been added.")
346+
print(
347+
f"{icons.green_dot} The '{aggTableName}'[{cName}] column has been added."
348+
)
332349

333350
# Create relationships
334351
relMap = {"m": "Many", "1": "One", "0": "None"}
@@ -367,22 +384,24 @@ def generate_aggs(
367384
print(
368385
f"{icons.green_dot} '{aggTableName}'[{fromColumn}] -> '{toTable}'[{toColumn}] relationship has been added."
369386
)
370-
except:
387+
except Exception as e:
371388
print(
372389
f"{icons.red_dot} '{aggTableName}'[{fromColumn}] -> '{toTable}'[{toColumn}] relationship has not been created."
373390
)
391+
print(f"Exception occured: {e}")
374392
elif toTable == table_name:
375393
try:
376394
rel.ToColumn = m.Tables[aggTableName].Columns[toColumn]
377395
m.Relationships.Add(rel)
378396
print(
379397
f"{icons.green_dot} '{fromTable}'[{fromColumn}] -> '{aggTableName}'[{toColumn}] relationship has been added."
380398
)
381-
except:
399+
except Exception as e:
382400
print(
383401
f"{icons.red_dot} '{fromTable}'[{fromColumn}] -> '{aggTableName}'[{toColumn}] relationship has not been created."
384402
)
385-
f"Relationship creation is complete."
403+
print(f"Exception occured: {e}")
404+
"Relationship creation is complete."
386405

387406
# Create IF measure
388407
f"\n{icons.in_progress} Creating measure to check if the agg table can be used..."

src/sempy_labs/_clear_cache.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import sempy
21
import sempy.fabric as fabric
32
from ._helper_functions import resolve_dataset_id
4-
from typing import List, Optional, Union
3+
from typing import Optional
54
import sempy_labs._icons as icons
65

76

@@ -25,10 +24,10 @@ def clear_cache(dataset: str, workspace: Optional[str] = None):
2524
datasetID = resolve_dataset_id(dataset=dataset, workspace=workspace)
2625

2726
xmla = f"""
28-
<ClearCache xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">
27+
<ClearCache xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">
2928
<Object>
30-
<DatabaseID>{datasetID}</DatabaseID>
31-
</Object>
29+
<DatabaseID>{datasetID}</DatabaseID>
30+
</Object>
3231
</ClearCache>
3332
"""
3433
fabric.execute_xmla(dataset=dataset, xmla_command=xmla, workspace=workspace)

src/sempy_labs/_connections.py

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import sempy
21
import sempy.fabric as fabric
32
import pandas as pd
4-
from typing import List, Optional, Union
53
import sempy_labs._icons as icons
64

75

@@ -56,21 +54,27 @@ def create_connection_cloud(
5654
},
5755
}
5856

59-
response = client.post(f"/v1/connections", json=request_body)
57+
response = client.post("/v1/connections", json=request_body)
6058

6159
if response.status_code == 200:
6260
o = response.json()
6361
new_data = {
6462
"Connection Id": o.get("id"),
6563
"Connection Name": o.get("name"),
6664
"Connectivity Type": o.get("connectivityType"),
67-
"Connection Type": o.get("connectionDetails",{}).get("type"),
68-
"Connection Path": o.get("connectionDetails",{}).get("path"),
65+
"Connection Type": o.get("connectionDetails", {}).get("type"),
66+
"Connection Path": o.get("connectionDetails", {}).get("path"),
6967
"Privacy Level": o.get("privacyLevel"),
70-
"Credential Type": o.get("credentialDetails",{}).get("credentialType"),
71-
"Single Sign On Type": o.get("credentialDetails",{}).get("singleSignOnType"),
72-
"Connection Encryption": o.get("credentialDetails",{}).get("connectionEncryption"),
73-
"Skip Test Connection": o.get("credentialDetails",{}).get("skipTestConnection"),
68+
"Credential Type": o.get("credentialDetails", {}).get("credentialType"),
69+
"Single Sign On Type": o.get("credentialDetails", {}).get(
70+
"singleSignOnType"
71+
),
72+
"Connection Encryption": o.get("credentialDetails", {}).get(
73+
"connectionEncryption"
74+
),
75+
"Skip Test Connection": o.get("credentialDetails", {}).get(
76+
"skipTestConnection"
77+
),
7478
}
7579
df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
7680

@@ -131,7 +135,7 @@ def create_connection_on_prem(
131135
},
132136
}
133137

134-
response = client.post(f"/v1/connections", json=request_body)
138+
response = client.post("/v1/connections", json=request_body)
135139

136140
if response.status_code == 200:
137141
o = response.json()
@@ -140,13 +144,19 @@ def create_connection_on_prem(
140144
"Connection Name": o.get("name"),
141145
"Gateway ID": o.get("gatewayId"),
142146
"Connectivity Type": o.get("connectivityType"),
143-
"Connection Type": o.get("connectionDetails",{}).get("type"),
144-
"Connection Path": o.get("connectionDetails",{}).get("path"),
147+
"Connection Type": o.get("connectionDetails", {}).get("type"),
148+
"Connection Path": o.get("connectionDetails", {}).get("path"),
145149
"Privacy Level": o.get("privacyLevel"),
146-
"Credential Type": o.get("credentialDetails",{}).get("credentialType"),
147-
"Single Sign On Type": o.get("credentialDetails",{}).get("singleSignOnType"),
148-
"Connection Encryption": o.get("credentialDetails",{}).get("connectionEncryption"),
149-
"Skip Test Connection": o.get("credentialDetails",{}).get("skipTestConnection"),
150+
"Credential Type": o.get("credentialDetails", {}).get("credentialType"),
151+
"Single Sign On Type": o.get("credentialDetails", {}).get(
152+
"singleSignOnType"
153+
),
154+
"Connection Encryption": o.get("credentialDetails", {}).get(
155+
"connectionEncryption"
156+
),
157+
"Skip Test Connection": o.get("credentialDetails", {}).get(
158+
"skipTestConnection"
159+
),
150160
}
151161
df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
152162

@@ -209,7 +219,7 @@ def create_connection_vnet(
209219
},
210220
}
211221

212-
response = client.post(f"/v1/connections", json=request_body)
222+
response = client.post("/v1/connections", json=request_body)
213223

214224
if response.status_code == 200:
215225
o = response.json()
@@ -218,13 +228,19 @@ def create_connection_vnet(
218228
"Connection Name": o.get("name"),
219229
"Gateway ID": o.get("gatewayId"),
220230
"Connectivity Type": o.get("connectivityType"),
221-
"Connection Type": o.get("connectionDetails",{}).get("type"),
222-
"Connection Path": o.get("connectionDetails",{}).get("path"),
231+
"Connection Type": o.get("connectionDetails", {}).get("type"),
232+
"Connection Path": o.get("connectionDetails", {}).get("path"),
223233
"Privacy Level": o.get("privacyLevel"),
224-
"Credential Type": o.get("credentialDetails",{}).get("credentialType"),
225-
"Single Sign On Type": o.get("credentialDetails",{}).get("singleSignOnType"),
226-
"Connection Encryption": o.get("credentialDetails",{}).get("connectionEncryption"),
227-
"Skip Test Connection": o.get("credentialDetails",{}).get("skipTestConnection"),
234+
"Credential Type": o.get("credentialDetails", {}).get("credentialType"),
235+
"Single Sign On Type": o.get("credentialDetails", {}).get(
236+
"singleSignOnType"
237+
),
238+
"Connection Encryption": o.get("credentialDetails", {}).get(
239+
"connectionEncryption"
240+
),
241+
"Skip Test Connection": o.get("credentialDetails", {}).get(
242+
"skipTestConnection"
243+
),
228244
}
229245
df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True)
230246

0 commit comments

Comments
 (0)