semantic-link-labs 0.9.11__py3-none-any.whl → 0.10.1__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.9.11.dist-info → semantic_link_labs-0.10.1.dist-info}/METADATA +6 -3
- {semantic_link_labs-0.9.11.dist-info → semantic_link_labs-0.10.1.dist-info}/RECORD +27 -19
- {semantic_link_labs-0.9.11.dist-info → semantic_link_labs-0.10.1.dist-info}/WHEEL +1 -1
- sempy_labs/__init__.py +11 -1
- sempy_labs/_a_lib_info.py +2 -0
- sempy_labs/_daxformatter.py +78 -0
- sempy_labs/_dictionary_diffs.py +221 -0
- sempy_labs/_helper_functions.py +165 -0
- sempy_labs/_list_functions.py +0 -43
- sempy_labs/_notebooks.py +3 -3
- sempy_labs/_semantic_models.py +101 -0
- sempy_labs/_sql.py +1 -1
- sempy_labs/_sql_endpoints.py +185 -0
- sempy_labs/_user_delegation_key.py +42 -0
- sempy_labs/_vpax.py +2 -0
- sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +3 -3
- sempy_labs/lakehouse/__init__.py +0 -2
- sempy_labs/lakehouse/_blobs.py +0 -37
- sempy_labs/mirrored_azure_databricks_catalog/__init__.py +15 -0
- sempy_labs/mirrored_azure_databricks_catalog/_discover.py +209 -0
- sempy_labs/mirrored_azure_databricks_catalog/_refresh_catalog_metadata.py +43 -0
- sempy_labs/report/__init__.py +2 -0
- sempy_labs/report/_report_helper.py +27 -128
- sempy_labs/report/_reportwrapper.py +1935 -1205
- sempy_labs/tom/_model.py +193 -1
- {semantic_link_labs-0.9.11.dist-info → semantic_link_labs-0.10.1.dist-info}/licenses/LICENSE +0 -0
- {semantic_link_labs-0.9.11.dist-info → semantic_link_labs-0.10.1.dist-info}/top_level.txt +0 -0
sempy_labs/tom/_model.py
CHANGED
|
@@ -47,6 +47,13 @@ class TOMWrapper:
|
|
|
47
47
|
_tables_added: List[str]
|
|
48
48
|
_table_map = dict
|
|
49
49
|
_column_map = dict
|
|
50
|
+
_dax_formatting = {
|
|
51
|
+
"measures": [],
|
|
52
|
+
"calculated_columns": [],
|
|
53
|
+
"calculated_tables": [],
|
|
54
|
+
"calculation_items": [],
|
|
55
|
+
"rls": [],
|
|
56
|
+
}
|
|
50
57
|
|
|
51
58
|
def __init__(self, dataset, workspace, readonly):
|
|
52
59
|
|
|
@@ -4716,7 +4723,12 @@ class TOMWrapper:
|
|
|
4716
4723
|
TOM.ValueFilterBehaviorType, value_filter_behavior
|
|
4717
4724
|
)
|
|
4718
4725
|
|
|
4719
|
-
def add_role_member(
|
|
4726
|
+
def add_role_member(
|
|
4727
|
+
self,
|
|
4728
|
+
role_name: str,
|
|
4729
|
+
member: str | List[str],
|
|
4730
|
+
role_member_type: Optional[str] = "User",
|
|
4731
|
+
):
|
|
4720
4732
|
"""
|
|
4721
4733
|
Adds an external model role member (AzureAD) to a role.
|
|
4722
4734
|
|
|
@@ -4726,13 +4738,23 @@ class TOMWrapper:
|
|
|
4726
4738
|
The role name.
|
|
4727
4739
|
member : str | List[str]
|
|
4728
4740
|
The email address(es) of the member(s) to add.
|
|
4741
|
+
role_member_type : str, default="User"
|
|
4742
|
+
The type of the role member. Default is "User". Other options include "Group" for Azure AD groups.
|
|
4743
|
+
All members must be of the same role_member_type.
|
|
4729
4744
|
"""
|
|
4730
4745
|
|
|
4731
4746
|
import Microsoft.AnalysisServices.Tabular as TOM
|
|
4747
|
+
import System
|
|
4732
4748
|
|
|
4733
4749
|
if isinstance(member, str):
|
|
4734
4750
|
member = [member]
|
|
4735
4751
|
|
|
4752
|
+
role_member_type = role_member_type.capitalize()
|
|
4753
|
+
if role_member_type not in ["User", "Group"]:
|
|
4754
|
+
raise ValueError(
|
|
4755
|
+
f"{icons.red_dot} The '{role_member_type}' is not a valid role member type. Valid options: 'User', 'Group'."
|
|
4756
|
+
)
|
|
4757
|
+
|
|
4736
4758
|
role = self.model.Roles[role_name]
|
|
4737
4759
|
current_members = [m.MemberName for m in role.Members]
|
|
4738
4760
|
|
|
@@ -4741,6 +4763,7 @@ class TOMWrapper:
|
|
|
4741
4763
|
rm = TOM.ExternalModelRoleMember()
|
|
4742
4764
|
rm.IdentityProvider = "AzureAD"
|
|
4743
4765
|
rm.MemberName = m
|
|
4766
|
+
rm.MemberType = System.Enum.Parse(TOM.RoleMemberType, role_member_type)
|
|
4744
4767
|
role.Members.Add(rm)
|
|
4745
4768
|
print(
|
|
4746
4769
|
f"{icons.green_dot} '{m}' has been added as a member of the '{role_name}' role."
|
|
@@ -5138,8 +5161,177 @@ class TOMWrapper:
|
|
|
5138
5161
|
f"{icons.green_dot} The '{object.Name}' {str(object.ObjectType).lower()} has been copied to the '{target_dataset}' semantic model within the '{target_workspace}' workspace."
|
|
5139
5162
|
)
|
|
5140
5163
|
|
|
5164
|
+
def format_dax(
|
|
5165
|
+
self,
|
|
5166
|
+
object: Optional[
|
|
5167
|
+
Union[
|
|
5168
|
+
"TOM.Measure",
|
|
5169
|
+
"TOM.CalcultedColumn",
|
|
5170
|
+
"TOM.CalculationItem",
|
|
5171
|
+
"TOM.CalculatedTable",
|
|
5172
|
+
"TOM.TablePermission",
|
|
5173
|
+
]
|
|
5174
|
+
] = None,
|
|
5175
|
+
):
|
|
5176
|
+
"""
|
|
5177
|
+
Formats the DAX expressions of measures, calculated columns, calculation items, calculated tables and row level security expressions in the semantic model.
|
|
5178
|
+
|
|
5179
|
+
This function uses the `DAX Formatter API <https://www.daxformatter.com/>`_.
|
|
5180
|
+
|
|
5181
|
+
Parameters
|
|
5182
|
+
----------
|
|
5183
|
+
object : TOM Object, default=None
|
|
5184
|
+
The TOM object to format. If None, formats all measures, calculated columns, calculation items, calculated tables and row level security expressions in the semantic model.
|
|
5185
|
+
If a specific object is provided, only that object will be formatted.
|
|
5186
|
+
"""
|
|
5187
|
+
|
|
5188
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
5189
|
+
|
|
5190
|
+
if object is None:
|
|
5191
|
+
object_map = {
|
|
5192
|
+
"measures": self.all_measures,
|
|
5193
|
+
"calculated_columns": self.all_calculated_columns,
|
|
5194
|
+
"calculation_items": self.all_calculation_items,
|
|
5195
|
+
"calculated_tables": self.all_calculated_tables,
|
|
5196
|
+
"rls": self.all_rls,
|
|
5197
|
+
}
|
|
5198
|
+
|
|
5199
|
+
for key, func in object_map.items():
|
|
5200
|
+
for obj in func():
|
|
5201
|
+
if key == "calculated_tables":
|
|
5202
|
+
p = next(p for p in obj.Partitions)
|
|
5203
|
+
name = obj.Name
|
|
5204
|
+
expr = p.Source.Expression
|
|
5205
|
+
table = obj.Name
|
|
5206
|
+
elif key == "calculation_items":
|
|
5207
|
+
name = obj.Name
|
|
5208
|
+
expr = obj.Expression
|
|
5209
|
+
table = obj.Parent.Table.Name
|
|
5210
|
+
elif key == "rls":
|
|
5211
|
+
name = obj.Role.Name
|
|
5212
|
+
expr = obj.FilterExpression
|
|
5213
|
+
table = obj.Table.Name
|
|
5214
|
+
else:
|
|
5215
|
+
name = obj.Name
|
|
5216
|
+
expr = obj.Expression
|
|
5217
|
+
table = obj.Table.Name
|
|
5218
|
+
self._dax_formatting[key].append(
|
|
5219
|
+
{
|
|
5220
|
+
"name": name,
|
|
5221
|
+
"expression": expr,
|
|
5222
|
+
"table": table,
|
|
5223
|
+
}
|
|
5224
|
+
)
|
|
5225
|
+
return
|
|
5226
|
+
|
|
5227
|
+
if object.ObjectType == TOM.ObjectType.Measure:
|
|
5228
|
+
self._dax_formatting["measures"].append(
|
|
5229
|
+
{
|
|
5230
|
+
"name": object.Name,
|
|
5231
|
+
"expression": object.Expression,
|
|
5232
|
+
"table": object.Parent.Name,
|
|
5233
|
+
}
|
|
5234
|
+
)
|
|
5235
|
+
elif object.ObjectType == TOM.ObjectType.CalculatedColumn:
|
|
5236
|
+
self._dax_formatting["measures"].append(
|
|
5237
|
+
{
|
|
5238
|
+
"name": object.Name,
|
|
5239
|
+
"expression": object.Expression,
|
|
5240
|
+
"table": object.Parent.Name,
|
|
5241
|
+
}
|
|
5242
|
+
)
|
|
5243
|
+
elif object.ObjectType == TOM.ObjectType.CalculationItem:
|
|
5244
|
+
self._dax_formatting["measures"].append(
|
|
5245
|
+
{
|
|
5246
|
+
"name": object.Name,
|
|
5247
|
+
"expression": object.Expression,
|
|
5248
|
+
"table": object.Parent.Name,
|
|
5249
|
+
}
|
|
5250
|
+
)
|
|
5251
|
+
elif object.ObjectType == TOM.ObjectType.CalculatedTable:
|
|
5252
|
+
self._dax_formatting["measures"].append(
|
|
5253
|
+
{
|
|
5254
|
+
"name": object.Name,
|
|
5255
|
+
"expression": object.Expression,
|
|
5256
|
+
"table": object.Name,
|
|
5257
|
+
}
|
|
5258
|
+
)
|
|
5259
|
+
else:
|
|
5260
|
+
raise ValueError(
|
|
5261
|
+
f"{icons.red_dot} The '{str(object.ObjectType)}' object type is not supported for DAX formatting."
|
|
5262
|
+
)
|
|
5263
|
+
|
|
5141
5264
|
def close(self):
|
|
5142
5265
|
|
|
5266
|
+
# DAX Formatting
|
|
5267
|
+
from sempy_labs._daxformatter import _format_dax
|
|
5268
|
+
|
|
5269
|
+
def _process_dax_objects(object_type, model_accessor=None):
|
|
5270
|
+
items = self._dax_formatting.get(object_type, [])
|
|
5271
|
+
if not items:
|
|
5272
|
+
return False
|
|
5273
|
+
|
|
5274
|
+
# Extract and format expressions
|
|
5275
|
+
expressions = [item["expression"] for item in items]
|
|
5276
|
+
metadata = [
|
|
5277
|
+
{"name": item["name"], "table": item["table"], "type": object_type}
|
|
5278
|
+
for item in items
|
|
5279
|
+
]
|
|
5280
|
+
|
|
5281
|
+
formatted_expressions = _format_dax(expressions, metadata=metadata)
|
|
5282
|
+
|
|
5283
|
+
# Update the expressions in the original structure
|
|
5284
|
+
for item, formatted in zip(items, formatted_expressions):
|
|
5285
|
+
item["expression"] = formatted
|
|
5286
|
+
|
|
5287
|
+
# Apply updated expressions to the model
|
|
5288
|
+
for item in items:
|
|
5289
|
+
table_name = (
|
|
5290
|
+
item["table"]
|
|
5291
|
+
if object_type != "calculated_tables"
|
|
5292
|
+
else item["name"]
|
|
5293
|
+
)
|
|
5294
|
+
name = item["name"]
|
|
5295
|
+
expression = item["expression"]
|
|
5296
|
+
|
|
5297
|
+
if object_type == "calculated_tables":
|
|
5298
|
+
t = self.model.Tables[table_name]
|
|
5299
|
+
p = next(p for p in t.Partitions)
|
|
5300
|
+
p.Source.Expression = expression
|
|
5301
|
+
elif object_type == "rls":
|
|
5302
|
+
self.model.Roles[name].TablePermissions[
|
|
5303
|
+
table_name
|
|
5304
|
+
].FilterExpression = expression
|
|
5305
|
+
elif object_type == "calculation_items":
|
|
5306
|
+
self.model.Tables[table_name].CalculationGroup.CalculationItems[
|
|
5307
|
+
name
|
|
5308
|
+
].Expression = expression
|
|
5309
|
+
else:
|
|
5310
|
+
getattr(self.model.Tables[table_name], model_accessor)[
|
|
5311
|
+
name
|
|
5312
|
+
].Expression = expression
|
|
5313
|
+
return True
|
|
5314
|
+
|
|
5315
|
+
# Use the helper for each object type
|
|
5316
|
+
a = _process_dax_objects("measures", "Measures")
|
|
5317
|
+
b = _process_dax_objects("calculated_columns", "Columns")
|
|
5318
|
+
c = _process_dax_objects("calculation_items")
|
|
5319
|
+
d = _process_dax_objects("calculated_tables")
|
|
5320
|
+
e = _process_dax_objects("rls")
|
|
5321
|
+
if any([a, b, c, d, e]) and not self._readonly:
|
|
5322
|
+
from IPython.display import display, HTML
|
|
5323
|
+
|
|
5324
|
+
html = """
|
|
5325
|
+
<span style="font-family: Segoe UI, Arial, sans-serif; color: #cccccc;">
|
|
5326
|
+
CODE BEAUTIFIED WITH
|
|
5327
|
+
</span>
|
|
5328
|
+
<a href="https://www.daxformatter.com" target="_blank" style="font-family: Segoe UI, Arial, sans-serif; color: #ff5a5a; font-weight: bold; text-decoration: none;">
|
|
5329
|
+
DAX FORMATTER
|
|
5330
|
+
</a>
|
|
5331
|
+
"""
|
|
5332
|
+
|
|
5333
|
+
display(HTML(html))
|
|
5334
|
+
|
|
5143
5335
|
if not self._readonly and self.model is not None:
|
|
5144
5336
|
|
|
5145
5337
|
import Microsoft.AnalysisServices.Tabular as TOM
|
{semantic_link_labs-0.9.11.dist-info → semantic_link_labs-0.10.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|