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.

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(self, role_name: str, member: str | List[str]):
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