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.
- semantic_link_labs-0.7.0.dist-info/METADATA +148 -0
- semantic_link_labs-0.7.0.dist-info/RECORD +111 -0
- {semantic_link_labs-0.5.0.dist-info → semantic_link_labs-0.7.0.dist-info}/WHEEL +1 -1
- sempy_labs/__init__.py +45 -15
- sempy_labs/_ai.py +42 -85
- sempy_labs/_bpa_translation/_translations_am-ET.po +828 -0
- sempy_labs/_bpa_translation/_translations_ar-AE.po +860 -0
- sempy_labs/_bpa_translation/_translations_cs-CZ.po +894 -0
- sempy_labs/_bpa_translation/_translations_da-DK.po +894 -0
- sempy_labs/_bpa_translation/_translations_de-DE.po +933 -0
- sempy_labs/_bpa_translation/_translations_el-GR.po +936 -0
- sempy_labs/_bpa_translation/_translations_es-ES.po +915 -0
- sempy_labs/_bpa_translation/_translations_fa-IR.po +883 -0
- sempy_labs/_bpa_translation/_translations_fr-FR.po +938 -0
- sempy_labs/_bpa_translation/_translations_ga-IE.po +912 -0
- sempy_labs/_bpa_translation/_translations_he-IL.po +855 -0
- sempy_labs/_bpa_translation/_translations_hi-IN.po +892 -0
- sempy_labs/_bpa_translation/_translations_hu-HU.po +910 -0
- sempy_labs/_bpa_translation/_translations_is-IS.po +887 -0
- sempy_labs/_bpa_translation/_translations_it-IT.po +931 -0
- sempy_labs/_bpa_translation/_translations_ja-JP.po +805 -0
- sempy_labs/_bpa_translation/_translations_nl-NL.po +924 -0
- sempy_labs/_bpa_translation/_translations_pl-PL.po +913 -0
- sempy_labs/_bpa_translation/_translations_pt-BR.po +909 -0
- sempy_labs/_bpa_translation/_translations_pt-PT.po +904 -0
- sempy_labs/_bpa_translation/_translations_ru-RU.po +909 -0
- sempy_labs/_bpa_translation/_translations_ta-IN.po +922 -0
- sempy_labs/_bpa_translation/_translations_te-IN.po +896 -0
- sempy_labs/_bpa_translation/_translations_th-TH.po +873 -0
- sempy_labs/_bpa_translation/_translations_zh-CN.po +767 -0
- sempy_labs/_bpa_translation/_translations_zu-ZA.po +916 -0
- sempy_labs/_clear_cache.py +12 -8
- sempy_labs/_connections.py +77 -70
- sempy_labs/_dax.py +7 -9
- sempy_labs/_generate_semantic_model.py +75 -90
- sempy_labs/_helper_functions.py +371 -20
- sempy_labs/_icons.py +23 -0
- sempy_labs/_list_functions.py +855 -427
- sempy_labs/_model_auto_build.py +4 -3
- sempy_labs/_model_bpa.py +307 -1118
- sempy_labs/_model_bpa_bulk.py +363 -0
- sempy_labs/_model_bpa_rules.py +831 -0
- sempy_labs/_model_dependencies.py +20 -16
- sempy_labs/_one_lake_integration.py +18 -12
- sempy_labs/_query_scale_out.py +116 -129
- sempy_labs/_refresh_semantic_model.py +23 -10
- sempy_labs/_translations.py +367 -288
- sempy_labs/_vertipaq.py +152 -123
- sempy_labs/directlake/__init__.py +7 -1
- sempy_labs/directlake/_directlake_schema_compare.py +33 -30
- sempy_labs/directlake/_directlake_schema_sync.py +60 -77
- sempy_labs/directlake/_dl_helper.py +233 -0
- sempy_labs/directlake/_get_directlake_lakehouse.py +7 -8
- sempy_labs/directlake/_get_shared_expression.py +5 -3
- sempy_labs/directlake/_guardrails.py +20 -16
- sempy_labs/directlake/_list_directlake_model_calc_tables.py +17 -10
- sempy_labs/directlake/_show_unsupported_directlake_objects.py +3 -2
- sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +10 -5
- sempy_labs/directlake/_update_directlake_partition_entity.py +169 -22
- sempy_labs/directlake/_warm_cache.py +7 -4
- sempy_labs/lakehouse/_get_lakehouse_columns.py +1 -1
- sempy_labs/lakehouse/_get_lakehouse_tables.py +65 -71
- sempy_labs/lakehouse/_lakehouse.py +5 -3
- sempy_labs/lakehouse/_shortcuts.py +20 -13
- sempy_labs/migration/__init__.py +1 -1
- sempy_labs/migration/_create_pqt_file.py +184 -186
- sempy_labs/migration/_migrate_calctables_to_lakehouse.py +240 -269
- sempy_labs/migration/_migrate_calctables_to_semantic_model.py +78 -77
- sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +444 -425
- sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +96 -102
- sempy_labs/migration/_migration_validation.py +2 -2
- sempy_labs/migration/_refresh_calc_tables.py +94 -100
- sempy_labs/report/_BPAReportTemplate.json +232 -0
- sempy_labs/report/__init__.py +6 -2
- sempy_labs/report/_bpareporttemplate/.pbi/localSettings.json +9 -0
- sempy_labs/report/_bpareporttemplate/.platform +11 -0
- sempy_labs/report/_bpareporttemplate/StaticResources/SharedResources/BaseThemes/CY24SU06.json +710 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/page.json +11 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/1b08bce3bebabb0a27a8/visual.json +191 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/2f22ddb70c301693c165/visual.json +438 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/3b1182230aa6c600b43a/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/58577ba6380c69891500/visual.json +576 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/a2a8fa5028b3b776c96c/visual.json +207 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/adfd47ef30652707b987/visual.json +506 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/b6a80ee459e716e170b1/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/ce3130a721c020cc3d81/visual.json +513 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/page.json +8 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/visuals/66e60dfb526437cd78d1/visual.json +112 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/page.json +11 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/07deb8bce824e1be37d7/visual.json +513 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0b1c68838818b32ad03b/visual.json +352 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0c171de9d2683d10b930/visual.json +37 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0efa01be0510e40a645e/visual.json +542 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/6bf2f0eb830ab53cc668/visual.json +221 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/88d8141cb8500b60030c/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/a753273590beed656a03/visual.json +576 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/b8fdc82cddd61ac447bc/visual.json +127 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/page.json +9 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/visuals/ce8532a7e25020271077/visual.json +38 -0
- sempy_labs/report/_bpareporttemplate/definition/pages/pages.json +10 -0
- sempy_labs/report/_bpareporttemplate/definition/report.json +176 -0
- sempy_labs/report/_bpareporttemplate/definition/version.json +4 -0
- sempy_labs/report/_bpareporttemplate/definition.pbir +14 -0
- sempy_labs/report/_generate_report.py +260 -139
- sempy_labs/report/_report_functions.py +90 -59
- sempy_labs/report/_report_rebind.py +40 -34
- sempy_labs/tom/__init__.py +1 -4
- sempy_labs/tom/_model.py +601 -181
- semantic_link_labs-0.5.0.dist-info/METADATA +0 -22
- semantic_link_labs-0.5.0.dist-info/RECORD +0 -53
- sempy_labs/directlake/_fallback.py +0 -58
- {semantic_link_labs-0.5.0.dist-info → semantic_link_labs-0.7.0.dist-info}/LICENSE +0 -0
- {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.
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1117
|
+
if any(a.Name == name for a in object.Annotations):
|
|
1089
1118
|
object.Annotations[name].Value = value
|
|
1090
|
-
|
|
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
|
|
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
|
-
|
|
1221
|
+
if any(a.Name == name for a in object.Annotations):
|
|
1189
1222
|
object.ExtendedProperties[name].Value = value
|
|
1190
|
-
|
|
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
|
|
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(
|
|
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(
|
|
1359
|
+
raise ValueError(
|
|
1360
|
+
f"{icons.red_dot} Only the following object types are valid for perspectives: {validObjects}."
|
|
1361
|
+
)
|
|
1321
1362
|
|
|
1322
|
-
|
|
1363
|
+
if any(p.Name == perspective_name for p in self.model.Perspectives):
|
|
1323
1364
|
object.Model.Perspectives[perspective_name]
|
|
1324
|
-
|
|
1325
|
-
raise ValueError(
|
|
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(
|
|
1419
|
+
raise ValueError(
|
|
1420
|
+
f"{icons.red_dot} Only the following object types are valid for perspectives: {validObjects}."
|
|
1421
|
+
)
|
|
1380
1422
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
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[
|
|
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
|
-
|
|
1495
|
+
TOM.ObjectType.Level,
|
|
1496
|
+
]
|
|
1455
1497
|
|
|
1456
1498
|
if object.ObjectType not in validObjects:
|
|
1457
|
-
raise ValueError(
|
|
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
|
|
1467
|
-
raise ValueError(
|
|
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
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
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[
|
|
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.
|
|
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
|
|
1697
|
+
for lev in self.all_levels():
|
|
1635
1698
|
if (
|
|
1636
|
-
|
|
1637
|
-
and
|
|
1699
|
+
lev.Parent.Table.Name == column.Parent.Name
|
|
1700
|
+
and lev.Column.Name == column.Name
|
|
1638
1701
|
):
|
|
1639
|
-
yield
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 = [
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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].
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
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
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
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(
|
|
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
|
|
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(
|
|
3019
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
3552
|
-
if m.Name == measure_name
|
|
3553
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
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
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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()
|