semantic-link-labs 0.5.0__py3-none-any.whl → 0.7.0__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.

Files changed (113) hide show
  1. semantic_link_labs-0.7.0.dist-info/METADATA +148 -0
  2. semantic_link_labs-0.7.0.dist-info/RECORD +111 -0
  3. {semantic_link_labs-0.5.0.dist-info → semantic_link_labs-0.7.0.dist-info}/WHEEL +1 -1
  4. sempy_labs/__init__.py +45 -15
  5. sempy_labs/_ai.py +42 -85
  6. sempy_labs/_bpa_translation/_translations_am-ET.po +828 -0
  7. sempy_labs/_bpa_translation/_translations_ar-AE.po +860 -0
  8. sempy_labs/_bpa_translation/_translations_cs-CZ.po +894 -0
  9. sempy_labs/_bpa_translation/_translations_da-DK.po +894 -0
  10. sempy_labs/_bpa_translation/_translations_de-DE.po +933 -0
  11. sempy_labs/_bpa_translation/_translations_el-GR.po +936 -0
  12. sempy_labs/_bpa_translation/_translations_es-ES.po +915 -0
  13. sempy_labs/_bpa_translation/_translations_fa-IR.po +883 -0
  14. sempy_labs/_bpa_translation/_translations_fr-FR.po +938 -0
  15. sempy_labs/_bpa_translation/_translations_ga-IE.po +912 -0
  16. sempy_labs/_bpa_translation/_translations_he-IL.po +855 -0
  17. sempy_labs/_bpa_translation/_translations_hi-IN.po +892 -0
  18. sempy_labs/_bpa_translation/_translations_hu-HU.po +910 -0
  19. sempy_labs/_bpa_translation/_translations_is-IS.po +887 -0
  20. sempy_labs/_bpa_translation/_translations_it-IT.po +931 -0
  21. sempy_labs/_bpa_translation/_translations_ja-JP.po +805 -0
  22. sempy_labs/_bpa_translation/_translations_nl-NL.po +924 -0
  23. sempy_labs/_bpa_translation/_translations_pl-PL.po +913 -0
  24. sempy_labs/_bpa_translation/_translations_pt-BR.po +909 -0
  25. sempy_labs/_bpa_translation/_translations_pt-PT.po +904 -0
  26. sempy_labs/_bpa_translation/_translations_ru-RU.po +909 -0
  27. sempy_labs/_bpa_translation/_translations_ta-IN.po +922 -0
  28. sempy_labs/_bpa_translation/_translations_te-IN.po +896 -0
  29. sempy_labs/_bpa_translation/_translations_th-TH.po +873 -0
  30. sempy_labs/_bpa_translation/_translations_zh-CN.po +767 -0
  31. sempy_labs/_bpa_translation/_translations_zu-ZA.po +916 -0
  32. sempy_labs/_clear_cache.py +12 -8
  33. sempy_labs/_connections.py +77 -70
  34. sempy_labs/_dax.py +7 -9
  35. sempy_labs/_generate_semantic_model.py +75 -90
  36. sempy_labs/_helper_functions.py +371 -20
  37. sempy_labs/_icons.py +23 -0
  38. sempy_labs/_list_functions.py +855 -427
  39. sempy_labs/_model_auto_build.py +4 -3
  40. sempy_labs/_model_bpa.py +307 -1118
  41. sempy_labs/_model_bpa_bulk.py +363 -0
  42. sempy_labs/_model_bpa_rules.py +831 -0
  43. sempy_labs/_model_dependencies.py +20 -16
  44. sempy_labs/_one_lake_integration.py +18 -12
  45. sempy_labs/_query_scale_out.py +116 -129
  46. sempy_labs/_refresh_semantic_model.py +23 -10
  47. sempy_labs/_translations.py +367 -288
  48. sempy_labs/_vertipaq.py +152 -123
  49. sempy_labs/directlake/__init__.py +7 -1
  50. sempy_labs/directlake/_directlake_schema_compare.py +33 -30
  51. sempy_labs/directlake/_directlake_schema_sync.py +60 -77
  52. sempy_labs/directlake/_dl_helper.py +233 -0
  53. sempy_labs/directlake/_get_directlake_lakehouse.py +7 -8
  54. sempy_labs/directlake/_get_shared_expression.py +5 -3
  55. sempy_labs/directlake/_guardrails.py +20 -16
  56. sempy_labs/directlake/_list_directlake_model_calc_tables.py +17 -10
  57. sempy_labs/directlake/_show_unsupported_directlake_objects.py +3 -2
  58. sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +10 -5
  59. sempy_labs/directlake/_update_directlake_partition_entity.py +169 -22
  60. sempy_labs/directlake/_warm_cache.py +7 -4
  61. sempy_labs/lakehouse/_get_lakehouse_columns.py +1 -1
  62. sempy_labs/lakehouse/_get_lakehouse_tables.py +65 -71
  63. sempy_labs/lakehouse/_lakehouse.py +5 -3
  64. sempy_labs/lakehouse/_shortcuts.py +20 -13
  65. sempy_labs/migration/__init__.py +1 -1
  66. sempy_labs/migration/_create_pqt_file.py +184 -186
  67. sempy_labs/migration/_migrate_calctables_to_lakehouse.py +240 -269
  68. sempy_labs/migration/_migrate_calctables_to_semantic_model.py +78 -77
  69. sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +444 -425
  70. sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +96 -102
  71. sempy_labs/migration/_migration_validation.py +2 -2
  72. sempy_labs/migration/_refresh_calc_tables.py +94 -100
  73. sempy_labs/report/_BPAReportTemplate.json +232 -0
  74. sempy_labs/report/__init__.py +6 -2
  75. sempy_labs/report/_bpareporttemplate/.pbi/localSettings.json +9 -0
  76. sempy_labs/report/_bpareporttemplate/.platform +11 -0
  77. sempy_labs/report/_bpareporttemplate/StaticResources/SharedResources/BaseThemes/CY24SU06.json +710 -0
  78. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/page.json +11 -0
  79. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/1b08bce3bebabb0a27a8/visual.json +191 -0
  80. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/2f22ddb70c301693c165/visual.json +438 -0
  81. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/3b1182230aa6c600b43a/visual.json +127 -0
  82. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/58577ba6380c69891500/visual.json +576 -0
  83. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/a2a8fa5028b3b776c96c/visual.json +207 -0
  84. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/adfd47ef30652707b987/visual.json +506 -0
  85. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/b6a80ee459e716e170b1/visual.json +127 -0
  86. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/ce3130a721c020cc3d81/visual.json +513 -0
  87. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/page.json +8 -0
  88. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/visuals/66e60dfb526437cd78d1/visual.json +112 -0
  89. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/page.json +11 -0
  90. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/07deb8bce824e1be37d7/visual.json +513 -0
  91. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0b1c68838818b32ad03b/visual.json +352 -0
  92. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0c171de9d2683d10b930/visual.json +37 -0
  93. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0efa01be0510e40a645e/visual.json +542 -0
  94. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/6bf2f0eb830ab53cc668/visual.json +221 -0
  95. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/88d8141cb8500b60030c/visual.json +127 -0
  96. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/a753273590beed656a03/visual.json +576 -0
  97. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/b8fdc82cddd61ac447bc/visual.json +127 -0
  98. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/page.json +9 -0
  99. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/visuals/ce8532a7e25020271077/visual.json +38 -0
  100. sempy_labs/report/_bpareporttemplate/definition/pages/pages.json +10 -0
  101. sempy_labs/report/_bpareporttemplate/definition/report.json +176 -0
  102. sempy_labs/report/_bpareporttemplate/definition/version.json +4 -0
  103. sempy_labs/report/_bpareporttemplate/definition.pbir +14 -0
  104. sempy_labs/report/_generate_report.py +260 -139
  105. sempy_labs/report/_report_functions.py +90 -59
  106. sempy_labs/report/_report_rebind.py +40 -34
  107. sempy_labs/tom/__init__.py +1 -4
  108. sempy_labs/tom/_model.py +601 -181
  109. semantic_link_labs-0.5.0.dist-info/METADATA +0 -22
  110. semantic_link_labs-0.5.0.dist-info/RECORD +0 -53
  111. sempy_labs/directlake/_fallback.py +0 -58
  112. {semantic_link_labs-0.5.0.dist-info → semantic_link_labs-0.7.0.dist-info}/LICENSE +0 -0
  113. {semantic_link_labs-0.5.0.dist-info → semantic_link_labs-0.7.0.dist-info}/top_level.txt +0 -0
sempy_labs/tom/_model.py CHANGED
@@ -6,7 +6,7 @@ from datetime import datetime
6
6
  from sempy_labs._helper_functions import format_dax_object_name
7
7
  from sempy_labs._list_functions import list_relationships
8
8
  from sempy_labs._refresh_semantic_model import refresh_semantic_model
9
- from sempy_labs.directlake._fallback import check_fallback_reason
9
+ from sempy_labs.directlake._dl_helper import check_fallback_reason
10
10
  from contextlib import contextmanager
11
11
  from typing import List, Iterator, Optional, Union, TYPE_CHECKING
12
12
  from sempy._utils._log import log
@@ -21,7 +21,8 @@ class TOMWrapper:
21
21
  """
22
22
  Convenience wrapper around the TOM object model for a semantic model. Always use the connect_semantic_model function to make sure the TOM object is initialized correctly.
23
23
 
24
- `XMLA read/write endpoints <https://learn.microsoft.com/power-bi/enterprise/service-premium-connect-tools#to-enable-read-write-for-a-premium-capacity>`_ must be enabled if setting the readonly parameter to False.
24
+ `XMLA read/write endpoints <https://learn.microsoft.com/power-bi/enterprise/service-premium-connect-tools#to-enable-read-write-for-a-premium-capacity>`_ must
25
+ be enabled if setting the readonly parameter to False.
25
26
  """
