semantic-link-labs 0.4.2__py3-none-any.whl → 0.6.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.4.2.dist-info → semantic_link_labs-0.6.0.dist-info}/METADATA +2 -2
- semantic_link_labs-0.6.0.dist-info/RECORD +54 -0
- {semantic_link_labs-0.4.2.dist-info → semantic_link_labs-0.6.0.dist-info}/WHEEL +1 -1
- sempy_labs/__init__.py +44 -14
- sempy_labs/_ai.py +31 -32
- sempy_labs/_clear_cache.py +5 -8
- sempy_labs/_connections.py +80 -72
- sempy_labs/_dax.py +7 -9
- sempy_labs/_generate_semantic_model.py +60 -54
- sempy_labs/_helper_functions.py +8 -10
- sempy_labs/_icons.py +15 -0
- sempy_labs/_list_functions.py +1139 -428
- sempy_labs/_model_auto_build.py +5 -6
- sempy_labs/_model_bpa.py +134 -1125
- sempy_labs/_model_bpa_rules.py +831 -0
- sempy_labs/_model_dependencies.py +21 -25
- sempy_labs/_one_lake_integration.py +10 -7
- sempy_labs/_query_scale_out.py +83 -93
- sempy_labs/_refresh_semantic_model.py +12 -16
- sempy_labs/_translations.py +214 -288
- sempy_labs/_vertipaq.py +51 -42
- sempy_labs/directlake/__init__.py +2 -0
- sempy_labs/directlake/_directlake_schema_compare.py +12 -11
- sempy_labs/directlake/_directlake_schema_sync.py +13 -23
- sempy_labs/directlake/_fallback.py +5 -7
- sempy_labs/directlake/_get_directlake_lakehouse.py +1 -1
- sempy_labs/directlake/_get_shared_expression.py +4 -8
- sempy_labs/directlake/_guardrails.py +6 -8
- sempy_labs/directlake/_list_directlake_model_calc_tables.py +18 -12
- sempy_labs/directlake/_show_unsupported_directlake_objects.py +4 -4
- sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +9 -8
- sempy_labs/directlake/_update_directlake_partition_entity.py +129 -12
- sempy_labs/directlake/_warm_cache.py +5 -5
- sempy_labs/lakehouse/_get_lakehouse_columns.py +2 -2
- sempy_labs/lakehouse/_get_lakehouse_tables.py +4 -4
- sempy_labs/lakehouse/_lakehouse.py +3 -4
- sempy_labs/lakehouse/_shortcuts.py +17 -13
- sempy_labs/migration/__init__.py +1 -1
- sempy_labs/migration/_create_pqt_file.py +21 -24
- sempy_labs/migration/_migrate_calctables_to_lakehouse.py +16 -13
- sempy_labs/migration/_migrate_calctables_to_semantic_model.py +17 -18
- sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +45 -46
- sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +14 -14
- sempy_labs/migration/_migration_validation.py +6 -2
- sempy_labs/migration/_refresh_calc_tables.py +10 -5
- sempy_labs/report/__init__.py +2 -2
- sempy_labs/report/_generate_report.py +8 -7
- sempy_labs/report/_report_functions.py +47 -52
- sempy_labs/report/_report_rebind.py +38 -37
- sempy_labs/tom/__init__.py +1 -4
- sempy_labs/tom/_model.py +541 -180
- semantic_link_labs-0.4.2.dist-info/RECORD +0 -53
- {semantic_link_labs-0.4.2.dist-info → semantic_link_labs-0.6.0.dist-info}/LICENSE +0 -0
- {semantic_link_labs-0.4.2.dist-info → semantic_link_labs-0.6.0.dist-info}/top_level.txt +0 -0
sempy_labs/tom/_model.py
CHANGED
|
@@ -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):
|
|
@@ -500,7 +503,8 @@ class TOMWrapper:
|
|
|
500
503
|
description: Optional[str] = None,
|
|
501
504
|
):
|
|
502
505
|
"""
|
|
503
|
-
Adds a `calculation item <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.calculationitem?view=analysisservices-dotnet>`_ to
|
|
506
|
+
Adds a `calculation item <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.calculationitem?view=analysisservices-dotnet>`_ to
|
|
507
|
+
a `calculation group <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.calculationgroup?view=analysisservices-dotnet>`_ within a semantic model.
|
|
504
508
|
|
|
505
509
|
Parameters
|
|
506
510
|
----------
|
|
@@ -582,11 +586,15 @@ class TOMWrapper:
|
|
|
582
586
|
tp.Table = self.model.Tables[table_name]
|
|
583
587
|
tp.FilterExpression = filter_expression
|
|
584
588
|
|
|
585
|
-
|
|
589
|
+
if any(
|
|
590
|
+
t.Name == table_name and r.Name == role_name
|
|
591
|
+
for r in self.model.Roles
|
|
592
|
+
for t in r.TablePermissions
|
|
593
|
+
):
|
|
586
594
|
self.model.Roles[role_name].TablePermissions[
|
|
587
595
|
table_name
|
|
588
596
|
].FilterExpression = filter_expression
|
|
589
|
-
|
|
597
|
+
else:
|
|
590
598
|
self.model.Roles[role_name].TablePermissions.Add(tp)
|
|
591
599
|
|
|
592
600
|
def set_ols(
|
|
@@ -613,17 +621,22 @@ class TOMWrapper:
|
|
|
613
621
|
permission = permission.capitalize()
|
|
614
622
|
|
|
615
623
|
if permission not in ["Read", "None", "Default"]:
|
|
616
|
-
|
|
617
|
-
return
|
|
624
|
+
raise ValueError(f"{icons.red_dot} Invalid 'permission' value.")
|
|
618
625
|
|
|
619
626
|
cp = TOM.ColumnPermission()
|
|
620
627
|
cp.Column = self.model.Tables[table_name].Columns[column_name]
|
|
621
628
|
cp.MetadataPermission = System.Enum.Parse(TOM.MetadataPermission, permission)
|
|
622
|
-
|
|
629
|
+
|
|
630
|
+
if any(
|
|
631
|
+
c.Name == column_name and t.Name == table_name and r.Name == role_name
|
|
632
|
+
for r in self.model.Roles
|
|
633
|
+
for t in r.TablePermissions
|
|
634
|
+
for c in t.ColumnPermissions
|
|
635
|
+
):
|
|
623
636
|
self.model.Roles[role_name].TablePermissions[table_name].ColumnPermissions[
|
|
624
637
|
column_name
|
|
625
638
|
].MetadataPermission = System.Enum.Parse(TOM.MetadataPermission, permission)
|
|
626
|
-
|
|
639
|
+
else:
|
|
627
640
|
self.model.Roles[role_name].TablePermissions[
|
|
628
641
|
table_name
|
|
629
642
|
].ColumnPermissions.Add(cp)
|
|
@@ -658,22 +671,22 @@ class TOMWrapper:
|
|
|
658
671
|
import Microsoft.AnalysisServices.Tabular as TOM
|
|
659
672
|
|
|
660
673
|
if isinstance(columns, str):
|
|
661
|
-
|
|
674
|
+
raise ValueError(
|
|
662
675
|
f"{icons.red_dot} The 'levels' parameter must be a list. For example: ['Continent', 'Country', 'City']"
|
|
663
676
|
)
|
|
664
|
-
|
|
677
|
+
|
|
665
678
|
if len(columns) == 1:
|
|
666
|
-
|
|
667
|
-
|
|
679
|
+
raise ValueError(
|
|
680
|
+
f"{icons.red_dot} There must be at least 2 levels in order to create a hierarchy."
|
|
681
|
+
)
|
|
668
682
|
|
|
669
683
|
if levels is None:
|
|
670
684
|
levels = columns
|
|
671
685
|
|
|
672
686
|
if len(columns) != len(levels):
|
|
673
|
-
|
|
687
|
+
raise ValueError(
|
|
674
688
|
f"{icons.red_dot} If specifying level names, you must specify a level for each column."
|
|
675
689
|
)
|
|
676
|
-
return
|
|
677
690
|
|
|
678
691
|
obj = TOM.Hierarchy()
|
|
679
692
|
obj.Name = hierarchy_name
|
|
@@ -864,10 +877,8 @@ class TOMWrapper:
|
|
|
864
877
|
cul = TOM.Culture()
|
|
865
878
|
cul.Name = language
|
|
866
879
|
|
|
867
|
-
|
|
880
|
+
if not any(c.Name == language for c in self.model.Cultures):
|
|
868
881
|
self.model.Cultures.Add(cul)
|
|
869
|
-
except:
|
|
870
|
-
pass
|
|
871
882
|
|
|
872
883
|
def add_perspective(self, perspective_name: str):
|
|
873
884
|
"""
|
|
@@ -1003,7 +1014,7 @@ class TOMWrapper:
|
|
|
1003
1014
|
import System
|
|
1004
1015
|
|
|
1005
1016
|
if base_column is not None and base_table is None:
|
|
1006
|
-
|
|
1017
|
+
raise ValueError(
|
|
1007
1018
|
f"{icons.red_dot} If you specify the base table you must also specify the base column"
|
|
1008
1019
|
)
|
|
1009
1020
|
|
|
@@ -1015,10 +1026,9 @@ class TOMWrapper:
|
|
|
1015
1026
|
|
|
1016
1027
|
summarizationTypes = ["Sum", "GroupBy", "Count", "Min", "Max"]
|
|
1017
1028
|
if summarization_type not in summarizationTypes:
|
|
1018
|
-
|
|
1029
|
+
raise ValueError(
|
|
1019
1030
|
f"{icons.red_dot} The 'summarization_type' parameter must be one of the following valuse: {summarizationTypes}."
|
|
1020
1031
|
)
|
|
1021
|
-
return
|
|
1022
1032
|
|
|
1023
1033
|
ao = TOM.AlternateOf()
|
|
1024
1034
|
ao.Summarization = System.Enum.Parse(TOM.SummarizationType, summarization_type)
|
|
@@ -1096,9 +1106,9 @@ class TOMWrapper:
|
|
|
1096
1106
|
ann.Name = name
|
|
1097
1107
|
ann.Value = value
|
|
1098
1108
|
|
|
1099
|
-
|
|
1109
|
+
if any(a.Name == name for a in object.Annotations):
|
|
1100
1110
|
object.Annotations[name].Value = value
|
|
1101
|
-
|
|
1111
|
+
else:
|
|
1102
1112
|
object.Annotations.Add(ann)
|
|
1103
1113
|
|
|
1104
1114
|
def get_annotation_value(self, object, name: str):
|
|
@@ -1117,8 +1127,12 @@ class TOMWrapper:
|
|
|
1117
1127
|
str
|
|
1118
1128
|
The annotation value.
|
|
1119
1129
|
"""
|
|
1130
|
+
if any(a.Name == name for a in object.Annotations):
|
|
1131
|
+
value = object.Annotations[name].Value
|
|
1132
|
+
else:
|
|
1133
|
+
value = None
|
|
1120
1134
|
|
|
1121
|
-
return
|
|
1135
|
+
return value
|
|
1122
1136
|
|
|
1123
1137
|
def remove_annotation(self, object, name: str):
|
|
1124
1138
|
"""
|
|
@@ -1196,9 +1210,9 @@ class TOMWrapper:
|
|
|
1196
1210
|
ep.Name = name
|
|
1197
1211
|
ep.Value = value
|
|
1198
1212
|
|
|
1199
|
-
|
|
1213
|
+
if any(a.Name == name for a in object.Annotations):
|
|
1200
1214
|
object.ExtendedProperties[name].Value = value
|
|
1201
|
-
|
|
1215
|
+
else:
|
|
1202
1216
|
object.ExtendedProperties.Add(ep)
|
|
1203
1217
|
|
|
1204
1218
|
def get_extended_property_value(self, object, name: str):
|
|
@@ -1217,8 +1231,12 @@ class TOMWrapper:
|
|
|
1217
1231
|
str
|
|
1218
1232
|
The extended property value.
|
|
1219
1233
|
"""
|
|
1234
|
+
if any(a.Name == name for a in object.ExtendedProperties):
|
|
1235
|
+
value = object.ExtendedProperties[name].Value
|
|
1236
|
+
else:
|
|
1237
|
+
value = None
|
|
1220
1238
|
|
|
1221
|
-
return
|
|
1239
|
+
return value
|
|
1222
1240
|
|
|
1223
1241
|
def remove_extended_property(self, object, name: str):
|
|
1224
1242
|
"""
|
|
@@ -1277,10 +1295,9 @@ class TOMWrapper:
|
|
|
1277
1295
|
objectType = object.ObjectType
|
|
1278
1296
|
|
|
1279
1297
|
if objectType not in validObjects:
|
|
1280
|
-
|
|
1298
|
+
raise ValueError(
|
|
1281
1299
|
f"{icons.red_dot} Only the following object types are valid for perspectives: {validObjects}."
|
|
1282
1300
|
)
|
|
1283
|
-
return
|
|
1284
1301
|
|
|
1285
1302
|
object.Model.Perspectives[perspective_name]
|
|
1286
1303
|
|
|
@@ -1302,7 +1319,7 @@ class TOMWrapper:
|
|
|
1302
1319
|
object.Parent.Name
|
|
1303
1320
|
].PerspectiveHierarchies[object.Name]
|
|
1304
1321
|
return True
|
|
1305
|
-
except:
|
|
1322
|
+
except Exception:
|
|
1306
1323
|
return False
|
|
1307
1324
|
|
|
1308
1325
|
def add_to_perspective(
|
|
@@ -1331,17 +1348,17 @@ class TOMWrapper:
|
|
|
1331
1348
|
objectType = object.ObjectType
|
|
1332
1349
|
|
|
1333
1350
|
if objectType not in validObjects:
|
|
1334
|
-
|
|
1351
|
+
raise ValueError(
|
|
1335
1352
|
f"{icons.red_dot} Only the following object types are valid for perspectives: {validObjects}."
|
|
1336
1353
|
)
|
|
1337
|
-
|
|
1338
|
-
|
|
1354
|
+
|
|
1355
|
+
if any(p.Name == perspective_name for p in self.model.Perspectives):
|
|
1339
1356
|
object.Model.Perspectives[perspective_name]
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1357
|
+
else:
|
|
1358
|
+
raise ValueError(
|
|
1359
|
+
f"{icons.red_dot} The '{perspective_name}' perspective does not exist."
|
|
1360
|
+
)
|
|
1343
1361
|
|
|
1344
|
-
# try:
|
|
1345
1362
|
if objectType == TOM.ObjectType.Table:
|
|
1346
1363
|
pt = TOM.PerspectiveTable()
|
|
1347
1364
|
pt.Table = object
|
|
@@ -1364,8 +1381,6 @@ class TOMWrapper:
|
|
|
1364
1381
|
object.Model.Perspectives[perspective_name].PerspectiveTables[
|
|
1365
1382
|
object.Parent.Name
|
|
1366
1383
|
].PerspectiveHierarchies.Add(ph)
|
|
1367
|
-
# except:
|
|
1368
|
-
# pass
|
|
1369
1384
|
|
|
1370
1385
|
def remove_from_perspective(
|
|
1371
1386
|
self,
|
|
@@ -1393,17 +1408,15 @@ class TOMWrapper:
|
|
|
1393
1408
|
objectType = object.ObjectType
|
|
1394
1409
|
|
|
1395
1410
|
if objectType not in validObjects:
|
|
1396
|
-
|
|
1411
|
+
raise ValueError(
|
|
1397
1412
|
f"{icons.red_dot} Only the following object types are valid for perspectives: {validObjects}."
|
|
1398
1413
|
)
|
|
1399
|
-
return
|
|
1400
|
-
try:
|
|
1401
|
-
object.Model.Perspectives[perspective_name]
|
|
1402
|
-
except:
|
|
1403
|
-
print(f"{icons.red_dot} The '{perspective_name}' perspective does not exist.")
|
|
1404
|
-
return
|
|
1405
1414
|
|
|
1406
|
-
|
|
1415
|
+
if not any(p.Name == perspective_name for p in self.model.Perspectives):
|
|
1416
|
+
raise ValueError(
|
|
1417
|
+
f"{icons.red_dot} The '{perspective_name}' perspective does not exist."
|
|
1418
|
+
)
|
|
1419
|
+
|
|
1407
1420
|
if objectType == TOM.ObjectType.Table:
|
|
1408
1421
|
pt = object.Model.Perspectives[perspective_name].PerspectiveTables[
|
|
1409
1422
|
object.Name
|
|
@@ -1436,12 +1449,12 @@ class TOMWrapper:
|
|
|
1436
1449
|
object.Model.Perspectives[perspective_name].PerspectiveTables[
|
|
1437
1450
|
object.Parent.Name
|
|
1438
1451
|
].PerspectiveHierarchies.Remove(ph)
|
|
1439
|
-
# except:
|
|
1440
|
-
# pass
|
|
1441
1452
|
|
|
1442
1453
|
def set_translation(
|
|
1443
1454
|
self,
|
|
1444
|
-
object: Union[
|
|
1455
|
+
object: Union[
|
|
1456
|
+
"TOM.Table", "TOM.Column", "TOM.Measure", "TOM.Hierarchy", "TOM.Level"
|
|
1457
|
+
],
|
|
1445
1458
|
language: str,
|
|
1446
1459
|
property: str,
|
|
1447
1460
|
value: str,
|
|
@@ -1471,11 +1484,13 @@ class TOMWrapper:
|
|
|
1471
1484
|
TOM.ObjectType.Column,
|
|
1472
1485
|
TOM.ObjectType.Measure,
|
|
1473
1486
|
TOM.ObjectType.Hierarchy,
|
|
1474
|
-
|
|
1487
|
+
TOM.ObjectType.Level,
|
|
1488
|
+
]
|
|
1475
1489
|
|
|
1476
1490
|
if object.ObjectType not in validObjects:
|
|
1477
|
-
|
|
1478
|
-
|
|
1491
|
+
raise ValueError(
|
|
1492
|
+
f"{icons.red_dot} Translations can only be set to {validObjects}."
|
|
1493
|
+
)
|
|
1479
1494
|
|
|
1480
1495
|
mapping = {
|
|
1481
1496
|
"Name": TOM.TranslatedProperty.Caption,
|
|
@@ -1484,22 +1499,38 @@ class TOMWrapper:
|
|
|
1484
1499
|
}
|
|
1485
1500
|
|
|
1486
1501
|
prop = mapping.get(property)
|
|
1502
|
+
if prop is None:
|
|
1503
|
+
raise ValueError(
|
|
1504
|
+
f"{icons.red_dot} Invalid property value. Please choose from the following: ['Name', 'Description', Display Folder]."
|
|
1505
|
+
)
|
|
1487
1506
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
except:
|
|
1491
|
-
print(
|
|
1507
|
+
if not any(c.Name == language for c in self.model.Cultures):
|
|
1508
|
+
raise ValueError(
|
|
1492
1509
|
f"{icons.red_dot} The '{language}' translation language does not exist in the semantic model."
|
|
1493
1510
|
)
|
|
1494
|
-
return
|
|
1495
1511
|
|
|
1496
1512
|
object.Model.Cultures[language].ObjectTranslations.SetTranslation(
|
|
1497
1513
|
object, prop, value
|
|
1498
1514
|
)
|
|
1499
1515
|
|
|
1516
|
+
if object.ObjectType in [TOM.ObjectType.Table, TOM.ObjectType.Measure]:
|
|
1517
|
+
print(
|
|
1518
|
+
f"{icons.green_dot} The {property} property for the '{object.Name}' {str(object.ObjectType).lower()} has been translated into '{language}' as '{value}'."
|
|
1519
|
+
)
|
|
1520
|
+
elif object.ObjectType in [
|
|
1521
|
+
TOM.ObjectType.Column,
|
|
1522
|
+
TOM.ObjectType.Hierarchy,
|
|
1523
|
+
TOM.ObjectType.Level,
|
|
1524
|
+
]:
|
|
1525
|
+
print(
|
|
1526
|
+
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}'."
|
|
1527
|
+
)
|
|
1528
|
+
|
|
1500
1529
|
def remove_translation(
|
|
1501
1530
|
self,
|
|
1502
|
-
object: Union[
|
|
1531
|
+
object: Union[
|
|
1532
|
+
"TOM.Table", "TOM.Column", "TOM.Measure", "TOM.Hierarchy", "TOM.Level"
|
|
1533
|
+
],
|
|
1503
1534
|
language: str,
|
|
1504
1535
|
):
|
|
1505
1536
|
"""
|
|
@@ -1537,7 +1568,7 @@ class TOMWrapper:
|
|
|
1537
1568
|
for lang in object.Model.Cultures:
|
|
1538
1569
|
try:
|
|
1539
1570
|
self.remove_translation(object=object, language=lang.Name)
|
|
1540
|
-
except:
|
|
1571
|
+
except Exception:
|
|
1541
1572
|
pass
|
|
1542
1573
|
if objType in ["Table", "Column", "Measure", "Hierarchy"]:
|
|
1543
1574
|
for persp in object.Model.Perspectives:
|
|
@@ -1545,7 +1576,7 @@ class TOMWrapper:
|
|
|
1545
1576
|
self.remove_from_perspective(
|
|
1546
1577
|
object=object, perspective_name=persp.Name
|
|
1547
1578
|
)
|
|
1548
|
-
except:
|
|
1579
|
+
except Exception:
|
|
1549
1580
|
pass
|
|
1550
1581
|
|
|
1551
1582
|
if objType == TOM.ObjectType.Column:
|
|
@@ -1653,12 +1684,12 @@ class TOMWrapper:
|
|
|
1653
1684
|
objType = column.ObjectType
|
|
1654
1685
|
|
|
1655
1686
|
if objType == TOM.ObjectType.Column:
|
|
1656
|
-
for
|
|
1687
|
+
for lev in self.all_levels():
|
|
1657
1688
|
if (
|
|
1658
|
-
|
|
1659
|
-
and
|
|
1689
|
+
lev.Parent.Table.Name == column.Parent.Name
|
|
1690
|
+
and lev.Column.Name == column.Name
|
|
1660
1691
|
):
|
|
1661
|
-
yield
|
|
1692
|
+
yield lev.Parent
|
|
1662
1693
|
|
|
1663
1694
|
def used_in_sort_by(self, column: "TOM.Column"):
|
|
1664
1695
|
"""
|
|
@@ -1851,7 +1882,7 @@ class TOMWrapper:
|
|
|
1851
1882
|
if m.Name in meas:
|
|
1852
1883
|
yield m
|
|
1853
1884
|
|
|
1854
|
-
def
|
|
1885
|
+
def all_hybrid_tables(self):
|
|
1855
1886
|
"""
|
|
1856
1887
|
Outputs the `hybrid tables <https://learn.microsoft.com/power-bi/connect-data/service-dataset-modes-understand#hybrid-tables>`_ within a semantic model.
|
|
1857
1888
|
|
|
@@ -1870,7 +1901,7 @@ class TOMWrapper:
|
|
|
1870
1901
|
if any(p.Mode == TOM.ModeType.DirectQuery for p in t.Partitions):
|
|
1871
1902
|
yield t
|
|
1872
1903
|
|
|
1873
|
-
def
|
|
1904
|
+
def all_date_tables(self):
|
|
1874
1905
|
"""
|
|
1875
1906
|
Outputs the tables which are marked as `date tables <https://learn.microsoft.com/power-bi/transform-model/desktop-date-tables>`_ within a semantic model.
|
|
1876
1907
|
|
|
@@ -1937,7 +1968,11 @@ class TOMWrapper:
|
|
|
1937
1968
|
"""
|
|
1938
1969
|
import Microsoft.AnalysisServices.Tabular as TOM
|
|
1939
1970
|
|
|
1940
|
-
return any(
|
|
1971
|
+
return any(
|
|
1972
|
+
c.IsKey and c.DataType == TOM.DataType.DateTime
|
|
1973
|
+
for c in self.all_columns()
|
|
1974
|
+
if c.Parent.Name == table_name and c.Parent.DataCategory == "Time"
|
|
1975
|
+
)
|
|
1941
1976
|
|
|
1942
1977
|
def mark_as_date_table(self, table_name: str, column_name: str):
|
|
1943
1978
|
"""
|
|
@@ -1955,10 +1990,9 @@ class TOMWrapper:
|
|
|
1955
1990
|
t = self.model.Tables[table_name]
|
|
1956
1991
|
c = t.Columns[column_name]
|
|
1957
1992
|
if c.DataType != TOM.DataType.DateTime:
|
|
1958
|
-
|
|
1993
|
+
raise ValueError(
|
|
1959
1994
|
f"{icons.red_dot} The column specified in the 'column_name' parameter in this function must be of DateTime data type."
|
|
1960
1995
|
)
|
|
1961
|
-
return
|
|
1962
1996
|
|
|
1963
1997
|
daxQuery = f"""
|
|
1964
1998
|
define measure '{table_name}'[test] =
|
|
@@ -1978,10 +2012,9 @@ class TOMWrapper:
|
|
|
1978
2012
|
)
|
|
1979
2013
|
value = df["1"].iloc[0]
|
|
1980
2014
|
if value != "1":
|
|
1981
|
-
|
|
2015
|
+
raise ValueError(
|
|
1982
2016
|
f"{icons.red_dot} The '{column_name}' within the '{table_name}' table does not contain contiguous date values."
|
|
1983
2017
|
)
|
|
1984
|
-
return
|
|
1985
2018
|
|
|
1986
2019
|
# Mark as a date table
|
|
1987
2020
|
t.DataCategory = "Time"
|
|
@@ -2035,7 +2068,7 @@ class TOMWrapper:
|
|
|
2035
2068
|
-------
|
|
2036
2069
|
bool
|
|
2037
2070
|
Indicates if the semantic model has a hybrid table.
|
|
2038
|
-
"""
|
|
2071
|
+
"""
|
|
2039
2072
|
|
|
2040
2073
|
return any(self.is_hybrid_table(table_name=t.Name) for t in self.model.Tables)
|
|
2041
2074
|
|
|
@@ -2176,15 +2209,19 @@ class TOMWrapper:
|
|
|
2176
2209
|
# https://github.com/m-kovalsky/Tabular/blob/master/KPI%20Graphics.md
|
|
2177
2210
|
|
|
2178
2211
|
if measure_name == target:
|
|
2179
|
-
|
|
2212
|
+
raise ValueError(
|
|
2180
2213
|
f"{icons.red_dot} The 'target' parameter cannot be the same measure as the 'measure_name' parameter."
|
|
2181
2214
|
)
|
|
2182
|
-
return
|
|
2183
2215
|
|
|
2184
2216
|
if status_graphic is None:
|
|
2185
2217
|
status_graphic = "Three Circles Colored"
|
|
2186
2218
|
|
|
2187
|
-
valid_status_types = [
|
|
2219
|
+
valid_status_types = [
|
|
2220
|
+
"Linear",
|
|
2221
|
+
"LinearReversed",
|
|
2222
|
+
"Centered",
|
|
2223
|
+
"CenteredReversed",
|
|
2224
|
+
]
|
|
2188
2225
|
status_type = status_type
|
|
2189
2226
|
if status_type is None:
|
|
2190
2227
|
status_type = "Linear"
|
|
@@ -2192,43 +2229,47 @@ class TOMWrapper:
|
|
|
2192
2229
|
status_type = status_type.title().replace(" ", "")
|
|
2193
2230
|
|
|
2194
2231
|
if status_type not in valid_status_types:
|
|
2195
|
-
|
|
2232
|
+
raise ValueError(
|
|
2196
2233
|
f"{icons.red_dot} '{status_type}' is an invalid status_type. Please choose from these options: {valid_status_types}."
|
|
2197
2234
|
)
|
|
2198
|
-
return
|
|
2199
2235
|
|
|
2200
2236
|
if status_type in ["Linear", "LinearReversed"]:
|
|
2201
2237
|
if upper_bound is not None or lower_mid_bound is not None:
|
|
2202
|
-
|
|
2238
|
+
raise ValueError(
|
|
2203
2239
|
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."
|
|
2204
2240
|
)
|
|
2205
|
-
|
|
2241
|
+
|
|
2206
2242
|
elif upper_bound <= lower_bound:
|
|
2207
|
-
|
|
2208
|
-
|
|
2243
|
+
raise ValueError(
|
|
2244
|
+
f"{icons.red_dot} The upper_bound must be greater than the lower_bound."
|
|
2245
|
+
)
|
|
2209
2246
|
|
|
2210
2247
|
if status_type in ["Centered", "CenteredReversed"]:
|
|
2211
2248
|
if upper_mid_bound is None or lower_mid_bound is None:
|
|
2212
|
-
|
|
2249
|
+
raise ValueError(
|
|
2213
2250
|
f"{icons.red_dot} The 'upper_mid_bound' and 'lower_mid_bound' parameters are necessary in the 'Centered' and 'CenteredReversed' status types."
|
|
2214
2251
|
)
|
|
2215
|
-
return
|
|
2216
2252
|
elif upper_bound <= upper_mid_bound:
|
|
2217
|
-
|
|
2253
|
+
raise ValueError(
|
|
2254
|
+
f"{icons.red_dot} The upper_bound must be greater than the upper_mid_bound."
|
|
2255
|
+
)
|
|
2218
2256
|
elif upper_mid_bound <= lower_mid_bound:
|
|
2219
|
-
|
|
2257
|
+
raise ValueError(
|
|
2258
|
+
f"{icons.red_dot} The upper_mid_bound must be greater than the lower_mid_bound."
|
|
2259
|
+
)
|
|
2220
2260
|
elif lower_mid_bound <= lower_bound:
|
|
2221
|
-
|
|
2261
|
+
raise ValueError(
|
|
2262
|
+
f"{icons.red_dot} The lower_mid_bound must be greater than the lower_bound."
|
|
2263
|
+
)
|
|
2222
2264
|
|
|
2223
2265
|
try:
|
|
2224
2266
|
table_name = next(
|
|
2225
2267
|
m.Parent.Name for m in self.all_measures() if m.Name == measure_name
|
|
2226
2268
|
)
|
|
2227
|
-
except:
|
|
2228
|
-
|
|
2269
|
+
except Exception:
|
|
2270
|
+
raise ValueError(
|
|
2229
2271
|
f"{icons.red_dot} The '{measure_name}' measure does not exist in the '{self._dataset}' semantic model within the '{self._workspace}'."
|
|
2230
2272
|
)
|
|
2231
|
-
return
|
|
2232
2273
|
|
|
2233
2274
|
graphics = [
|
|
2234
2275
|
"Cylinder",
|
|
@@ -2251,10 +2292,9 @@ class TOMWrapper:
|
|
|
2251
2292
|
]
|
|
2252
2293
|
|
|
2253
2294
|
if status_graphic not in graphics:
|
|
2254
|
-
|
|
2295
|
+
raise ValueError(
|
|
2255
2296
|
f"{icons.red_dot} The '{status_graphic}' status graphic is not valid. Please choose from these options: {graphics}."
|
|
2256
2297
|
)
|
|
2257
|
-
return
|
|
2258
2298
|
|
|
2259
2299
|
measure_target = True
|
|
2260
2300
|
|
|
@@ -2262,15 +2302,15 @@ class TOMWrapper:
|
|
|
2262
2302
|
float(target)
|
|
2263
2303
|
tgt = str(target)
|
|
2264
2304
|
measure_target = False
|
|
2265
|
-
except:
|
|
2305
|
+
except Exception:
|
|
2266
2306
|
try:
|
|
2267
2307
|
tgt = next(
|
|
2268
2308
|
format_dax_object_name(m.Parent.Name, m.Name)
|
|
2269
2309
|
for m in self.all_measures()
|
|
2270
2310
|
if m.Name == target
|
|
2271
2311
|
)
|
|
2272
|
-
except:
|
|
2273
|
-
|
|
2312
|
+
except Exception:
|
|
2313
|
+
raise ValueError(
|
|
2274
2314
|
f"{icons.red_dot} The '{target}' measure does not exist in the '{self._dataset}' semantic model within the '{self._workspace}'."
|
|
2275
2315
|
)
|
|
2276
2316
|
|
|
@@ -2294,11 +2334,11 @@ class TOMWrapper:
|
|
|
2294
2334
|
kpi.StatusExpression = expr
|
|
2295
2335
|
|
|
2296
2336
|
ms = self.model.Tables[table_name].Measures[measure_name]
|
|
2297
|
-
|
|
2337
|
+
if ms.KPI is not None:
|
|
2298
2338
|
ms.KPI.TargetExpression = tgt
|
|
2299
2339
|
ms.KPI.StatusGraphic = status_graphic
|
|
2300
2340
|
ms.KPI.StatusExpression = expr
|
|
2301
|
-
|
|
2341
|
+
else:
|
|
2302
2342
|
ms.KPI = kpi
|
|
2303
2343
|
|
|
2304
2344
|
def set_aggregations(self, table_name: str, agg_table_name: str):
|
|
@@ -2317,6 +2357,8 @@ class TOMWrapper:
|
|
|
2317
2357
|
|
|
2318
2358
|
"""
|
|
2319
2359
|
|
|
2360
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
2361
|
+
|
|
2320
2362
|
for c in self.model.Tables[agg_table_name].Columns:
|
|
2321
2363
|
|
|
2322
2364
|
dataType = c.DataType
|
|
@@ -2354,7 +2396,7 @@ class TOMWrapper:
|
|
|
2354
2396
|
The IsAvailableInMdx property value.
|
|
2355
2397
|
"""
|
|
2356
2398
|
|
|
2357
|
-
self.model.Tables[table_name].Columns[column_name].
|
|
2399
|
+
self.model.Tables[table_name].Columns[column_name].IsAvailableInMDX = value
|
|
2358
2400
|
|
|
2359
2401
|
def set_summarize_by(
|
|
2360
2402
|
self, table_name: str, column_name: str, value: Optional[str] = None
|
|
@@ -2373,6 +2415,7 @@ class TOMWrapper:
|
|
|
2373
2415
|
Defaults to none which resolves to 'Default'.
|
|
2374
2416
|
`Aggregate valid values <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.aggregatefunction?view=analysisservices-dotnet>`_
|
|
2375
2417
|
"""
|
|
2418
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
2376
2419
|
import System
|
|
2377
2420
|
|
|
2378
2421
|
values = [
|
|
@@ -2396,10 +2439,9 @@ class TOMWrapper:
|
|
|
2396
2439
|
)
|
|
2397
2440
|
|
|
2398
2441
|
if value not in values:
|
|
2399
|
-
|
|
2442
|
+
raise ValueError(
|
|
2400
2443
|
f"{icons.red_dot} '{value}' is not a valid value for the SummarizeBy property. These are the valid values: {values}."
|
|
2401
2444
|
)
|
|
2402
|
-
return
|
|
2403
2445
|
|
|
2404
2446
|
self.model.Tables[table_name].Columns[column_name].SummarizeBy = (
|
|
2405
2447
|
System.Enum.Parse(TOM.AggregateFunction, value)
|
|
@@ -2415,6 +2457,7 @@ class TOMWrapper:
|
|
|
2415
2457
|
The DirectLakeBehavior property value.
|
|
2416
2458
|
`DirectLakeBehavior valid values <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.directlakebehavior?view=analysisservices-dotnet>`_
|
|
2417
2459
|
"""
|
|
2460
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
2418
2461
|
import System
|
|
2419
2462
|
|
|
2420
2463
|
direct_lake_behavior = direct_lake_behavior.capitalize()
|
|
@@ -2428,10 +2471,9 @@ class TOMWrapper:
|
|
|
2428
2471
|
dlValues = ["Automatic", "DirectLakeOnly", "DirectQueryOnly"]
|
|
2429
2472
|
|
|
2430
2473
|
if direct_lake_behavior not in dlValues:
|
|
2431
|
-
|
|
2474
|
+
raise ValueError(
|
|
2432
2475
|
f"{icons.red_dot} The 'direct_lake_behavior' parameter must be one of these values: {dlValues}."
|
|
2433
2476
|
)
|
|
2434
|
-
return
|
|
2435
2477
|
|
|
2436
2478
|
self.model.DirectLakeBehavior = System.Enum.Parse(
|
|
2437
2479
|
TOM.DirectLakeBehavior, direct_lake_behavior
|
|
@@ -2529,13 +2571,14 @@ class TOMWrapper:
|
|
|
2529
2571
|
import Microsoft.AnalysisServices.Tabular as TOM
|
|
2530
2572
|
|
|
2531
2573
|
if isinstance(objects, str):
|
|
2532
|
-
|
|
2533
|
-
|
|
2574
|
+
raise ValueError(
|
|
2575
|
+
f"{icons.red_dot} The 'objects' parameter must be a list of columns/measures."
|
|
2576
|
+
)
|
|
2577
|
+
|
|
2534
2578
|
if len(objects) == 1:
|
|
2535
|
-
|
|
2579
|
+
raise ValueError(
|
|
2536
2580
|
f"{icons.red_dot} There must be more than one object (column/measure) within the objects parameter."
|
|
2537
2581
|
)
|
|
2538
|
-
return
|
|
2539
2582
|
|
|
2540
2583
|
expr = ""
|
|
2541
2584
|
i = 0
|
|
@@ -2571,10 +2614,9 @@ class TOMWrapper:
|
|
|
2571
2614
|
)
|
|
2572
2615
|
success = True
|
|
2573
2616
|
if not success:
|
|
2574
|
-
|
|
2617
|
+
raise ValueError(
|
|
2575
2618
|
f"{icons.red_dot} The '{obj}' object was not found in the '{self._dataset}' semantic model."
|
|
2576
2619
|
)
|
|
2577
|
-
return
|
|
2578
2620
|
else:
|
|
2579
2621
|
i += 1
|
|
2580
2622
|
|
|
@@ -2742,7 +2784,7 @@ class TOMWrapper:
|
|
|
2742
2784
|
try:
|
|
2743
2785
|
runId = self.get_annotation_value(object=self.model, name="Vertipaq_Run")
|
|
2744
2786
|
runId = str(int(runId) + 1)
|
|
2745
|
-
except:
|
|
2787
|
+
except Exception:
|
|
2746
2788
|
runId = "1"
|
|
2747
2789
|
self.set_annotation(object=self.model, name="Vertipaq_Run", value=runId)
|
|
2748
2790
|
|
|
@@ -2771,7 +2813,7 @@ class TOMWrapper:
|
|
|
2771
2813
|
object=object, name="Vertipaq_RecordCount"
|
|
2772
2814
|
)
|
|
2773
2815
|
|
|
2774
|
-
return int(result)
|
|
2816
|
+
return int(result) if result is not None else 0
|
|
2775
2817
|
|
|
2776
2818
|
def records_per_segment(self, object: "TOM.Partition"):
|
|
2777
2819
|
"""
|
|
@@ -2796,7 +2838,7 @@ class TOMWrapper:
|
|
|
2796
2838
|
object=object, name="Vertipaq_RecordsPerSegment"
|
|
2797
2839
|
)
|
|
2798
2840
|
|
|
2799
|
-
return float(result)
|
|
2841
|
+
return float(result) if result is not None else 0
|
|
2800
2842
|
|
|
2801
2843
|
def used_size(self, object: Union["TOM.Hierarchy", "TOM.Relationship"]):
|
|
2802
2844
|
"""
|
|
@@ -2821,7 +2863,7 @@ class TOMWrapper:
|
|
|
2821
2863
|
elif objType == TOM.ObjectType.Relationship:
|
|
2822
2864
|
result = self.get_annotation_value(object=object, name="Vertipaq_UsedSize")
|
|
2823
2865
|
|
|
2824
|
-
return int(result)
|
|
2866
|
+
return int(result) if result is not None else 0
|
|
2825
2867
|
|
|
2826
2868
|
def data_size(self, column: "TOM.Column"):
|
|
2827
2869
|
"""
|
|
@@ -2844,7 +2886,7 @@ class TOMWrapper:
|
|
|
2844
2886
|
if objType == TOM.ObjectType.Column:
|
|
2845
2887
|
result = self.get_annotation_value(object=column, name="Vertipaq_DataSize")
|
|
2846
2888
|
|
|
2847
|
-
return int(result)
|
|
2889
|
+
return int(result) if result is not None else 0
|
|
2848
2890
|
|
|
2849
2891
|
def dictionary_size(self, column: "TOM.Column"):
|
|
2850
2892
|
"""
|
|
@@ -2869,7 +2911,7 @@ class TOMWrapper:
|
|
|
2869
2911
|
object=column, name="Vertipaq_DictionarySize"
|
|
2870
2912
|
)
|
|
2871
2913
|
|
|
2872
|
-
return int(result)
|
|
2914
|
+
return int(result) if result is not None else 0
|
|
2873
2915
|
|
|
2874
2916
|
def total_size(self, object: Union["TOM.Table", "TOM.Column"]):
|
|
2875
2917
|
"""
|
|
@@ -2894,7 +2936,7 @@ class TOMWrapper:
|
|
|
2894
2936
|
elif objType == TOM.ObjectType.Table:
|
|
2895
2937
|
result = self.get_annotation_value(object=object, name="Vertipaq_TotalSize")
|
|
2896
2938
|
|
|
2897
|
-
return int(result)
|
|
2939
|
+
return int(result) if result is not None else 0
|
|
2898
2940
|
|
|
2899
2941
|
def cardinality(self, column: "TOM.Column"):
|
|
2900
2942
|
"""
|
|
@@ -2919,7 +2961,7 @@ class TOMWrapper:
|
|
|
2919
2961
|
object=column, name="Vertipaq_Cardinality"
|
|
2920
2962
|
)
|
|
2921
2963
|
|
|
2922
|
-
return int(result)
|
|
2964
|
+
return int(result) if result is not None else 0
|
|
2923
2965
|
|
|
2924
2966
|
def depends_on(self, object, dependencies: pd.DataFrame):
|
|
2925
2967
|
"""
|
|
@@ -2947,7 +2989,7 @@ class TOMWrapper:
|
|
|
2947
2989
|
objParentName = objName
|
|
2948
2990
|
|
|
2949
2991
|
fil = dependencies[
|
|
2950
|
-
(dependencies["Object Type"] == objType)
|
|
2992
|
+
(dependencies["Object Type"] == str(objType))
|
|
2951
2993
|
& (dependencies["Table Name"] == objParentName)
|
|
2952
2994
|
& (dependencies["Object Name"] == objName)
|
|
2953
2995
|
]
|
|
@@ -3004,7 +3046,7 @@ class TOMWrapper:
|
|
|
3004
3046
|
objParentName = objName
|
|
3005
3047
|
|
|
3006
3048
|
fil = dependencies[
|
|
3007
|
-
(dependencies["Referenced Object Type"] == objType)
|
|
3049
|
+
(dependencies["Referenced Object Type"] == str(objType))
|
|
3008
3050
|
& (dependencies["Referenced Table"] == objParentName)
|
|
3009
3051
|
& (dependencies["Referenced Object"] == objName)
|
|
3010
3052
|
]
|
|
@@ -3051,7 +3093,7 @@ class TOMWrapper:
|
|
|
3051
3093
|
|
|
3052
3094
|
for obj in self.depends_on(object=object, dependencies=dependencies):
|
|
3053
3095
|
if obj.ObjectType == TOM.ObjectType.Measure:
|
|
3054
|
-
if (obj.Parent.Name
|
|
3096
|
+
if (f"{obj.Parent.Name}[{obj.Name}]" in object.Expression) or (
|
|
3055
3097
|
format_dax_object_name(obj.Parent.Name, obj.Name)
|
|
3056
3098
|
in object.Expression
|
|
3057
3099
|
):
|
|
@@ -3075,15 +3117,22 @@ class TOMWrapper:
|
|
|
3075
3117
|
"""
|
|
3076
3118
|
import Microsoft.AnalysisServices.Tabular as TOM
|
|
3077
3119
|
|
|
3078
|
-
def create_pattern(
|
|
3079
|
-
|
|
3120
|
+
def create_pattern(tableList, b):
|
|
3121
|
+
patterns = [
|
|
3122
|
+
r"(?<!" + re.escape(table) + r"\[)(?<!" + re.escape(table) + r"'\[)"
|
|
3123
|
+
for table in tableList
|
|
3124
|
+
]
|
|
3125
|
+
combined_pattern = "".join(patterns) + re.escape(b)
|
|
3126
|
+
return combined_pattern
|
|
3080
3127
|
|
|
3081
3128
|
for obj in self.depends_on(object=object, dependencies=dependencies):
|
|
3082
3129
|
if obj.ObjectType == TOM.ObjectType.Column:
|
|
3130
|
+
tableList = []
|
|
3131
|
+
for c in self.all_columns():
|
|
3132
|
+
if c.Name == obj.Name:
|
|
3133
|
+
tableList.append(c.Parent.Name)
|
|
3083
3134
|
if (
|
|
3084
|
-
re.search(
|
|
3085
|
-
create_pattern(obj.Parent.Name, obj.Name), object.Expression
|
|
3086
|
-
)
|
|
3135
|
+
re.search(create_pattern(tableList, obj.Name), object.Expression)
|
|
3087
3136
|
is not None
|
|
3088
3137
|
):
|
|
3089
3138
|
yield obj
|
|
@@ -3244,26 +3293,24 @@ class TOMWrapper:
|
|
|
3244
3293
|
rolling_window_granularity = rolling_window_granularity.capitalize()
|
|
3245
3294
|
|
|
3246
3295
|
if incremental_granularity not in incGran:
|
|
3247
|
-
|
|
3296
|
+
raise ValueError(
|
|
3248
3297
|
f"{icons.red_dot} Invalid 'incremental_granularity' value. Please choose from the following options: {incGran}."
|
|
3249
3298
|
)
|
|
3250
|
-
|
|
3299
|
+
|
|
3251
3300
|
if rolling_window_granularity not in incGran:
|
|
3252
|
-
|
|
3301
|
+
raise ValueError(
|
|
3253
3302
|
f"{icons.red_dot} Invalid 'rolling_window_granularity' value. Please choose from the following options: {incGran}."
|
|
3254
3303
|
)
|
|
3255
|
-
return
|
|
3256
3304
|
|
|
3257
3305
|
if rolling_window_periods < 1:
|
|
3258
|
-
|
|
3306
|
+
raise ValueError(
|
|
3259
3307
|
f"{icons.red_dot} Invalid 'rolling_window_periods' value. Must be a value greater than 0."
|
|
3260
3308
|
)
|
|
3261
|
-
|
|
3309
|
+
|
|
3262
3310
|
if incremental_periods < 1:
|
|
3263
|
-
|
|
3311
|
+
raise ValueError(
|
|
3264
3312
|
f"{icons.red_dot} Invalid 'incremental_periods' value. Must be a value greater than 0."
|
|
3265
3313
|
)
|
|
3266
|
-
return
|
|
3267
3314
|
|
|
3268
3315
|
t = self.model.Tables[table_name]
|
|
3269
3316
|
|
|
@@ -3271,10 +3318,9 @@ class TOMWrapper:
|
|
|
3271
3318
|
dc = t.Columns[detect_data_changes_column]
|
|
3272
3319
|
|
|
3273
3320
|
if dc.DataType != TOM.DataType.DateTime:
|
|
3274
|
-
|
|
3321
|
+
raise ValueError(
|
|
3275
3322
|
f"{icons.red_dot} Invalid 'detect_data_changes_column' parameter. This column must be of DateTime data type."
|
|
3276
3323
|
)
|
|
3277
|
-
return
|
|
3278
3324
|
|
|
3279
3325
|
rp = TOM.BasicRefreshPolicy()
|
|
3280
3326
|
rp.IncrementalPeriods = incremental_periods
|
|
@@ -3354,26 +3400,24 @@ class TOMWrapper:
|
|
|
3354
3400
|
rolling_window_granularity = rolling_window_granularity.capitalize()
|
|
3355
3401
|
|
|
3356
3402
|
if incremental_granularity not in incGran:
|
|
3357
|
-
|
|
3403
|
+
raise ValueError(
|
|
3358
3404
|
f"{icons.red_dot} Invalid 'incremental_granularity' value. Please choose from the following options: {incGran}."
|
|
3359
3405
|
)
|
|
3360
|
-
|
|
3406
|
+
|
|
3361
3407
|
if rolling_window_granularity not in incGran:
|
|
3362
|
-
|
|
3408
|
+
raise ValueError(
|
|
3363
3409
|
f"{icons.red_dot} Invalid 'rolling_window_granularity' value. Please choose from the following options: {incGran}."
|
|
3364
3410
|
)
|
|
3365
|
-
return
|
|
3366
3411
|
|
|
3367
3412
|
if rolling_window_periods < 1:
|
|
3368
|
-
|
|
3413
|
+
raise ValueError(
|
|
3369
3414
|
f"{icons.red_dot} Invalid 'rolling_window_periods' value. Must be a value greater than 0."
|
|
3370
3415
|
)
|
|
3371
|
-
|
|
3416
|
+
|
|
3372
3417
|
if incremental_periods < 1:
|
|
3373
|
-
|
|
3418
|
+
raise ValueError(
|
|
3374
3419
|
f"{icons.red_dot} Invalid 'incremental_periods' value. Must be a value greater than 0."
|
|
3375
3420
|
)
|
|
3376
|
-
return
|
|
3377
3421
|
|
|
3378
3422
|
date_format = "%m/%d/%Y"
|
|
3379
3423
|
|
|
@@ -3388,10 +3432,9 @@ class TOMWrapper:
|
|
|
3388
3432
|
end_day = date_obj_end.day
|
|
3389
3433
|
|
|
3390
3434
|
if date_obj_end <= date_obj_start:
|
|
3391
|
-
|
|
3435
|
+
raise ValueError(
|
|
3392
3436
|
f"{icons.red_dot} Invalid 'start_date' or 'end_date'. The 'end_date' must be after the 'start_date'."
|
|
3393
3437
|
)
|
|
3394
|
-
return
|
|
3395
3438
|
|
|
3396
3439
|
t = self.model.Tables[table_name]
|
|
3397
3440
|
|
|
@@ -3400,20 +3443,18 @@ class TOMWrapper:
|
|
|
3400
3443
|
dType = c.DataType
|
|
3401
3444
|
|
|
3402
3445
|
if dType != TOM.DataType.DateTime:
|
|
3403
|
-
|
|
3446
|
+
raise ValueError(
|
|
3404
3447
|
f"{icons.red_dot} The {fcName} column is of '{dType}' data type. The column chosen must be of DateTime data type."
|
|
3405
3448
|
)
|
|
3406
|
-
return
|
|
3407
3449
|
|
|
3408
3450
|
if detect_data_changes_column is not None:
|
|
3409
3451
|
dc = t.Columns[detect_data_changes_column]
|
|
3410
3452
|
dcType = dc.DataType
|
|
3411
3453
|
|
|
3412
3454
|
if dcType != TOM.DataType.DateTime:
|
|
3413
|
-
|
|
3455
|
+
raise ValueError(
|
|
3414
3456
|
f"{icons.red_dot} Invalid 'detect_data_changes_column' parameter. This column must be of DateTime data type."
|
|
3415
3457
|
)
|
|
3416
|
-
return
|
|
3417
3458
|
|
|
3418
3459
|
# Start changes:
|
|
3419
3460
|
|
|
@@ -3421,10 +3462,10 @@ class TOMWrapper:
|
|
|
3421
3462
|
i = 0
|
|
3422
3463
|
for p in t.Partitions:
|
|
3423
3464
|
if p.SourceType != TOM.PartitionSourceType.M:
|
|
3424
|
-
|
|
3465
|
+
raise ValueError(
|
|
3425
3466
|
f"{icons.red_dot} Invalid partition source type. Incremental refresh can only be set up if the table's partition is an M-partition."
|
|
3426
3467
|
)
|
|
3427
|
-
|
|
3468
|
+
|
|
3428
3469
|
elif i == 0:
|
|
3429
3470
|
text = p.Expression
|
|
3430
3471
|
text = text.rstrip()
|
|
@@ -3440,8 +3481,7 @@ class TOMWrapper:
|
|
|
3440
3481
|
|
|
3441
3482
|
print(text_before_last_match)
|
|
3442
3483
|
else:
|
|
3443
|
-
|
|
3444
|
-
return
|
|
3484
|
+
raise ValueError(f"{icons.red_dot} Invalid M-partition expression.")
|
|
3445
3485
|
|
|
3446
3486
|
endExpr = f'#"Filtered Rows IR" = Table.SelectRows({obj}, each [{column_name}] >= RangeStart and [{column_name}] <= RangeEnd)\n#"Filtered Rows IR"'
|
|
3447
3487
|
finalExpr = text_before_last_match + endExpr
|
|
@@ -3537,15 +3577,13 @@ class TOMWrapper:
|
|
|
3537
3577
|
ht = self.is_hybrid_table(table_name=table_name)
|
|
3538
3578
|
|
|
3539
3579
|
if not ht:
|
|
3540
|
-
|
|
3580
|
+
raise ValueError(
|
|
3541
3581
|
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}."
|
|
3542
3582
|
)
|
|
3543
|
-
return
|
|
3544
3583
|
if p.Mode != TOM.ModeType.DirectQuery:
|
|
3545
|
-
|
|
3584
|
+
raise ValueError(
|
|
3546
3585
|
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}."
|
|
3547
3586
|
)
|
|
3548
|
-
return
|
|
3549
3587
|
|
|
3550
3588
|
dcd = TOM.DataCoverageDefinition()
|
|
3551
3589
|
dcd.Expression = expression
|
|
@@ -3572,10 +3610,9 @@ class TOMWrapper:
|
|
|
3572
3610
|
value = value.capitalize()
|
|
3573
3611
|
|
|
3574
3612
|
if value not in values:
|
|
3575
|
-
|
|
3613
|
+
raise ValueError(
|
|
3576
3614
|
f"{icons.red_dot} Invalid encoding hint value. Please choose from these options: {values}."
|
|
3577
3615
|
)
|
|
3578
|
-
return
|
|
3579
3616
|
|
|
3580
3617
|
self.model.Tables[table_name].Columns[column_name].EncodingHint = (
|
|
3581
3618
|
System.Enum.Parse(TOM.EncodingHintType, value)
|
|
@@ -3617,10 +3654,9 @@ class TOMWrapper:
|
|
|
3617
3654
|
value = "Boolean"
|
|
3618
3655
|
|
|
3619
3656
|
if value not in values:
|
|
3620
|
-
|
|
3657
|
+
raise ValueError(
|
|
3621
3658
|
f"{icons.red_dot} Invalid data type. Please choose from these options: {values}."
|
|
3622
3659
|
)
|
|
3623
|
-
return
|
|
3624
3660
|
|
|
3625
3661
|
self.model.Tables[table_name].Columns[column_name].DataType = System.Enum.Parse(
|
|
3626
3662
|
TOM.DataType, value
|
|
@@ -3652,44 +3688,369 @@ class TOMWrapper:
|
|
|
3652
3688
|
for t in time_intel:
|
|
3653
3689
|
t = t.capitalize()
|
|
3654
3690
|
if t not in [time_intel_options]:
|
|
3655
|
-
|
|
3691
|
+
raise ValueError(
|
|
3656
3692
|
f"{icons.red_dot} The '{t}' time intelligence variation is not supported. Valid options: {time_intel_options}."
|
|
3657
3693
|
)
|
|
3658
|
-
return
|
|
3659
3694
|
|
|
3660
3695
|
# Validate measure and extract table name
|
|
3661
|
-
|
|
3662
|
-
if m.Name == measure_name
|
|
3663
|
-
|
|
3696
|
+
matching_measures = [
|
|
3697
|
+
m.Parent.Name for m in self.all_measures() if m.Name == measure_name
|
|
3698
|
+
]
|
|
3664
3699
|
|
|
3665
3700
|
if table_name is None:
|
|
3666
|
-
|
|
3701
|
+
raise ValueError(
|
|
3667
3702
|
f"{icons.red_dot} The '{measure_name}' is not a valid measure in the '{self._dataset}' semantic model within the '{self._workspace}' workspace."
|
|
3668
3703
|
)
|
|
3669
|
-
return
|
|
3670
3704
|
|
|
3705
|
+
table_name = matching_measures[0]
|
|
3671
3706
|
# Validate date table
|
|
3672
3707
|
if not self.is_date_table(date_table):
|
|
3673
|
-
|
|
3708
|
+
raise ValueError(
|
|
3674
3709
|
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."
|
|
3675
3710
|
)
|
|
3676
|
-
return
|
|
3677
3711
|
|
|
3678
3712
|
# Extract date key from date table
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3713
|
+
matching_columns = [
|
|
3714
|
+
c.Name
|
|
3715
|
+
for c in self.all_columns()
|
|
3716
|
+
if c.Parent.Name == date_table and c.IsKey
|
|
3717
|
+
]
|
|
3718
|
+
|
|
3719
|
+
if not matching_columns:
|
|
3720
|
+
raise ValueError(
|
|
3721
|
+
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."
|
|
3722
|
+
)
|
|
3723
|
+
|
|
3724
|
+
date_key = matching_columns[0]
|
|
3682
3725
|
|
|
3683
3726
|
# Create the new time intelligence measures
|
|
3684
3727
|
for t in time_intel:
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3728
|
+
expr = f"CALCULATE([{measure_name}],DATES{t}('{date_table}'[{date_key}]))"
|
|
3729
|
+
new_meas_name = f"{measure_name} {t}"
|
|
3730
|
+
self.add_measure(
|
|
3731
|
+
table_name=table_name,
|
|
3732
|
+
measure_name=new_meas_name,
|
|
3733
|
+
expression=expr,
|
|
3734
|
+
)
|
|
3735
|
+
|
|
3736
|
+
def update_m_partition(
|
|
3737
|
+
self,
|
|
3738
|
+
table_name: str,
|
|
3739
|
+
partition_name: str,
|
|
3740
|
+
expression: Optional[str | None] = None,
|
|
3741
|
+
mode: Optional[str | None] = None,
|
|
3742
|
+
description: Optional[str | None] = None,
|
|
3743
|
+
):
|
|
3744
|
+
"""
|
|
3745
|
+
Updates an M partition for a table within a semantic model.
|
|
3746
|
+
|
|
3747
|
+
Parameters
|
|
3748
|
+
----------
|
|
3749
|
+
table_name : str
|
|
3750
|
+
Name of the table.
|
|
3751
|
+
partition_name : str
|
|
3752
|
+
Name of the partition.
|
|
3753
|
+
expression : str, default=None
|
|
3754
|
+
The `M expression <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.mpartitionsource.expression?view=analysisservices-dotnet>`_ containing the logic for the partition.
|
|
3755
|
+
Defaults to None which keeps the existing setting.
|
|
3756
|
+
mode : str, default=None
|
|
3757
|
+
The query `mode <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.modetype?view=analysisservices-dotnet>`_ of the partition.
|
|
3758
|
+
Defaults to None which keeps the existing setting.
|
|
3759
|
+
description : str, default=None
|
|
3760
|
+
The description of the partition.
|
|
3761
|
+
Defaults to None which keeps the existing setting.
|
|
3762
|
+
"""
|
|
3763
|
+
|
|
3764
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
3765
|
+
import System
|
|
3766
|
+
|
|
3767
|
+
p = self.model.Tables[table_name].Partitions[partition_name]
|
|
3768
|
+
if p.SourceType != TOM.PartitionSourceType.M:
|
|
3769
|
+
raise ValueError(
|
|
3770
|
+
f"{icons.red_dot} Invalid partition source type. This function is only for M partitions."
|
|
3771
|
+
)
|
|
3772
|
+
if expression is not None:
|
|
3773
|
+
p.Source.Expression = expression
|
|
3774
|
+
if mode is not None:
|
|
3775
|
+
p.Mode = System.Enum.Parse(TOM.ModeType, mode)
|
|
3776
|
+
if description is not None:
|
|
3777
|
+
p.Description = description
|
|
3778
|
+
|
|
3779
|
+
def update_measure(
|
|
3780
|
+
self,
|
|
3781
|
+
measure_name: str,
|
|
3782
|
+
expression: Optional[str | None] = None,
|
|
3783
|
+
format_string: Optional[str | None] = None,
|
|
3784
|
+
hidden: Optional[bool | None] = None,
|
|
3785
|
+
description: Optional[str | None] = None,
|
|
3786
|
+
display_folder: Optional[str | None] = None,
|
|
3787
|
+
):
|
|
3788
|
+
"""
|
|
3789
|
+
Updates a measure within a semantic model.
|
|
3790
|
+
|
|
3791
|
+
Parameters
|
|
3792
|
+
----------
|
|
3793
|
+
measure_name : str
|
|
3794
|
+
Name of the measure.
|
|
3795
|
+
expression : str, default=None
|
|
3796
|
+
DAX expression of the measure.
|
|
3797
|
+
Defaults to None which keeps the existing setting.
|
|
3798
|
+
format_string : str, default=None
|
|
3799
|
+
Format string of the measure.
|
|
3800
|
+
Defaults to None which keeps the existing setting.
|
|
3801
|
+
hidden : bool, default=None
|
|
3802
|
+
Whether the measure will be hidden or visible.
|
|
3803
|
+
Defaults to None which keeps the existing setting.
|
|
3804
|
+
description : str, default=None
|
|
3805
|
+
A description of the measure.
|
|
3806
|
+
Defaults to None which keeps the existing setting.
|
|
3807
|
+
display_folder : str, default=None
|
|
3808
|
+
The display folder in which the measure will reside.
|
|
3809
|
+
Defaults to None which keeps the existing setting.
|
|
3810
|
+
"""
|
|
3811
|
+
|
|
3812
|
+
table_name = next(
|
|
3813
|
+
m.Parent.Name for m in self.all_measures() if m.Name == measure_name
|
|
3814
|
+
)
|
|
3815
|
+
m = self.model.Tables[table_name].Measures[measure_name]
|
|
3816
|
+
if expression is not None:
|
|
3817
|
+
m.Expression = expression
|
|
3818
|
+
if format_string is not None:
|
|
3819
|
+
m.FormatString = format_string
|
|
3820
|
+
if hidden is not None:
|
|
3821
|
+
m.IsHidden = hidden
|
|
3822
|
+
if description is not None:
|
|
3823
|
+
m.Description = description
|
|
3824
|
+
if display_folder is not None:
|
|
3825
|
+
m.DisplayFolder = display_folder
|
|
3826
|
+
|
|
3827
|
+
def update_column(
|
|
3828
|
+
self,
|
|
3829
|
+
table_name: str,
|
|
3830
|
+
column_name: str,
|
|
3831
|
+
source_column: Optional[str | None] = None,
|
|
3832
|
+
data_type: Optional[str | None] = None,
|
|
3833
|
+
expression: Optional[str | None] = None,
|
|
3834
|
+
format_string: Optional[str | None] = None,
|
|
3835
|
+
hidden: Optional[bool | None] = None,
|
|
3836
|
+
description: Optional[str | None] = None,
|
|
3837
|
+
display_folder: Optional[str | None] = None,
|
|
3838
|
+
data_category: Optional[str | None] = None,
|
|
3839
|
+
key: Optional[bool | None] = None,
|
|
3840
|
+
summarize_by: Optional[str | None] = None,
|
|
3841
|
+
):
|
|
3842
|
+
"""
|
|
3843
|
+
Updates a column within a semantic model.
|
|
3844
|
+
|
|
3845
|
+
Parameters
|
|
3846
|
+
----------
|
|
3847
|
+
table_name : str
|
|
3848
|
+
Name of the table in which the column exists.
|
|
3849
|
+
column_name : str
|
|
3850
|
+
Name of the column.
|
|
3851
|
+
source_column : str, default=None
|
|
3852
|
+
The source column for the column (for data columns only).
|
|
3853
|
+
Defaults to None which keeps the existing setting.
|
|
3854
|
+
data_type : str, default=None
|
|
3855
|
+
The data type of the column.
|
|
3856
|
+
Defaults to None which keeps the existing setting.
|
|
3857
|
+
expression : str, default=None
|
|
3858
|
+
The DAX expression of the column (for calculated columns only).
|
|
3859
|
+
Defaults to None which keeps the existing setting.
|
|
3860
|
+
format_string : str, default=None
|
|
3861
|
+
Format string of the column.
|
|
3862
|
+
Defaults to None which keeps the existing setting.
|
|
3863
|
+
hidden : bool, default=None
|
|
3864
|
+
Whether the column will be hidden or visible.
|
|
3865
|
+
Defaults to None which keeps the existing setting.
|
|
3866
|
+
description : str, default=None
|
|
3867
|
+
A description of the column.
|
|
3868
|
+
Defaults to None which keeps the existing setting.
|
|
3869
|
+
display_folder : str, default=None
|
|
3870
|
+
The display folder in which the column will reside.
|
|
3871
|
+
Defaults to None which keeps the existing setting.
|
|
3872
|
+
data_category : str, default=None
|
|
3873
|
+
The data category of the column.
|
|
3874
|
+
Defaults to None which keeps the existing setting.
|
|
3875
|
+
key : bool, default=False
|
|
3876
|
+
Marks the column as the primary key of the table.
|
|
3877
|
+
Defaults to None which keeps the existing setting.
|
|
3878
|
+
summarize_by : str, default=None
|
|
3879
|
+
Sets the value for the Summarize By property of the column.
|
|
3880
|
+
Defaults to None which keeps the existing setting.
|
|
3881
|
+
"""
|
|
3882
|
+
|
|
3883
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
3884
|
+
import System
|
|
3885
|
+
|
|
3886
|
+
c = self.model.Tables[table_name].Measures[column_name]
|
|
3887
|
+
if c.Type == TOM.ColumnType.Data:
|
|
3888
|
+
if source_column is not None:
|
|
3889
|
+
c.SourceColumn = source_column
|
|
3890
|
+
if c.Type == TOM.ColumnType.Calculated:
|
|
3891
|
+
if expression is not None:
|
|
3892
|
+
c.Expression = expression
|
|
3893
|
+
if data_type is not None:
|
|
3894
|
+
c.DataType = System.Enum.Parse(TOM.DataType, data_type)
|
|
3895
|
+
if format_string is not None:
|
|
3896
|
+
c.FormatString = format_string
|
|
3897
|
+
if hidden is not None:
|
|
3898
|
+
c.IsHidden = hidden
|
|
3899
|
+
if description is not None:
|
|
3900
|
+
c.Description = description
|
|
3901
|
+
if display_folder is not None:
|
|
3902
|
+
c.DisplayFolder = display_folder
|
|
3903
|
+
if key is not None:
|
|
3904
|
+
c.IsKey = key
|
|
3905
|
+
if data_category is not None:
|
|
3906
|
+
c.DataCategory = data_category
|
|
3907
|
+
if summarize_by is not None:
|
|
3908
|
+
c.SummarizeBy = System.Enum.Parse(TOM.AggregateFunction, summarize_by)
|
|
3909
|
+
|
|
3910
|
+
def update_role(
|
|
3911
|
+
self,
|
|
3912
|
+
role_name: str,
|
|
3913
|
+
model_permission: Optional[str | None] = None,
|
|
3914
|
+
description: Optional[str | None] = None,
|
|
3915
|
+
):
|
|
3916
|
+
"""
|
|
3917
|
+
Updates a role within a semantic model.
|
|
3918
|
+
|
|
3919
|
+
Parameters
|
|
3920
|
+
----------
|
|
3921
|
+
role_name : str
|
|
3922
|
+
Name of the role.
|
|
3923
|
+
model_permission : str, default=None
|
|
3924
|
+
The model permission for the role.
|
|
3925
|
+
Defaults to None which keeps the existing setting.
|
|
3926
|
+
description : str, default=None
|
|
3927
|
+
The description of the role.
|
|
3928
|
+
Defaults to None which keeps the existing setting.
|
|
3929
|
+
"""
|
|
3930
|
+
|
|
3931
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
3932
|
+
import System
|
|
3933
|
+
|
|
3934
|
+
obj = self.model.Roles[role_name]
|
|
3935
|
+
|
|
3936
|
+
if model_permission is not None:
|
|
3937
|
+
obj.ModelPermission = System.Enum.Parse(
|
|
3938
|
+
TOM.ModelPermission, model_permission
|
|
3939
|
+
)
|
|
3940
|
+
if description is not None:
|
|
3941
|
+
obj.Description = description
|
|
3942
|
+
|
|
3943
|
+
def update_calculation_item(
|
|
3944
|
+
self,
|
|
3945
|
+
table_name: str,
|
|
3946
|
+
calculation_item_name: str,
|
|
3947
|
+
expression: Optional[str | None] = None,
|
|
3948
|
+
ordinal: Optional[int | None] = None,
|
|
3949
|
+
format_string_expression: Optional[str | None] = None,
|
|
3950
|
+
description: Optional[str | None] = None,
|
|
3951
|
+
):
|
|
3952
|
+
"""
|
|
3953
|
+
Updates a calculation item within a semantic model.
|
|
3954
|
+
|
|
3955
|
+
Parameters
|
|
3956
|
+
----------
|
|
3957
|
+
table_name : str
|
|
3958
|
+
Name of the calculation group (table).
|
|
3959
|
+
calculation_item_name : str
|
|
3960
|
+
Name of the calculation item.
|
|
3961
|
+
expression : str, default=None
|
|
3962
|
+
The DAX expression of the calculation item.
|
|
3963
|
+
Defaults to None which keeps the existing setting.
|
|
3964
|
+
ordinal : int, default=None
|
|
3965
|
+
The ordinal of the calculation item.
|
|
3966
|
+
Defaults to None which keeps the existing setting.
|
|
3967
|
+
format_string_expression : str, default=None
|
|
3968
|
+
The format string expression for the calculation item.
|
|
3969
|
+
Defaults to None which keeps the existing setting.
|
|
3970
|
+
description : str, default=None
|
|
3971
|
+
The description of the role.
|
|
3972
|
+
Defaults to None which keeps the existing setting.
|
|
3973
|
+
"""
|
|
3974
|
+
|
|
3975
|
+
obj = self.Tables[table_name].CalculationGroup.CalculationItems[
|
|
3976
|
+
calculation_item_name
|
|
3977
|
+
]
|
|
3978
|
+
|
|
3979
|
+
if expression is not None:
|
|
3980
|
+
obj.Expression = expression
|
|
3981
|
+
if format_string_expression is not None:
|
|
3982
|
+
obj.FormatStringDefinition.Expression = format_string_expression
|
|
3983
|
+
if ordinal is not None:
|
|
3984
|
+
obj.Ordinal = ordinal
|
|
3985
|
+
if description is not None:
|
|
3986
|
+
obj.Description = description
|
|
3987
|
+
|
|
3988
|
+
def set_sort_by_column(
|
|
3989
|
+
self, table_name: str, column_name: str, sort_by_column: str
|
|
3990
|
+
):
|
|
3991
|
+
"""
|
|
3992
|
+
Sets the sort by column for a column in a semantic model.
|
|
3993
|
+
|
|
3994
|
+
Parameters
|
|
3995
|
+
----------
|
|
3996
|
+
table_name : str
|
|
3997
|
+
Name of the table.
|
|
3998
|
+
column_name : str
|
|
3999
|
+
Name of the column.
|
|
4000
|
+
sort_by_column : str
|
|
4001
|
+
Name of the column to use for sorting. Must be of integer (Int64) data type.
|
|
4002
|
+
"""
|
|
4003
|
+
|
|
4004
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
4005
|
+
|
|
4006
|
+
sbc = self.model.Tables[table_name].Columns[sort_by_column]
|
|
4007
|
+
|
|
4008
|
+
if sbc.DataType != TOM.DataType.Int64:
|
|
4009
|
+
raise ValueError(
|
|
4010
|
+
f"{icons.red_dot} Invalid sort by column data type. The sort by column must be of 'Int64' data type."
|
|
4011
|
+
)
|
|
4012
|
+
|
|
4013
|
+
self.model.Tables[table_name].Columns[column_name].SortByColumn = sbc
|
|
4014
|
+
|
|
4015
|
+
def remove_sort_by_column(self, table_name: str, column_name: str):
|
|
4016
|
+
"""
|
|
4017
|
+
Removes the sort by column for a column in a semantic model.
|
|
4018
|
+
|
|
4019
|
+
Parameters
|
|
4020
|
+
----------
|
|
4021
|
+
table_name : str
|
|
4022
|
+
Name of the table.
|
|
4023
|
+
column_name : str
|
|
4024
|
+
Name of the column.
|
|
4025
|
+
"""
|
|
4026
|
+
|
|
4027
|
+
self.model.Tables[table_name].Columns[column_name].SortByColumn = None
|
|
4028
|
+
|
|
4029
|
+
def is_calculated_table(self, table_name: str):
|
|
4030
|
+
"""
|
|
4031
|
+
Identifies if a table is a calculated table.
|
|
4032
|
+
|
|
4033
|
+
Parameters
|
|
4034
|
+
----------
|
|
4035
|
+
table_name : str
|
|
4036
|
+
Name of the table.
|
|
4037
|
+
|
|
4038
|
+
Returns
|
|
4039
|
+
-------
|
|
4040
|
+
bool
|
|
4041
|
+
A boolean value indicating whether the table is a calculated table.
|
|
4042
|
+
"""
|
|
4043
|
+
|
|
4044
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
4045
|
+
|
|
4046
|
+
isCalcTable = False
|
|
4047
|
+
t = self.model.Tables[table_name]
|
|
4048
|
+
if t.ObjectType == TOM.ObjectType.Table:
|
|
4049
|
+
if any(
|
|
4050
|
+
p.SourceType == TOM.PartitionSourceType.Calculated for p in t.Partitions
|
|
4051
|
+
):
|
|
4052
|
+
isCalcTable = True
|
|
4053
|
+
return isCalcTable
|
|
3693
4054
|
|
|
3694
4055
|
def close(self):
|
|
3695
4056
|
if not self._readonly and self.model is not None:
|