26
27
 
27
28
  _dataset: str
@@ -93,7 +94,9 @@ class TOMWrapper:
93
94
  import Microsoft.AnalysisServices.Tabular as TOM
94
95
 
95
96
  for t in self.model.Tables:
96
- if any(p.SourceType == TOM.PartitionSourceType.Calculated for p in t.Partitions):
97
+ if any(
98
+ p.SourceType == TOM.PartitionSourceType.Calculated for p in t.Partitions
99
+ ):
97
100
  yield t
98
101
 
99
102
  def all_calculation_groups(self):
@@ -226,6 +229,7 @@ class TOMWrapper:
226
229
  hidden: Optional[bool] = False,
227
230
  description: Optional[str] = None,
228
231
  display_folder: Optional[str] = None,
232
+ format_string_expression: Optional[str] = None,
229
233
  ):
230
234
  """
231
235
  Adds a measure to the semantic model.
@@ -246,6 +250,8 @@ class TOMWrapper:
246
250
  A description of the measure.
247
251
  display_folder : str, default=None
248
252
  The display folder in which the measure will reside.
253
+ format_string_expression : str, default=None
254
+ The format string expression.
249
255
  """
250
256
  import Microsoft.AnalysisServices.Tabular as TOM
251
257
 
@@ -259,6 +265,10 @@ class TOMWrapper:
259
265
  obj.Description = description
260
266
  if display_folder is not None:
261
267
  obj.DisplayFolder = display_folder
268
+ if format_string_expression is not None:
269
+ fsd = TOM.FormatStringDefinition()
270
+ fsd.Expression = format_string_expression
271
+ obj.FormatStringDefinition = fsd
262
272
 
263
273
  self.model.Tables[table_name].Measures.Add(obj)
264
274
 
@@ -496,11 +506,12 @@ class TOMWrapper:
496
506
  calculation_item_name: str,
497
507
  expression: str,
498
508
  ordinal: Optional[int] = None,
499
- format_string_expression: Optional[str] = None,
500
509
  description: Optional[str] = None,
510
+ format_string_expression: Optional[str] = None,
501
511
  ):
502
512
  """
503
- Adds a `calculation item <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.calculationitem?view=analysisservices-dotnet>`_ to a `calculation group <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.calculationgroup?view=analysisservices-dotnet>`_ within a semantic model.
513
+ Adds a `calculation item <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.calculationitem?view=analysisservices-dotnet>`_ to
514
+ a `calculation group <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.calculationgroup?view=analysisservices-dotnet>`_ within a semantic model.
504
515
 
505
516
  Parameters
506
517
  ----------
@@ -520,7 +531,6 @@ class TOMWrapper:
520
531
  import Microsoft.AnalysisServices.Tabular as TOM
521
532
 
522
533
  obj = TOM.CalculationItem()
523
- fsd = TOM.FormatStringDefinition()
524
534
  obj.Name = calculation_item_name
525
535
  obj.Expression = expression
526
536
  if ordinal is not None:
@@ -528,7 +538,9 @@ class TOMWrapper:
528
538
  if description is not None:
529
539
  obj.Description = description
530
540
  if format_string_expression is not None:
531
- obj.FormatStringDefinition = fsd.Expression = format_string_expression
541
+ fsd = TOM.FormatStringDefinition()
542
+ fsd.Expression = format_string_expression
543
+ obj.FormatStringDefinition = fsd
532
544
  self.model.Tables[table_name].CalculationGroup.CalculationItems.Add(obj)
533
545
 
534
546
  def add_role(
@@ -582,11 +594,15 @@ class TOMWrapper:
582
594
  tp.Table = self.model.Tables[table_name]
583
595
  tp.FilterExpression = filter_expression
584
596
 
585
- try:
597
+ if any(
598
+ t.Name == table_name and r.Name == role_name
599
+ for r in self.model.Roles
600
+ for t in r.TablePermissions
601
+ ):
586
602
  self.model.Roles[role_name].TablePermissions[
587
603
  table_name
588
604
  ].FilterExpression = filter_expression
589
- except:
605
+ else:
590
606
  self.model.Roles[role_name].TablePermissions.Add(tp)
591
607
 
592
608
  def set_ols(
@@ -613,17 +629,22 @@ class TOMWrapper:
613
629
  permission = permission.capitalize()
614
630
 
615
631
  if permission not in ["Read", "None", "Default"]:
616
- print(f"ERROR! Invalid 'permission' value.")
617
- return
632
+ raise ValueError(f"{icons.red_dot} Invalid 'permission' value.")
618
633
 
619
634
  cp = TOM.ColumnPermission()
620
635
  cp.Column = self.model.Tables[table_name].Columns[column_name]
621
636
  cp.MetadataPermission = System.Enum.Parse(TOM.MetadataPermission, permission)
622
- try:
637
+
638
+ if any(
639
+ c.Name == column_name and t.Name == table_name and r.Name == role_name
640
+ for r in self.model.Roles
641
+ for t in r.TablePermissions
642
+ for c in t.ColumnPermissions
643
+ ):
623
644
  self.model.Roles[role_name].TablePermissions[table_name].ColumnPermissions[
624
645
  column_name
625
646
  ].MetadataPermission = System.Enum.Parse(TOM.MetadataPermission, permission)
626
- except:
647
+ else:
627
648
  self.model.Roles[role_name].TablePermissions[
628
649
  table_name
629
650
  ].ColumnPermissions.Add(cp)
@@ -658,16 +679,22 @@ class TOMWrapper:
658
679
  import Microsoft.AnalysisServices.Tabular as TOM
659
680
 
660
681
  if isinstance(columns, str):
661
- raise ValueError(f"{icons.red_dot} The 'levels' parameter must be a list. For example: ['Continent', 'Country', 'City']")
662
-
682
+ raise ValueError(
683
+ f"{icons.red_dot} The 'levels' parameter must be a list. For example: ['Continent', 'Country', 'City']"
684
+ )
685
+
663
686
  if len(columns) == 1:
664
- raise ValueError(f"{icons.red_dot} There must be at least 2 levels in order to create a hierarchy.")
687
+ raise ValueError(
688
+ f"{icons.red_dot} There must be at least 2 levels in order to create a hierarchy."
689
+ )
665
690
 
666
691
  if levels is None:
667
692
  levels = columns
668
693
 
669
694
  if len(columns) != len(levels):
670
- raise ValueError(f"{icons.red_dot} If specifying level names, you must specify a level for each column.")
695
+ raise ValueError(
696
+ f"{icons.red_dot} If specifying level names, you must specify a level for each column."
697
+ )
671
698
 
672
699
  obj = TOM.Hierarchy()
673
700
  obj.Name = hierarchy_name
@@ -858,10 +885,8 @@ class TOMWrapper:
858
885
  cul = TOM.Culture()
859
886
  cul.Name = language
860
887
 
861
- try:
888
+ if not any(c.Name == language for c in self.model.Cultures):
862
889
  self.model.Cultures.Add(cul)
863
- except:
864
- pass
865
890
 
866
891
  def add_perspective(self, perspective_name: str):
867
892
  """
@@ -997,7 +1022,9 @@ class TOMWrapper:
997
1022
  import System
998
1023
 
999
1024
  if base_column is not None and base_table is None:
1000
- raise ValueError(f"{icons.red_dot} If you specify the base table you must also specify the base column")
1025
+ raise ValueError(
1026
+ f"{icons.red_dot} If you specify the base table you must also specify the base column"
1027
+ )
1001
1028
 
1002
1029
  summarization_type = (
1003
1030
  summarization_type.replace(" ", "")
@@ -1007,7 +1034,9 @@ class TOMWrapper:
1007
1034
 
1008
1035
  summarizationTypes = ["Sum", "GroupBy", "Count", "Min", "Max"]
1009
1036
  if summarization_type not in summarizationTypes:
1010
- raise ValueError(f"{icons.red_dot} The 'summarization_type' parameter must be one of the following valuse: {summarizationTypes}.")
1037
+ raise ValueError(
1038
+ f"{icons.red_dot} The 'summarization_type' parameter must be one of the following valuse: {summarizationTypes}."
1039
+ )
1011
1040
 
1012
1041
  ao = TOM.AlternateOf()
1013
1042
  ao.Summarization = System.Enum.Parse(TOM.SummarizationType, summarization_type)
@@ -1085,9 +1114,9 @@ class TOMWrapper:
1085
1114
  ann.Name = name
1086
1115
  ann.Value = value
1087
1116
 
1088
- try:
1117
+ if any(a.Name == name for a in object.Annotations):
1089
1118
  object.Annotations[name].Value = value
1090
- except:
1119
+ else:
1091
1120
  object.Annotations.Add(ann)
1092
1121
 
1093
1122
  def get_annotation_value(self, object, name: str):
@@ -1106,8 +1135,12 @@ class TOMWrapper:
1106
1135
  str
1107
1136
  The annotation value.
1108
1137
  """
1138
+ if any(a.Name == name for a in object.Annotations):
1139
+ value = object.Annotations[name].Value
1140
+ else:
1141
+ value = None
1109
1142
 
1110
- return object.Annotations[name].Value
1143
+ return value
1111
1144
 
1112
1145
  def remove_annotation(self, object, name: str):
1113
1146
  """
@@ -1185,9 +1218,9 @@ class TOMWrapper:
1185
1218
  ep.Name = name
1186
1219
  ep.Value = value
1187
1220
 
1188
- try:
1221
+ if any(a.Name == name for a in object.Annotations):
1189
1222
  object.ExtendedProperties[name].Value = value
1190
- except:
1223
+ else:
1191
1224
  object.ExtendedProperties.Add(ep)
1192
1225
 
1193
1226
  def get_extended_property_value(self, object, name: str):
@@ -1206,8 +1239,12 @@ class TOMWrapper:
1206
1239
  str
1207
1240
  The extended property value.
1208
1241
  """
1242
+ if any(a.Name == name for a in object.ExtendedProperties):
1243
+ value = object.ExtendedProperties[name].Value
1244
+ else:
1245
+ value = None
1209
1246
 
1210
- return object.ExtendedProperties[name].Value
1247
+ return value
1211
1248
 
1212
1249
  def remove_extended_property(self, object, name: str):
1213
1250
  """
@@ -1266,7 +1303,9 @@ class TOMWrapper:
1266
1303
  objectType = object.ObjectType
1267
1304
 
1268
1305
  if objectType not in validObjects:
1269
- raise ValueError(f"{icons.red_dot} Only the following object types are valid for perspectives: {validObjects}.")
1306
+ raise ValueError(
1307
+ f"{icons.red_dot} Only the following object types are valid for perspectives: {validObjects}."
1308
+ )
1270
1309
 
1271
1310
  object.Model.Perspectives[perspective_name]
1272
1311
 
@@ -1288,7 +1327,7 @@ class TOMWrapper:
1288
1327
  object.Parent.Name
1289
1328
  ].PerspectiveHierarchies[object.Name]
1290
1329
  return True
1291
- except:
1330
+ except Exception:
1292
1331
  return False
1293
1332
 
1294
1333
  def add_to_perspective(
@@ -1317,14 +1356,17 @@ class TOMWrapper:
1317
1356
  objectType = object.ObjectType
1318
1357
 
1319
1358
  if objectType not in validObjects:
1320
- raise ValueError(f"{icons.red_dot} Only the following object types are valid for perspectives: {validObjects}.")
1359
+ raise ValueError(
1360
+ f"{icons.red_dot} Only the following object types are valid for perspectives: {validObjects}."
1361
+ )
1321
1362
 
1322
- try:
1363
+ if any(p.Name == perspective_name for p in self.model.Perspectives):
1323
1364
  object.Model.Perspectives[perspective_name]
1324
- except:
1325
- raise ValueError(f"{icons.red_dot} The '{perspective_name}' perspective does not exist.")
1365
+ else:
1366
+ raise ValueError(
1367
+ f"{icons.red_dot} The '{perspective_name}' perspective does not exist."
1368
+ )
1326
1369
 
1327
- # try:
1328
1370
  if objectType == TOM.ObjectType.Table:
1329
1371
  pt = TOM.PerspectiveTable()
1330
1372
  pt.Table = object
@@ -1347,8 +1389,6 @@ class TOMWrapper:
1347
1389
  object.Model.Perspectives[perspective_name].PerspectiveTables[
1348
1390
  object.Parent.Name
1349
1391
  ].PerspectiveHierarchies.Add(ph)
1350
- # except:
1351
- # pass
1352
1392
 
1353
1393
  def remove_from_perspective(
1354
1394
  self,
@@ -1376,14 +1416,15 @@ class TOMWrapper:
1376
1416
  objectType = object.ObjectType
1377
1417
 
1378
1418
  if objectType not in validObjects:
1379
- raise ValueError(f"{icons.red_dot} Only the following object types are valid for perspectives: {validObjects}.")
1419
+ raise ValueError(
1420
+ f"{icons.red_dot} Only the following object types are valid for perspectives: {validObjects}."
1421
+ )
1380
1422
 
1381
- try:
1382
- object.Model.Perspectives[perspective_name]
1383
- except:
1384
- raise ValueError(f"{icons.red_dot} The '{perspective_name}' perspective does not exist.")
1423
+ if not any(p.Name == perspective_name for p in self.model.Perspectives):
1424
+ raise ValueError(
1425
+ f"{icons.red_dot} The '{perspective_name}' perspective does not exist."
1426
+ )
1385
1427
 
1386
- # try:
1387
1428
  if objectType == TOM.ObjectType.Table:
1388
1429
  pt = object.Model.Perspectives[perspective_name].PerspectiveTables[
1389
1430
  object.Name
@@ -1416,12 +1457,12 @@ class TOMWrapper:
1416
1457
  object.Model.Perspectives[perspective_name].PerspectiveTables[
1417
1458
  object.Parent.Name
1418
1459
  ].PerspectiveHierarchies.Remove(ph)
1419
- # except:
1420
- # pass
1421
1460
 
1422
1461
  def set_translation(
1423
1462
  self,
1424
- object: Union["TOM.Table", "TOM.Column", "TOM.Measure", "TOM.Hierarchy"],
1463
+ object: Union[
1464
+ "TOM.Table", "TOM.Column", "TOM.Measure", "TOM.Hierarchy", "TOM.Level"
1465
+ ],
1425
1466
  language: str,
1426
1467
  property: str,
1427
1468
  value: str,
@@ -1451,10 +1492,13 @@ class TOMWrapper:
1451
1492
  TOM.ObjectType.Column,
1452
1493
  TOM.ObjectType.Measure,
1453
1494
  TOM.ObjectType.Hierarchy,
1454
- ] # , 'Level'
1495
+ TOM.ObjectType.Level,
1496
+ ]
1455
1497
 
1456
1498
  if object.ObjectType not in validObjects:
1457
- raise ValueError(f"{icons.red_dot} Translations can only be set to {validObjects}.")
1499
+ raise ValueError(
1500
+ f"{icons.red_dot} Translations can only be set to {validObjects}."
1501
+ )
1458
1502
 
1459
1503
  mapping = {
1460
1504
  "Name": TOM.TranslatedProperty.Caption,
@@ -1463,21 +1507,38 @@ class TOMWrapper:
1463
1507
  }
1464
1508
 
1465
1509
  prop = mapping.get(property)
1466
- if prop == None:
1467
- raise ValueError(f"{icons.red_dot} Invalid property value. Please choose from the following: ['Name', 'Description', Display Folder].")
1510
+ if prop is None:
1511
+ raise ValueError(
1512
+ f"{icons.red_dot} Invalid property value. Please choose from the following: ['Name', 'Description', Display Folder]."
1513
+ )
1468
1514
 
1469
- try:
1470
- object.Model.Cultures[language]
1471
- except:
1472
- raise ValueError(f"{icons.red_dot} The '{language}' translation language does not exist in the semantic model.")
1515
+ if not any(c.Name == language for c in self.model.Cultures):
1516
+ raise ValueError(
1517
+ f"{icons.red_dot} The '{language}' translation language does not exist in the semantic model."
1518
+ )
1473
1519
 
1474
1520
  object.Model.Cultures[language].ObjectTranslations.SetTranslation(
1475
1521
  object, prop, value
1476
1522
  )
1477
1523
 
1524
+ if object.ObjectType in [TOM.ObjectType.Table, TOM.ObjectType.Measure]:
1525
+ print(
1526
+ f"{icons.green_dot} The {property} property for the '{object.Name}' {str(object.ObjectType).lower()} has been translated into '{language}' as '{value}'."
1527
+ )
1528
+ elif object.ObjectType in [
1529
+ TOM.ObjectType.Column,
1530
+ TOM.ObjectType.Hierarchy,
1531
+ TOM.ObjectType.Level,
1532
+ ]:
1533
+ print(
1534
+ f"{icons.green_dot} The {property} property for the '{object.Parent.Name}'[{object.Name}] {str(object.ObjectType).lower()} has been translated into '{language}' as '{value}'."
1535
+ )
1536
+
1478
1537
  def remove_translation(
1479
1538
  self,
1480
- object: Union["TOM.Table", "TOM.Column", "TOM.Measure", "TOM.Hierarchy"],
1539
+ object: Union[
1540
+ "TOM.Table", "TOM.Column", "TOM.Measure", "TOM.Hierarchy", "TOM.Level"
1541
+ ],
1481
1542
  language: str,
1482
1543
  ):
1483
1544
  """
@@ -1515,7 +1576,7 @@ class TOMWrapper:
1515
1576
  for lang in object.Model.Cultures:
1516
1577
  try:
1517
1578
  self.remove_translation(object=object, language=lang.Name)
1518
- except:
1579
+ except Exception:
1519
1580
  pass
1520
1581
  if objType in ["Table", "Column", "Measure", "Hierarchy"]:
1521
1582
  for persp in object.Model.Perspectives:
@@ -1523,10 +1584,12 @@ class TOMWrapper:
1523
1584
  self.remove_from_perspective(
1524
1585
  object=object, perspective_name=persp.Name
1525
1586
  )
1526
- except:
1587
+ except Exception:
1527
1588
  pass
1528
1589
 
1529
- if objType == TOM.ObjectType.Column:
1590
+ if objType == TOM.ObjectType.Table:
1591
+ object.Parent.Tables.Remove(object.Name)
1592
+ elif objType == TOM.ObjectType.Column:
1530
1593
  object.Parent.Columns.Remove(object.Name)
1531
1594
  elif objType == TOM.ObjectType.Measure:
1532
1595
  object.Parent.Measures.Remove(object.Name)
@@ -1631,12 +1694,12 @@ class TOMWrapper:
1631
1694
  objType = column.ObjectType
1632
1695
 
1633
1696
  if objType == TOM.ObjectType.Column:
1634
- for l in self.all_levels():
1697
+ for lev in self.all_levels():
1635
1698
  if (
1636
- l.Parent.Table.Name == column.Parent.Name
1637
- and l.Column.Name == column.Name
1699
+ lev.Parent.Table.Name == column.Parent.Name
1700
+ and lev.Column.Name == column.Name
1638
1701
  ):
1639
- yield l.Parent
1702
+ yield lev.Parent
1640
1703
 
1641
1704
  def used_in_sort_by(self, column: "TOM.Column"):
1642
1705
  """
@@ -1829,7 +1892,7 @@ class TOMWrapper:
1829
1892
  if m.Name in meas:
1830
1893
  yield m
1831
1894
 
1832
- def hybrid_tables(self):
1895
+ def all_hybrid_tables(self):
1833
1896
  """
1834
1897
  Outputs the `hybrid tables <https://learn.microsoft.com/power-bi/connect-data/service-dataset-modes-understand#hybrid-tables>`_ within a semantic model.
1835
1898
 
@@ -1848,7 +1911,7 @@ class TOMWrapper:
1848
1911
  if any(p.Mode == TOM.ModeType.DirectQuery for p in t.Partitions):
1849
1912
  yield t
1850
1913
 
1851
- def date_tables(self):
1914
+ def all_date_tables(self):
1852
1915
  """
1853
1916
  Outputs the tables which are marked as `date tables <https://learn.microsoft.com/power-bi/transform-model/desktop-date-tables>`_ within a semantic model.
1854
1917
 
@@ -1915,7 +1978,11 @@ class TOMWrapper:
1915
1978
  """
1916
1979
  import Microsoft.AnalysisServices.Tabular as TOM
1917
1980
 
1918
- return any(c.IsKey and c.DataType == TOM.DataType.DateTime for c in self.all_columns() if c.Parent.Name == table_name and c.Parent.DataCategory == 'Time')
1981
+ return any(
1982
+ c.IsKey and c.DataType == TOM.DataType.DateTime
1983
+ for c in self.all_columns()
1984
+ if c.Parent.Name == table_name and c.Parent.DataCategory == "Time"
1985
+ )
1919
1986
 
1920
1987
  def mark_as_date_table(self, table_name: str, column_name: str):
1921
1988
  """
@@ -1933,8 +2000,10 @@ class TOMWrapper:
1933
2000
  t = self.model.Tables[table_name]
1934
2001
  c = t.Columns[column_name]
1935
2002
  if c.DataType != TOM.DataType.DateTime:
1936
- raise ValueError(f"{icons.red_dot} The column specified in the 'column_name' parameter in this function must be of DateTime data type.")
1937
-
2003
+ raise ValueError(
2004
+ f"{icons.red_dot} The column specified in the 'column_name' parameter in this function must be of DateTime data type."
2005
+ )
2006
+
1938
2007
  daxQuery = f"""
1939
2008
  define measure '{table_name}'[test] =
1940
2009
  var mn = MIN('{table_name}'[{column_name}])
@@ -1953,7 +2022,9 @@ class TOMWrapper:
1953
2022
  )
1954
2023
  value = df["1"].iloc[0]
1955
2024
  if value != "1":
1956
- raise ValueError(f"{icons.red_dot} The '{column_name}' within the '{table_name}' table does not contain contiguous date values.")
2025
+ raise ValueError(
2026
+ f"{icons.red_dot} The '{column_name}' within the '{table_name}' table does not contain contiguous date values."
2027
+ )
1957
2028
 
1958
2029
  # Mark as a date table
1959
2030
  t.DataCategory = "Time"
@@ -2007,7 +2078,7 @@ class TOMWrapper:
2007
2078
  -------
2008
2079
  bool
2009
2080
  Indicates if the semantic model has a hybrid table.
2010
- """
2081
+ """
2011
2082
 
2012
2083
  return any(self.is_hybrid_table(table_name=t.Name) for t in self.model.Tables)
2013
2084
 
@@ -2148,12 +2219,19 @@ class TOMWrapper:
2148
2219
  # https://github.com/m-kovalsky/Tabular/blob/master/KPI%20Graphics.md
2149
2220
 
2150
2221
  if measure_name == target:
2151
- raise ValueError(f"{icons.red_dot} The 'target' parameter cannot be the same measure as the 'measure_name' parameter.")
2222
+ raise ValueError(
2223
+ f"{icons.red_dot} The 'target' parameter cannot be the same measure as the 'measure_name' parameter."
2224
+ )
2152
2225
 
2153
2226
  if status_graphic is None:
2154
2227
  status_graphic = "Three Circles Colored"
2155
2228
 
2156
- valid_status_types = ["Linear", "LinearReversed", "Centered", "CenteredReversed"]
2229
+ valid_status_types = [
2230
+ "Linear",
2231
+ "LinearReversed",
2232
+ "Centered",
2233
+ "CenteredReversed",
2234
+ ]
2157
2235
  status_type = status_type
2158
2236
  if status_type is None:
2159
2237
  status_type = "Linear"
@@ -2161,31 +2239,47 @@ class TOMWrapper:
2161
2239
  status_type = status_type.title().replace(" ", "")
2162
2240
 
2163
2241
  if status_type not in valid_status_types:
2164
- raise ValueError(f"{icons.red_dot} '{status_type}' is an invalid status_type. Please choose from these options: {valid_status_types}.")
2242
+ raise ValueError(
2243
+ f"{icons.red_dot} '{status_type}' is an invalid status_type. Please choose from these options: {valid_status_types}."
2244
+ )
2165
2245
 
2166
2246
  if status_type in ["Linear", "LinearReversed"]:
2167
2247
  if upper_bound is not None or lower_mid_bound is not None:
2168
- raise ValueError(f"{icons.red_dot} The 'upper_mid_bound' and 'lower_mid_bound' parameters are not used in the 'Linear' and 'LinearReversed' status types. Make sure these parameters are set to None.")
2248
+ raise ValueError(
2249
+ f"{icons.red_dot} The 'upper_mid_bound' and 'lower_mid_bound' parameters are not used in the 'Linear' and 'LinearReversed' status types. Make sure these parameters are set to None."
2250
+ )
2169
2251
 
2170
2252
  elif upper_bound <= lower_bound:
2171
- raise ValueError(f"{icons.red_dot} The upper_bound must be greater than the lower_bound.")
2253
+ raise ValueError(
2254
+ f"{icons.red_dot} The upper_bound must be greater than the lower_bound."
2255
+ )
2172
2256
 
2173
2257
  if status_type in ["Centered", "CenteredReversed"]:
2174
2258
  if upper_mid_bound is None or lower_mid_bound is None:
2175
- raise ValueError(f"{icons.red_dot} The 'upper_mid_bound' and 'lower_mid_bound' parameters are necessary in the 'Centered' and 'CenteredReversed' status types.")
2259
+ raise ValueError(
2260
+ f"{icons.red_dot} The 'upper_mid_bound' and 'lower_mid_bound' parameters are necessary in the 'Centered' and 'CenteredReversed' status types."
2261
+ )
2176
2262
  elif upper_bound <= upper_mid_bound:
2177
- raise ValueError(f"{icons.red_dot} The upper_bound must be greater than the upper_mid_bound.")
2263
+ raise ValueError(
2264
+ f"{icons.red_dot} The upper_bound must be greater than the upper_mid_bound."
2265
+ )
2178
2266
  elif upper_mid_bound <= lower_mid_bound:
2179
- raise ValueError(f"{icons.red_dot} The upper_mid_bound must be greater than the lower_mid_bound.")
2267
+ raise ValueError(
2268
+ f"{icons.red_dot} The upper_mid_bound must be greater than the lower_mid_bound."
2269
+ )
2180
2270
  elif lower_mid_bound <= lower_bound:
2181
- raise ValueError(f"{icons.red_dot} The lower_mid_bound must be greater than the lower_bound.")
2271
+ raise ValueError(
2272
+ f"{icons.red_dot} The lower_mid_bound must be greater than the lower_bound."
2273
+ )
2182
2274
 
2183
2275
  try:
2184
2276
  table_name = next(
2185
2277
  m.Parent.Name for m in self.all_measures() if m.Name == measure_name
2186
2278
  )
2187
- except:
2188
- raise ValueError(f"{icons.red_dot} The '{measure_name}' measure does not exist in the '{self._dataset}' semantic model within the '{self._workspace}'.")
2279
+ except Exception:
2280
+ raise ValueError(
2281
+ f"{icons.red_dot} The '{measure_name}' measure does not exist in the '{self._dataset}' semantic model within the '{self._workspace}'."
2282
+ )
2189
2283
 
2190
2284
  graphics = [
2191
2285
  "Cylinder",
@@ -2208,7 +2302,9 @@ class TOMWrapper:
2208
2302
  ]
2209
2303
 
2210
2304
  if status_graphic not in graphics:
2211
- raise ValueError(f"{icons.red_dot} The '{status_graphic}' status graphic is not valid. Please choose from these options: {graphics}.")
2305
+ raise ValueError(
2306
+ f"{icons.red_dot} The '{status_graphic}' status graphic is not valid. Please choose from these options: {graphics}."
2307
+ )
2212
2308
 
2213
2309
  measure_target = True
2214
2310
 
@@ -2216,16 +2312,18 @@ class TOMWrapper:
2216
2312
  float(target)
2217
2313
  tgt = str(target)
2218
2314
  measure_target = False
2219
- except:
2315
+ except Exception:
2220
2316
  try:
2221
2317
  tgt = next(
2222
2318
  format_dax_object_name(m.Parent.Name, m.Name)
2223
2319
  for m in self.all_measures()
2224
2320
  if m.Name == target
2225
2321
  )
2226
- except:
2227
- raise ValueError(f"{icons.red_dot} The '{target}' measure does not exist in the '{self._dataset}' semantic model within the '{self._workspace}'.")
2228
-
2322
+ except Exception:
2323
+ raise ValueError(
2324
+ f"{icons.red_dot} The '{target}' measure does not exist in the '{self._dataset}' semantic model within the '{self._workspace}'."
2325
+ )
2326
+
2229
2327
  if measure_target:
2230
2328
  expr = f"var x = [{measure_name}]/[{target}]\nreturn"
2231
2329
  else:
@@ -2246,11 +2344,11 @@ class TOMWrapper:
2246
2344
  kpi.StatusExpression = expr
2247
2345
 
2248
2346
  ms = self.model.Tables[table_name].Measures[measure_name]
2249
- try:
2347
+ if ms.KPI is not None:
2250
2348
  ms.KPI.TargetExpression = tgt
2251
2349
  ms.KPI.StatusGraphic = status_graphic
2252
2350
  ms.KPI.StatusExpression = expr
2253
- except:
2351
+ else:
2254
2352
  ms.KPI = kpi
2255
2353
 
2256
2354
  def set_aggregations(self, table_name: str, agg_table_name: str):
@@ -2269,6 +2367,8 @@ class TOMWrapper:
2269
2367
 
2270
2368
  """
2271
2369
 
2370
+ import Microsoft.AnalysisServices.Tabular as TOM
2371
+
2272
2372
  for c in self.model.Tables[agg_table_name].Columns:
2273
2373
 
2274
2374
  dataType = c.DataType
@@ -2306,7 +2406,7 @@ class TOMWrapper:
2306
2406
  The IsAvailableInMdx property value.
2307
2407
  """
2308
2408
 
2309
- self.model.Tables[table_name].Columns[column_name].IsAvailableInMdx = value
2409
+ self.model.Tables[table_name].Columns[column_name].IsAvailableInMDX = value
2310
2410
 
2311
2411
  def set_summarize_by(
2312
2412
  self, table_name: str, column_name: str, value: Optional[str] = None
@@ -2325,6 +2425,7 @@ class TOMWrapper:
2325
2425
  Defaults to none which resolves to 'Default'.
2326
2426
  `Aggregate valid values <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.aggregatefunction?view=analysisservices-dotnet>`_
2327
2427
  """
2428
+ import Microsoft.AnalysisServices.Tabular as TOM
2328
2429
  import System
2329
2430
 
2330
2431
  values = [
@@ -2348,7 +2449,9 @@ class TOMWrapper:
2348
2449
  )
2349
2450
 
2350
2451
  if value not in values:
2351
- raise ValueError(f"{icons.red_dot} '{value}' is not a valid value for the SummarizeBy property. These are the valid values: {values}.")
2452
+ raise ValueError(
2453
+ f"{icons.red_dot} '{value}' is not a valid value for the SummarizeBy property. These are the valid values: {values}."
2454
+ )
2352
2455
 
2353
2456
  self.model.Tables[table_name].Columns[column_name].SummarizeBy = (
2354
2457
  System.Enum.Parse(TOM.AggregateFunction, value)
@@ -2364,6 +2467,7 @@ class TOMWrapper:
2364
2467
  The DirectLakeBehavior property value.
2365
2468
  `DirectLakeBehavior valid values <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.directlakebehavior?view=analysisservices-dotnet>`_
2366
2469
  """
2470
+ import Microsoft.AnalysisServices.Tabular as TOM
2367
2471
  import System
2368
2472
 
2369
2473
  direct_lake_behavior = direct_lake_behavior.capitalize()
@@ -2377,7 +2481,9 @@ class TOMWrapper:
2377
2481
  dlValues = ["Automatic", "DirectLakeOnly", "DirectQueryOnly"]
2378
2482
 
2379
2483
  if direct_lake_behavior not in dlValues:
2380
- raise ValueError(f"{icons.red_dot} The 'direct_lake_behavior' parameter must be one of these values: {dlValues}.")
2484
+ raise ValueError(
2485
+ f"{icons.red_dot} The 'direct_lake_behavior' parameter must be one of these values: {dlValues}."
2486
+ )
2381
2487
 
2382
2488
  self.model.DirectLakeBehavior = System.Enum.Parse(
2383
2489
  TOM.DirectLakeBehavior, direct_lake_behavior
@@ -2458,7 +2564,9 @@ class TOMWrapper:
2458
2564
  t.Partitions.Add(par)
2459
2565
  self.model.Tables.Add(t)
2460
2566
 
2461
- def add_field_parameter(self, table_name: str, objects: List[str]):
2567
+ def add_field_parameter(
2568
+ self, table_name: str, objects: List[str], object_names: List[str] = None
2569
+ ):
2462
2570
  """
2463
2571
  Adds a `field parameter <https://learn.microsoft.com/power-bi/create-reports/power-bi-field-parameters>`_ to the semantic model.
2464
2572
 
@@ -2470,51 +2578,52 @@ class TOMWrapper:
2470
2578
  The columns/measures to be included in the field parameter.
2471
2579
  Columns must be specified as such : 'Table Name'[Column Name].
2472
2580
  Measures may be formatted as '[Measure Name]' or 'Measure Name'.
2581
+ object_names : List[str], default=None
2582
+ The corresponding visible name for the measures/columns in the objects list.
2583
+ Defaults to None which shows the measure/column name.
2473
2584
  """
2474
2585
 
2475
2586
  import Microsoft.AnalysisServices.Tabular as TOM
2476
2587
 
2477
2588
  if isinstance(objects, str):
2478
- raise ValueError(f"{icons.red_dot} The 'objects' parameter must be a list of columns/measures.")
2589
+ raise ValueError(
2590
+ f"{icons.red_dot} The 'objects' parameter must be a list of columns/measures."
2591
+ )
2479
2592
 
2480
2593
  if len(objects) == 1:
2481
- raise ValueError(f"{icons.red_dot} There must be more than one object (column/measure) within the objects parameter.")
2594
+ raise ValueError(
2595
+ f"{icons.red_dot} There must be more than one object (column/measure) within the objects parameter."
2596
+ )
2597
+
2598
+ if object_names is not None and len(objects) != len(object_names):
2599
+ raise ValueError(
2600
+ f"{icons.red_dot} If the 'object_names' parameter is specified, it must correspond exactly to the 'objects' parameter."
2601
+ )
2482
2602
 
2483
2603
  expr = ""
2484
2604
  i = 0
2485
2605
  for obj in objects:
2606
+ index = objects.index(obj)
2486
2607
  success = False
2487
2608
  for m in self.all_measures():
2488
- if obj == "[" + m.Name + "]" or obj == m.Name:
2489
- expr = (
2490
- expr
2491
- + "\n\t"
2492
- + '("'
2493
- + m.Name
2494
- + '", NAMEOF(['
2495
- + m.Name
2496
- + "]), "
2497
- + str(i)
2498
- + "),"
2499
- )
2609
+ obj_name = m.Name
2610
+ if obj == f"[{obj_name}]" or obj == obj_name:
2611
+ if object_names is not None:
2612
+ obj_name = object_names[index]
2613
+ expr = f'{expr}\n\t("{obj_name}", NAMEOF([{m.Name}]), {str(i)}),'
2500
2614
  success = True
2501
2615
  for c in self.all_columns():
2616
+ obj_name = c.Name
2502
2617
  fullObjName = format_dax_object_name(c.Parent.Name, c.Name)
2503
2618
  if obj == fullObjName or obj == c.Parent.Name + "[" + c.Name + "]":
2504
- expr = (
2505
- expr
2506
- + "\n\t"
2507
- + '("'
2508
- + c.Name
2509
- + '", NAMEOF('
2510
- + fullObjName
2511
- + "), "
2512
- + str(i)
2513
- + "),"
2514
- )
2619
+ if object_names is not None:
2620
+ obj_name = object_names[index]
2621
+ expr = f'{expr}\n\t("{obj_name}", NAMEOF({fullObjName}), {str(i)}),'
2515
2622
  success = True
2516
2623
  if not success:
2517
- raise ValueError(f"{icons.red_dot} The '{obj}' object was not found in the '{self._dataset}' semantic model.")
2624
+ raise ValueError(
2625
+ f"{icons.red_dot} The '{obj}' object was not found in the '{self._dataset}' semantic model."
2626
+ )
2518
2627
  else:
2519
2628
  i += 1
2520
2629
 
@@ -2682,7 +2791,7 @@ class TOMWrapper:
2682
2791
  try:
2683
2792
  runId = self.get_annotation_value(object=self.model, name="Vertipaq_Run")
2684
2793
  runId = str(int(runId) + 1)
2685
- except:
2794
+ except Exception:
2686
2795
  runId = "1"
2687
2796
  self.set_annotation(object=self.model, name="Vertipaq_Run", value=runId)
2688
2797
 
@@ -2711,7 +2820,7 @@ class TOMWrapper:
2711
2820
  object=object, name="Vertipaq_RecordCount"
2712
2821
  )
2713
2822
 
2714
- return int(result)
2823
+ return int(result) if result is not None else 0
2715
2824
 
2716
2825
  def records_per_segment(self, object: "TOM.Partition"):
2717
2826
  """
@@ -2736,7 +2845,7 @@ class TOMWrapper:
2736
2845
  object=object, name="Vertipaq_RecordsPerSegment"
2737
2846
  )
2738
2847
 
2739
- return float(result)
2848
+ return float(result) if result is not None else 0
2740
2849
 
2741
2850
  def used_size(self, object: Union["TOM.Hierarchy", "TOM.Relationship"]):
2742
2851
  """
@@ -2761,7 +2870,7 @@ class TOMWrapper:
2761
2870
  elif objType == TOM.ObjectType.Relationship:
2762
2871
  result = self.get_annotation_value(object=object, name="Vertipaq_UsedSize")
2763
2872
 
2764
- return int(result)
2873
+ return int(result) if result is not None else 0
2765
2874
 
2766
2875
  def data_size(self, column: "TOM.Column"):
2767
2876
  """
@@ -2784,7 +2893,7 @@ class TOMWrapper:
2784
2893
  if objType == TOM.ObjectType.Column:
2785
2894
  result = self.get_annotation_value(object=column, name="Vertipaq_DataSize")
2786
2895
 
2787
- return int(result)
2896
+ return int(result) if result is not None else 0
2788
2897
 
2789
2898
  def dictionary_size(self, column: "TOM.Column"):
2790
2899
  """
@@ -2809,7 +2918,7 @@ class TOMWrapper:
2809
2918
  object=column, name="Vertipaq_DictionarySize"
2810
2919
  )
2811
2920
 
2812
- return int(result)
2921
+ return int(result) if result is not None else 0
2813
2922
 
2814
2923
  def total_size(self, object: Union["TOM.Table", "TOM.Column"]):
2815
2924
  """
@@ -2834,7 +2943,7 @@ class TOMWrapper:
2834
2943
  elif objType == TOM.ObjectType.Table:
2835
2944
  result = self.get_annotation_value(object=object, name="Vertipaq_TotalSize")
2836
2945
 
2837
- return int(result)
2946
+ return int(result) if result is not None else 0
2838
2947
 
2839
2948
  def cardinality(self, column: "TOM.Column"):
2840
2949
  """
@@ -2859,7 +2968,7 @@ class TOMWrapper:
2859
2968
  object=column, name="Vertipaq_Cardinality"
2860
2969
  )
2861
2970
 
2862
- return int(result)
2971
+ return int(result) if result is not None else 0
2863
2972
 
2864
2973
  def depends_on(self, object, dependencies: pd.DataFrame):
2865
2974
  """
@@ -2887,7 +2996,7 @@ class TOMWrapper:
2887
2996
  objParentName = objName
2888
2997
 
2889
2998
  fil = dependencies[
2890
- (dependencies["Object Type"] == objType)
2999
+ (dependencies["Object Type"] == str(objType))
2891
3000
  & (dependencies["Table Name"] == objParentName)
2892
3001
  & (dependencies["Object Name"] == objName)
2893
3002
  ]
@@ -2944,7 +3053,7 @@ class TOMWrapper:
2944
3053
  objParentName = objName
2945
3054
 
2946
3055
  fil = dependencies[
2947
- (dependencies["Referenced Object Type"] == objType)
3056
+ (dependencies["Referenced Object Type"] == str(objType))
2948
3057
  & (dependencies["Referenced Table"] == objParentName)
2949
3058
  & (dependencies["Referenced Object"] == objName)
2950
3059
  ]
@@ -2991,7 +3100,7 @@ class TOMWrapper:
2991
3100
 
2992
3101
  for obj in self.depends_on(object=object, dependencies=dependencies):
2993
3102
  if obj.ObjectType == TOM.ObjectType.Measure:
2994
- if (obj.Parent.Name + obj.Name in object.Expression) or (
3103
+ if (f"{obj.Parent.Name}[{obj.Name}]" in object.Expression) or (
2995
3104
  format_dax_object_name(obj.Parent.Name, obj.Name)
2996
3105
  in object.Expression
2997
3106
  ):
@@ -3015,15 +3124,22 @@ class TOMWrapper:
3015
3124
  """
3016
3125
  import Microsoft.AnalysisServices.Tabular as TOM
3017
3126
 
3018
- def create_pattern(a, b):
3019
- return r"(?<!" + re.escape(a) + r"\[)(?<!" + re.escape(a) + r"'\[)" + re.escape(b)
3127
+ def create_pattern(tableList, b):
3128
+ patterns = [
3129
+ r"(?<!" + re.escape(table) + r"\[)(?<!" + re.escape(table) + r"'\[)"
3130
+ for table in tableList
3131
+ ]
3132
+ combined_pattern = "".join(patterns) + re.escape(b)
3133
+ return combined_pattern
3020
3134
 
3021
3135
  for obj in self.depends_on(object=object, dependencies=dependencies):
3022
3136
  if obj.ObjectType == TOM.ObjectType.Column:
3137
+ tableList = []
3138
+ for c in self.all_columns():
3139
+ if c.Name == obj.Name:
3140
+ tableList.append(c.Parent.Name)
3023
3141
  if (
3024
- re.search(
3025
- create_pattern(obj.Parent.Name, obj.Name), object.Expression
3026
- )
3142
+ re.search(create_pattern(tableList, obj.Name), object.Expression)
3027
3143
  is not None
3028
3144
  ):
3029
3145
  yield obj
@@ -3184,16 +3300,24 @@ class TOMWrapper:
3184
3300
  rolling_window_granularity = rolling_window_granularity.capitalize()
3185
3301
 
3186
3302
  if incremental_granularity not in incGran:
3187
- raise ValueError(f"{icons.red_dot} Invalid 'incremental_granularity' value. Please choose from the following options: {incGran}.")
3188
-
3303
+ raise ValueError(
3304
+ f"{icons.red_dot} Invalid 'incremental_granularity' value. Please choose from the following options: {incGran}."
3305
+ )
3306
+
3189
3307
  if rolling_window_granularity not in incGran:
3190
- raise ValueError(f"{icons.red_dot} Invalid 'rolling_window_granularity' value. Please choose from the following options: {incGran}.")
3308
+ raise ValueError(
3309
+ f"{icons.red_dot} Invalid 'rolling_window_granularity' value. Please choose from the following options: {incGran}."
3310
+ )
3191
3311
 
3192
3312
  if rolling_window_periods < 1:
3193
- raise ValueError(f"{icons.red_dot} Invalid 'rolling_window_periods' value. Must be a value greater than 0.")
3194
-
3313
+ raise ValueError(
3314
+ f"{icons.red_dot} Invalid 'rolling_window_periods' value. Must be a value greater than 0."
3315
+ )
3316
+
3195
3317
  if incremental_periods < 1:
3196
- raise ValueError(f"{icons.red_dot} Invalid 'incremental_periods' value. Must be a value greater than 0.")
3318
+ raise ValueError(
3319
+ f"{icons.red_dot} Invalid 'incremental_periods' value. Must be a value greater than 0."
3320
+ )
3197
3321
 
3198
3322
  t = self.model.Tables[table_name]
3199
3323
 
@@ -3201,7 +3325,9 @@ class TOMWrapper:
3201
3325
  dc = t.Columns[detect_data_changes_column]
3202
3326
 
3203
3327
  if dc.DataType != TOM.DataType.DateTime:
3204
- raise ValueError(f"{icons.red_dot} Invalid 'detect_data_changes_column' parameter. This column must be of DateTime data type.")
3328
+ raise ValueError(
3329
+ f"{icons.red_dot} Invalid 'detect_data_changes_column' parameter. This column must be of DateTime data type."
3330
+ )
3205
3331
 
3206
3332
  rp = TOM.BasicRefreshPolicy()
3207
3333
  rp.IncrementalPeriods = incremental_periods
@@ -3281,16 +3407,24 @@ class TOMWrapper:
3281
3407
  rolling_window_granularity = rolling_window_granularity.capitalize()
3282
3408
 
3283
3409
  if incremental_granularity not in incGran:
3284
- raise ValueError(f"{icons.red_dot} Invalid 'incremental_granularity' value. Please choose from the following options: {incGran}.")
3285
-
3410
+ raise ValueError(
3411
+ f"{icons.red_dot} Invalid 'incremental_granularity' value. Please choose from the following options: {incGran}."
3412
+ )
3413
+
3286
3414
  if rolling_window_granularity not in incGran:
3287
- raise ValueError(f"{icons.red_dot} Invalid 'rolling_window_granularity' value. Please choose from the following options: {incGran}.")
3415
+ raise ValueError(
3416
+ f"{icons.red_dot} Invalid 'rolling_window_granularity' value. Please choose from the following options: {incGran}."
3417
+ )
3288
3418
 
3289
3419
  if rolling_window_periods < 1:
3290
- raise ValueError(f"{icons.red_dot} Invalid 'rolling_window_periods' value. Must be a value greater than 0.")
3420
+ raise ValueError(
3421
+ f"{icons.red_dot} Invalid 'rolling_window_periods' value. Must be a value greater than 0."
3422
+ )
3291
3423
 
3292
3424
  if incremental_periods < 1:
3293
- raise ValueError(f"{icons.red_dot} Invalid 'incremental_periods' value. Must be a value greater than 0.")
3425
+ raise ValueError(
3426
+ f"{icons.red_dot} Invalid 'incremental_periods' value. Must be a value greater than 0."
3427
+ )
3294
3428
 
3295
3429
  date_format = "%m/%d/%Y"
3296
3430
 
@@ -3305,7 +3439,9 @@ class TOMWrapper:
3305
3439
  end_day = date_obj_end.day
3306
3440
 
3307
3441
  if date_obj_end <= date_obj_start:
3308
- raise ValueError(f"{icons.red_dot} Invalid 'start_date' or 'end_date'. The 'end_date' must be after the 'start_date'.")
3442
+ raise ValueError(
3443
+ f"{icons.red_dot} Invalid 'start_date' or 'end_date'. The 'end_date' must be after the 'start_date'."
3444
+ )
3309
3445
 
3310
3446
  t = self.model.Tables[table_name]
3311
3447
 
@@ -3314,14 +3450,18 @@ class TOMWrapper:
3314
3450
  dType = c.DataType
3315
3451
 
3316
3452
  if dType != TOM.DataType.DateTime:
3317
- raise ValueError(f"{icons.red_dot} The {fcName} column is of '{dType}' data type. The column chosen must be of DateTime data type.")
3453
+ raise ValueError(
3454
+ f"{icons.red_dot} The {fcName} column is of '{dType}' data type. The column chosen must be of DateTime data type."
3455
+ )
3318
3456
 
3319
3457
  if detect_data_changes_column is not None:
3320
3458
  dc = t.Columns[detect_data_changes_column]
3321
3459
  dcType = dc.DataType
3322
3460
 
3323
3461
  if dcType != TOM.DataType.DateTime:
3324
- raise ValueError(f"{icons.red_dot} Invalid 'detect_data_changes_column' parameter. This column must be of DateTime data type.")
3462
+ raise ValueError(
3463
+ f"{icons.red_dot} Invalid 'detect_data_changes_column' parameter. This column must be of DateTime data type."
3464
+ )
3325
3465
 
3326
3466
  # Start changes:
3327
3467
 
@@ -3329,7 +3469,9 @@ class TOMWrapper:
3329
3469
  i = 0
3330
3470
  for p in t.Partitions:
3331
3471
  if p.SourceType != TOM.PartitionSourceType.M:
3332
- raise ValueError(f"{icons.red_dot} Invalid partition source type. Incremental refresh can only be set up if the table's partition is an M-partition.")
3472
+ raise ValueError(
3473
+ f"{icons.red_dot} Invalid partition source type. Incremental refresh can only be set up if the table's partition is an M-partition."
3474
+ )
3333
3475
 
3334
3476
  elif i == 0:
3335
3477
  text = p.Expression
@@ -3442,9 +3584,13 @@ class TOMWrapper:
3442
3584
  ht = self.is_hybrid_table(table_name=table_name)
3443
3585
 
3444
3586
  if not ht:
3445
- raise ValueError(f"{icons.red_dot} The `data coverage definition <https://learn.microsoft.com/analysis-services/tom/table-partitions?view=asallproducts-allversions>`_ property is only applicable to `hybrid tables <https://learn.microsoft.com/power-bi/connect-data/service-dataset-modes-understand#hybrid-tables>`_. See the documentation: {doc}.")
3587
+ raise ValueError(
3588
+ f"{icons.red_dot} The `data coverage definition <https://learn.microsoft.com/analysis-services/tom/table-partitions?view=asallproducts-allversions>`_ property is only applicable to `hybrid tables <https://learn.microsoft.com/power-bi/connect-data/service-dataset-modes-understand#hybrid-tables>`_. See the documentation: {doc}."
3589
+ )
3446
3590
  if p.Mode != TOM.ModeType.DirectQuery:
3447
- raise ValueError(f"{icons.red_dot} The `data coverage definition <https://learn.microsoft.com/analysis-services/tom/table-partitions?view=asallproducts-allversions>`_ property is only applicable to the DirectQuery partition of a `hybrid table <https://learn.microsoft.com/power-bi/connect-data/service-dataset-modes-understand#hybrid-tables>`_. See the documentation: {doc}.")
3591
+ raise ValueError(
3592
+ f"{icons.red_dot} The `data coverage definition <https://learn.microsoft.com/analysis-services/tom/table-partitions?view=asallproducts-allversions>`_ property is only applicable to the DirectQuery partition of a `hybrid table <https://learn.microsoft.com/power-bi/connect-data/service-dataset-modes-understand#hybrid-tables>`_. See the documentation: {doc}."
3593
+ )
3448
3594
 
3449
3595
  dcd = TOM.DataCoverageDefinition()
3450
3596
  dcd.Expression = expression
@@ -3471,7 +3617,9 @@ class TOMWrapper:
3471
3617
  value = value.capitalize()
3472
3618
 
3473
3619
  if value not in values:
3474
- raise ValueError(f"{icons.red_dot} Invalid encoding hint value. Please choose from these options: {values}.")
3620
+ raise ValueError(
3621
+ f"{icons.red_dot} Invalid encoding hint value. Please choose from these options: {values}."
3622
+ )
3475
3623
 
3476
3624
  self.model.Tables[table_name].Columns[column_name].EncodingHint = (
3477
3625
  System.Enum.Parse(TOM.EncodingHintType, value)
@@ -3513,7 +3661,9 @@ class TOMWrapper:
3513
3661
  value = "Boolean"
3514
3662
 
3515
3663
  if value not in values:
3516
- raise ValueError(f"{icons.red_dot} Invalid data type. Please choose from these options: {values}.")
3664
+ raise ValueError(
3665
+ f"{icons.red_dot} Invalid data type. Please choose from these options: {values}."
3666
+ )
3517
3667
 
3518
3668
  self.model.Tables[table_name].Columns[column_name].DataType = System.Enum.Parse(
3519
3669
  TOM.DataType, value
@@ -3545,45 +3695,66 @@ class TOMWrapper:
3545
3695
  for t in time_intel:
3546
3696
  t = t.capitalize()
3547
3697
  if t not in [time_intel_options]:
3548
- raise ValueError(f"{icons.red_dot} The '{t}' time intelligence variation is not supported. Valid options: {time_intel_options}.")
3698
+ raise ValueError(
3699
+ f"{icons.red_dot} The '{t}' time intelligence variation is not supported. Valid options: {time_intel_options}."
3700
+ )
3549
3701
 
3550
3702
  # Validate measure and extract table name
3551
- for m in self.all_measures():
3552
- if m.Name == measure_name:
3553
- table_name = m.Parent.Name
3703
+ matching_measures = [
3704
+ m.Parent.Name for m in self.all_measures() if m.Name == measure_name
3705
+ ]
3554
3706
 
3555
3707
  if table_name is None:
3556
- raise ValueError(f"{icons.red_dot} The '{measure_name}' is not a valid measure in the '{self._dataset}' semantic model within the '{self._workspace}' workspace.")
3708
+ raise ValueError(
3709
+ f"{icons.red_dot} The '{measure_name}' is not a valid measure in the '{self._dataset}' semantic model within the '{self._workspace}' workspace."
3710
+ )
3557
3711
 
3712
+ table_name = matching_measures[0]
3558
3713
  # Validate date table
3559
3714
  if not self.is_date_table(date_table):
3560
- raise ValueError(f"{icons.red_dot} The '{date_table}' table is not a valid date table in the '{self._dataset}' wemantic model within the '{self._workspace}' workspace.")
3715
+ raise ValueError(
3716
+ f"{icons.red_dot} The '{date_table}' table is not a valid date table in the '{self._dataset}' wemantic model within the '{self._workspace}' workspace."
3717
+ )
3561
3718
 
3562
3719
  # Extract date key from date table
3563
- for c in self.all_columns():
3564
- if c.Parent.Name == date_table and c.IsKey:
3565
- date_key = c.Name
3720
+ matching_columns = [
3721
+ c.Name
3722
+ for c in self.all_columns()
3723
+ if c.Parent.Name == date_table and c.IsKey
3724
+ ]
3725
+
3726
+ if not matching_columns:
3727
+ raise ValueError(
3728
+ f"{icons.red_dot} The '{date_table}' table does not have a date key column in the '{self._dataset}' semantic model within the '{self._workspace}' workspace."
3729
+ )
3730
+
3731
+ date_key = matching_columns[0]
3566
3732
 
3567
3733
  # Create the new time intelligence measures
3568
3734
  for t in time_intel:
3569
- if t == "MTD":
3570
- expr = f"CALCULATE([{measure_name}],DATES{time_intel}('{date_table}'[{date_key}]))"
3571
- new_meas_name = f"{measure_name} {t}"
3572
- self.add_measure(
3573
- table_name=table_name,
3574
- measure_name=new_meas_name,
3575
- expression=expr,
3576
- )
3577
-
3578
- def update_m_partition(self, table_name: str, partition_name: str, expression: Optional[str | None] = None, mode: Optional[str | None] = None, description: Optional[str | None] = None):
3735
+ expr = f"CALCULATE([{measure_name}],DATES{t}('{date_table}'[{date_key}]))"
3736
+ new_meas_name = f"{measure_name} {t}"
3737
+ self.add_measure(
3738
+ table_name=table_name,
3739
+ measure_name=new_meas_name,
3740
+ expression=expr,
3741
+ )
3579
3742
 
3743
+ def update_m_partition(
3744
+ self,
3745
+ table_name: str,
3746
+ partition_name: str,
3747
+ expression: Optional[str] = None,
3748
+ mode: Optional[str] = None,
3749
+ description: Optional[str] = None,
3750
+ ):
3580
3751
  """
3581
3752
  Updates an M partition for a table within a semantic model.
3582
3753
 
3583
3754
  Parameters
3584
3755
  ----------
3585
3756
  table_name : str
3586
- Name of the table.
3757
+ Name of the table.
3587
3758
  partition_name : str
3588
3759
  Name of the partition.
3589
3760
  expression : str, default=None
@@ -3602,7 +3773,9 @@ class TOMWrapper:
3602
3773
 
3603
3774
  p = self.model.Tables[table_name].Partitions[partition_name]
3604
3775
  if p.SourceType != TOM.PartitionSourceType.M:
3605
- raise ValueError(f"Invalid partition source type. This function is only for M partitions.")
3776
+ raise ValueError(
3777
+ f"{icons.red_dot} Invalid partition source type. This function is only for M partitions."
3778
+ )
3606
3779
  if expression is not None:
3607
3780
  p.Source.Expression = expression
3608
3781
  if mode is not None:
@@ -3610,15 +3783,235 @@ class TOMWrapper:
3610
3783
  if description is not None:
3611
3784
  p.Description = description
3612
3785
 
3613
- def set_sort_by_column(self, table_name: str, column_name: str, sort_by_column: str):
3786
+ def update_measure(
3787
+ self,
3788
+ measure_name: str,
3789
+ expression: Optional[str] = None,
3790
+ format_string: Optional[str] = None,
3791
+ hidden: Optional[bool] = None,
3792
+ description: Optional[str] = None,
3793
+ display_folder: Optional[str] = None,
3794
+ format_string_expression: Optional[str] = None,
3795
+ ):
3796
+ """
3797
+ Updates a measure within a semantic model.
3798
+
3799
+ Parameters
3800
+ ----------
3801
+ measure_name : str
3802
+ Name of the measure.
3803
+ expression : str, default=None
3804
+ DAX expression of the measure.
3805
+ Defaults to None which keeps the existing setting.
3806
+ format_string : str, default=None
3807
+ Format string of the measure.
3808
+ Defaults to None which keeps the existing setting.
3809
+ hidden : bool, default=None
3810
+ Whether the measure will be hidden or visible.
3811
+ Defaults to None which keeps the existing setting.
3812
+ description : str, default=None
3813
+ A description of the measure.
3814
+ Defaults to None which keeps the existing setting.
3815
+ display_folder : str, default=None
3816
+ The display folder in which the measure will reside.
3817
+ Defaults to None which keeps the existing setting.
3818
+ format_string_expression : str, default=None
3819
+ The format string expression for the calculation item.
3820
+ Defaults to None which keeps the existing setting.
3821
+ """
3822
+
3823
+ table_name = next(
3824
+ m.Parent.Name for m in self.all_measures() if m.Name == measure_name
3825
+ )
3826
+ m = self.model.Tables[table_name].Measures[measure_name]
3827
+ if expression is not None:
3828
+ m.Expression = expression
3829
+ if format_string is not None:
3830
+ m.FormatString = format_string
3831
+ if hidden is not None:
3832
+ m.IsHidden = hidden
3833
+ if description is not None:
3834
+ m.Description = description
3835
+ if display_folder is not None:
3836
+ m.DisplayFolder = display_folder
3837
+ if format_string_expression is not None:
3838
+ fsd = TOM.FormatStringDefinition()
3839
+ fsd.Expression = format_string_expression
3840
+ m.FormatStringDefinition = fsd
3614
3841
 
3842
+ def update_column(
3843
+ self,
3844
+ table_name: str,
3845
+ column_name: str,
3846
+ source_column: Optional[str] = None,
3847
+ data_type: Optional[str] = None,
3848
+ expression: Optional[str] = None,
3849
+ format_string: Optional[str] = None,
3850
+ hidden: Optional[bool] = None,
3851
+ description: Optional[str] = None,
3852
+ display_folder: Optional[str] = None,
3853
+ data_category: Optional[str] = None,
3854
+ key: Optional[bool] = None,
3855
+ summarize_by: Optional[str] = None,
3856
+ ):
3857
+ """
3858
+ Updates a column within a semantic model.
3859
+
3860
+ Parameters
3861
+ ----------
3862
+ table_name : str
3863
+ Name of the table in which the column exists.
3864
+ column_name : str
3865
+ Name of the column.
3866
+ source_column : str, default=None
3867
+ The source column for the column (for data columns only).
3868
+ Defaults to None which keeps the existing setting.
3869
+ data_type : str, default=None
3870
+ The data type of the column.
3871
+ Defaults to None which keeps the existing setting.
3872
+ expression : str, default=None
3873
+ The DAX expression of the column (for calculated columns only).
3874
+ Defaults to None which keeps the existing setting.
3875
+ format_string : str, default=None
3876
+ Format string of the column.
3877
+ Defaults to None which keeps the existing setting.
3878
+ hidden : bool, default=None
3879
+ Whether the column will be hidden or visible.
3880
+ Defaults to None which keeps the existing setting.
3881
+ description : str, default=None
3882
+ A description of the column.
3883
+ Defaults to None which keeps the existing setting.
3884
+ display_folder : str, default=None
3885
+ The display folder in which the column will reside.
3886
+ Defaults to None which keeps the existing setting.
3887
+ data_category : str, default=None
3888
+ The data category of the column.
3889
+ Defaults to None which keeps the existing setting.
3890
+ key : bool, default=False
3891
+ Marks the column as the primary key of the table.
3892
+ Defaults to None which keeps the existing setting.
3893
+ summarize_by : str, default=None
3894
+ Sets the value for the Summarize By property of the column.
3895
+ Defaults to None which keeps the existing setting.
3896
+ """
3897
+
3898
+ import Microsoft.AnalysisServices.Tabular as TOM
3899
+ import System
3900
+
3901
+ c = self.model.Tables[table_name].Measures[column_name]
3902
+ if c.Type == TOM.ColumnType.Data:
3903
+ if source_column is not None:
3904
+ c.SourceColumn = source_column
3905
+ if c.Type == TOM.ColumnType.Calculated:
3906
+ if expression is not None:
3907
+ c.Expression = expression
3908
+ if data_type is not None:
3909
+ c.DataType = System.Enum.Parse(TOM.DataType, data_type)
3910
+ if format_string is not None:
3911
+ c.FormatString = format_string
3912
+ if hidden is not None:
3913
+ c.IsHidden = hidden
3914
+ if description is not None:
3915
+ c.Description = description
3916
+ if display_folder is not None:
3917
+ c.DisplayFolder = display_folder
3918
+ if key is not None:
3919
+ c.IsKey = key
3920
+ if data_category is not None:
3921
+ c.DataCategory = data_category
3922
+ if summarize_by is not None:
3923
+ c.SummarizeBy = System.Enum.Parse(TOM.AggregateFunction, summarize_by)
3924
+
3925
+ def update_role(
3926
+ self,
3927
+ role_name: str,
3928
+ model_permission: Optional[str] = None,
3929
+ description: Optional[str] = None,
3930
+ ):
3931
+ """
3932
+ Updates a role within a semantic model.
3933
+
3934
+ Parameters
3935
+ ----------
3936
+ role_name : str
3937
+ Name of the role.
3938
+ model_permission : str, default=None
3939
+ The model permission for the role.
3940
+ Defaults to None which keeps the existing setting.
3941
+ description : str, default=None
3942
+ The description of the role.
3943
+ Defaults to None which keeps the existing setting.
3944
+ """
3945
+
3946
+ import Microsoft.AnalysisServices.Tabular as TOM
3947
+ import System
3948
+
3949
+ obj = self.model.Roles[role_name]
3950
+
3951
+ if model_permission is not None:
3952
+ obj.ModelPermission = System.Enum.Parse(
3953
+ TOM.ModelPermission, model_permission
3954
+ )
3955
+ if description is not None:
3956
+ obj.Description = description
3957
+
3958
+ def update_calculation_item(
3959
+ self,
3960
+ table_name: str,
3961
+ calculation_item_name: str,
3962
+ expression: Optional[str] = None,
3963
+ ordinal: Optional[int] = None,
3964
+ description: Optional[str] = None,
3965
+ format_string_expression: Optional[str] = None,
3966
+ ):
3967
+ """
3968
+ Updates a calculation item within a semantic model.
3969
+
3970
+ Parameters
3971
+ ----------
3972
+ table_name : str
3973
+ Name of the calculation group (table).
3974
+ calculation_item_name : str
3975
+ Name of the calculation item.
3976
+ expression : str, default=None
3977
+ The DAX expression of the calculation item.
3978
+ Defaults to None which keeps the existing setting.
3979
+ ordinal : int, default=None
3980
+ The ordinal of the calculation item.
3981
+ Defaults to None which keeps the existing setting.
3982
+ description : str, default=None
3983
+ The description of the role.
3984
+ Defaults to None which keeps the existing setting.
3985
+ format_string_expression : str, default=None
3986
+ The format string expression for the calculation item.
3987
+ Defaults to None which keeps the existing setting.
3988
+ """
3989
+
3990
+ obj = self.model.Tables[table_name].CalculationGroup.CalculationItems[
3991
+ calculation_item_name
3992
+ ]
3993
+
3994
+ if expression is not None:
3995
+ obj.Expression = expression
3996
+ if format_string_expression is not None:
3997
+ fsd = TOM.FormatStringDefinition()
3998
+ fsd.Expression = format_string_expression
3999
+ obj.FormatStringDefinition.Expression = fsd
4000
+ if ordinal is not None:
4001
+ obj.Ordinal = ordinal
4002
+ if description is not None:
4003
+ obj.Description = description
4004
+
4005
+ def set_sort_by_column(
4006
+ self, table_name: str, column_name: str, sort_by_column: str
4007
+ ):
3615
4008
  """
3616
4009
  Sets the sort by column for a column in a semantic model.
3617
4010
 
3618
4011
  Parameters
3619
4012
  ----------
3620
4013
  table_name : str
3621
- Name of the table.
4014
+ Name of the table.
3622
4015
  column_name : str
3623
4016
  Name of the column.
3624
4017
  sort_by_column : str
@@ -3630,25 +4023,52 @@ class TOMWrapper:
3630
4023
  sbc = self.model.Tables[table_name].Columns[sort_by_column]
3631
4024
 
3632
4025
  if sbc.DataType != TOM.DataType.Int64:
3633
- raise ValueError(f"Invalid sort by column data type. The sort by column must be of 'Int64' data type.")
3634
-
4026
+ raise ValueError(
4027
+ f"{icons.red_dot} Invalid sort by column data type. The sort by column must be of 'Int64' data type."
4028
+ )
4029
+
3635
4030
  self.model.Tables[table_name].Columns[column_name].SortByColumn = sbc
3636
4031
 
3637
4032
  def remove_sort_by_column(self, table_name: str, column_name: str):
3638
-
3639
4033
  """
3640
4034
  Removes the sort by column for a column in a semantic model.
3641
4035
 
3642
4036
  Parameters
3643
4037
  ----------
3644
4038
  table_name : str
3645
- Name of the table.
4039
+ Name of the table.
3646
4040
  column_name : str
3647
4041
  Name of the column.
3648
4042
  """
3649
4043
 
3650
4044
  self.model.Tables[table_name].Columns[column_name].SortByColumn = None
3651
4045
 
4046
+ def is_calculated_table(self, table_name: str):
4047
+ """
4048
+ Identifies if a table is a calculated table.
4049
+
4050
+ Parameters
4051
+ ----------
4052
+ table_name : str
4053
+ Name of the table.
4054
+
4055
+ Returns
4056
+ -------
4057
+ bool
4058
+ A boolean value indicating whether the table is a calculated table.
4059
+ """
4060
+
4061
+ import Microsoft.AnalysisServices.Tabular as TOM
4062
+
4063
+ isCalcTable = False
4064
+ t = self.model.Tables[table_name]
4065
+ if t.ObjectType == TOM.ObjectType.Table:
4066
+ if any(
4067
+ p.SourceType == TOM.PartitionSourceType.Calculated for p in t.Partitions
4068
+ ):
4069
+ isCalcTable = True
4070
+ return isCalcTable
4071
+
3652
4072
  def close(self):
3653
4073
  if not self._readonly and self.model is not None:
3654
4074
  self.model.SaveChanges()
@@ -3700,4 +4120,4 @@ def connect_semantic_model(
3700
4120
  try:
3701
4121
  yield tw
3702
4122
  finally:
3703
- tw.close()
4123
+ tw.close()