semantic-link-labs 0.5.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of semantic-link-labs might be problematic. Click here for more details.

Files changed (113) hide show
  1. semantic_link_labs-0.7.0.dist-info/METADATA +148 -0
  2. semantic_link_labs-0.7.0.dist-info/RECORD +111 -0
  3. {semantic_link_labs-0.5.0.dist-info → semantic_link_labs-0.7.0.dist-info}/WHEEL +1 -1
  4. sempy_labs/__init__.py +45 -15
  5. sempy_labs/_ai.py +42 -85
  6. sempy_labs/_bpa_translation/_translations_am-ET.po +828 -0
  7. sempy_labs/_bpa_translation/_translations_ar-AE.po +860 -0
  8. sempy_labs/_bpa_translation/_translations_cs-CZ.po +894 -0
  9. sempy_labs/_bpa_translation/_translations_da-DK.po +894 -0
  10. sempy_labs/_bpa_translation/_translations_de-DE.po +933 -0
  11. sempy_labs/_bpa_translation/_translations_el-GR.po +936 -0
  12. sempy_labs/_bpa_translation/_translations_es-ES.po +915 -0
  13. sempy_labs/_bpa_translation/_translations_fa-IR.po +883 -0
  14. sempy_labs/_bpa_translation/_translations_fr-FR.po +938 -0
  15. sempy_labs/_bpa_translation/_translations_ga-IE.po +912 -0
  16. sempy_labs/_bpa_translation/_translations_he-IL.po +855 -0
  17. sempy_labs/_bpa_translation/_translations_hi-IN.po +892 -0
  18. sempy_labs/_bpa_translation/_translations_hu-HU.po +910 -0
  19. sempy_labs/_bpa_translation/_translations_is-IS.po +887 -0
  20. sempy_labs/_bpa_translation/_translations_it-IT.po +931 -0
  21. sempy_labs/_bpa_translation/_translations_ja-JP.po +805 -0
  22. sempy_labs/_bpa_translation/_translations_nl-NL.po +924 -0
  23. sempy_labs/_bpa_translation/_translations_pl-PL.po +913 -0
  24. sempy_labs/_bpa_translation/_translations_pt-BR.po +909 -0
  25. sempy_labs/_bpa_translation/_translations_pt-PT.po +904 -0
  26. sempy_labs/_bpa_translation/_translations_ru-RU.po +909 -0
  27. sempy_labs/_bpa_translation/_translations_ta-IN.po +922 -0
  28. sempy_labs/_bpa_translation/_translations_te-IN.po +896 -0
  29. sempy_labs/_bpa_translation/_translations_th-TH.po +873 -0
  30. sempy_labs/_bpa_translation/_translations_zh-CN.po +767 -0
  31. sempy_labs/_bpa_translation/_translations_zu-ZA.po +916 -0
  32. sempy_labs/_clear_cache.py +12 -8
  33. sempy_labs/_connections.py +77 -70
  34. sempy_labs/_dax.py +7 -9
  35. sempy_labs/_generate_semantic_model.py +75 -90
  36. sempy_labs/_helper_functions.py +371 -20
  37. sempy_labs/_icons.py +23 -0
  38. sempy_labs/_list_functions.py +855 -427
  39. sempy_labs/_model_auto_build.py +4 -3
  40. sempy_labs/_model_bpa.py +307 -1118
  41. sempy_labs/_model_bpa_bulk.py +363 -0
  42. sempy_labs/_model_bpa_rules.py +831 -0
  43. sempy_labs/_model_dependencies.py +20 -16
  44. sempy_labs/_one_lake_integration.py +18 -12
  45. sempy_labs/_query_scale_out.py +116 -129
  46. sempy_labs/_refresh_semantic_model.py +23 -10
  47. sempy_labs/_translations.py +367 -288
  48. sempy_labs/_vertipaq.py +152 -123
  49. sempy_labs/directlake/__init__.py +7 -1
  50. sempy_labs/directlake/_directlake_schema_compare.py +33 -30
  51. sempy_labs/directlake/_directlake_schema_sync.py +60 -77
  52. sempy_labs/directlake/_dl_helper.py +233 -0
  53. sempy_labs/directlake/_get_directlake_lakehouse.py +7 -8
  54. sempy_labs/directlake/_get_shared_expression.py +5 -3
  55. sempy_labs/directlake/_guardrails.py +20 -16
  56. sempy_labs/directlake/_list_directlake_model_calc_tables.py +17 -10
  57. sempy_labs/directlake/_show_unsupported_directlake_objects.py +3 -2
  58. sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +10 -5
  59. sempy_labs/directlake/_update_directlake_partition_entity.py +169 -22
  60. sempy_labs/directlake/_warm_cache.py +7 -4
  61. sempy_labs/lakehouse/_get_lakehouse_columns.py +1 -1
  62. sempy_labs/lakehouse/_get_lakehouse_tables.py +65 -71
  63. sempy_labs/lakehouse/_lakehouse.py +5 -3
  64. sempy_labs/lakehouse/_shortcuts.py +20 -13
  65. sempy_labs/migration/__init__.py +1 -1
  66. sempy_labs/migration/_create_pqt_file.py +184 -186
  67. sempy_labs/migration/_migrate_calctables_to_lakehouse.py +240 -269
  68. sempy_labs/migration/_migrate_calctables_to_semantic_model.py +78 -77
  69. sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +444 -425
  70. sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +96 -102
  71. sempy_labs/migration/_migration_validation.py +2 -2
  72. sempy_labs/migration/_refresh_calc_tables.py +94 -100
  73. sempy_labs/report/_BPAReportTemplate.json +232 -0
  74. sempy_labs/report/__init__.py +6 -2
  75. sempy_labs/report/_bpareporttemplate/.pbi/localSettings.json +9 -0
  76. sempy_labs/report/_bpareporttemplate/.platform +11 -0
  77. sempy_labs/report/_bpareporttemplate/StaticResources/SharedResources/BaseThemes/CY24SU06.json +710 -0
  78. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/page.json +11 -0
  79. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/1b08bce3bebabb0a27a8/visual.json +191 -0
  80. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/2f22ddb70c301693c165/visual.json +438 -0
  81. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/3b1182230aa6c600b43a/visual.json +127 -0
  82. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/58577ba6380c69891500/visual.json +576 -0
  83. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/a2a8fa5028b3b776c96c/visual.json +207 -0
  84. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/adfd47ef30652707b987/visual.json +506 -0
  85. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/b6a80ee459e716e170b1/visual.json +127 -0
  86. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/ce3130a721c020cc3d81/visual.json +513 -0
  87. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/page.json +8 -0
  88. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/visuals/66e60dfb526437cd78d1/visual.json +112 -0
  89. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/page.json +11 -0
  90. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/07deb8bce824e1be37d7/visual.json +513 -0
  91. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0b1c68838818b32ad03b/visual.json +352 -0
  92. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0c171de9d2683d10b930/visual.json +37 -0
  93. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0efa01be0510e40a645e/visual.json +542 -0
  94. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/6bf2f0eb830ab53cc668/visual.json +221 -0
  95. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/88d8141cb8500b60030c/visual.json +127 -0
  96. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/a753273590beed656a03/visual.json +576 -0
  97. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/b8fdc82cddd61ac447bc/visual.json +127 -0
  98. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/page.json +9 -0
  99. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/visuals/ce8532a7e25020271077/visual.json +38 -0
  100. sempy_labs/report/_bpareporttemplate/definition/pages/pages.json +10 -0
  101. sempy_labs/report/_bpareporttemplate/definition/report.json +176 -0
  102. sempy_labs/report/_bpareporttemplate/definition/version.json +4 -0
  103. sempy_labs/report/_bpareporttemplate/definition.pbir +14 -0
  104. sempy_labs/report/_generate_report.py +260 -139
  105. sempy_labs/report/_report_functions.py +90 -59
  106. sempy_labs/report/_report_rebind.py +40 -34
  107. sempy_labs/tom/__init__.py +1 -4
  108. sempy_labs/tom/_model.py +601 -181
  109. semantic_link_labs-0.5.0.dist-info/METADATA +0 -22
  110. semantic_link_labs-0.5.0.dist-info/RECORD +0 -53
  111. sempy_labs/directlake/_fallback.py +0 -58
  112. {semantic_link_labs-0.5.0.dist-info → semantic_link_labs-0.7.0.dist-info}/LICENSE +0 -0
  113. {semantic_link_labs-0.5.0.dist-info → semantic_link_labs-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,18 @@
1
1
  import sempy
2
2
  import sempy.fabric as fabric
3
- import re, datetime, time
3
+ import re
4
4
  from sempy_labs._list_functions import list_tables
5
- from sempy_labs._helper_functions import create_relationship_name
5
+ from sempy_labs._helper_functions import (
6
+ create_relationship_name,
7
+ retry,
8
+ format_dax_object_name,
9
+ )
6
10
  from sempy_labs.tom import connect_semantic_model
7
11
  from typing import Optional
8
12
  from sempy._utils._log import log
9
13
  import sempy_labs._icons as icons
10
14
 
15
+
11
16
  @log
12
17
  def migrate_model_objects_to_semantic_model(
13
18
  dataset: str,
@@ -46,7 +51,6 @@ def migrate_model_objects_to_semantic_model(
46
51
  dfT = list_tables(dataset, workspace)
47
52
  dfC = fabric.list_columns(dataset=dataset, workspace=workspace)
48
53
  dfM = fabric.list_measures(dataset=dataset, workspace=workspace)
49
- dfR = fabric.list_relationships(dataset=dataset, workspace=workspace)
50
54
  dfRole = fabric.get_roles(dataset=dataset, workspace=workspace)
51
55
  dfRLS = fabric.get_row_level_security_permissions(
52
56
  dataset=dataset, workspace=workspace
@@ -59,461 +63,476 @@ def migrate_model_objects_to_semantic_model(
59
63
 
60
64
  dfP_cc = dfPar[(dfPar["Source Type"] == "Calculated")]
61
65
  dfP_fp = dfP_cc[dfP_cc["Query"].str.contains("NAMEOF")]
62
- dfC_fp = dfC[dfC["Table Name"].isin(dfP_fp["Table Name"].values)]
63
66
 
64
67
  print(f"{icons.in_progress} Updating '{new_dataset}' based on '{dataset}'...")
65
- start_time = datetime.datetime.now()
66
- timeout = datetime.timedelta(minutes=1)
67
- success = False
68
-
69
- while not success:
70
- try:
71
- with connect_semantic_model(
72
- dataset=new_dataset, readonly=False, workspace=new_dataset_workspace
73
- ) as tom:
74
- success = True
75
-
76
- isDirectLake = any(
77
- str(p.Mode) == "DirectLake"
78
- for t in tom.model.Tables
79
- for p in t.Partitions
80
- )
81
68
 
82
- print(f"\n{icons.in_progress} Updating table properties...")
83
- for t in tom.model.Tables:
84
- t.IsHidden = bool(dfT.loc[dfT["Name"] == t.Name, "Hidden"].iloc[0])
85
- t.Description = dfT.loc[dfT["Name"] == t.Name, "Description"].iloc[
86
- 0
87
- ]
88
- t.DataCategory = dfT.loc[
89
- dfT["Name"] == t.Name, "Data Category"
90
- ].iloc[0]
69
+ @retry(
70
+ sleep_time=1,
71
+ timeout_error_message=f"{icons.red_dot} Function timed out after 1 minute",
72
+ )
73
+ def dyn_connect():
74
+ with connect_semantic_model(
75
+ dataset=new_dataset, readonly=True, workspace=new_dataset_workspace
76
+ ) as tom:
91
77
 
92
- print(
93
- f"{icons.green_dot} The '{t.Name}' table's properties have been updated."
94
- )
78
+ tom.model
95
79
 
96
- print(f"\n{icons.in_progress} Updating column properties...")
97
- for t in tom.model.Tables:
98
- if (
99
- t.Name not in dfP_fp["Table Name"].values
100
- ): # do not include field parameters
101
- dfT_filtered = dfT[dfT["Name"] == t.Name]
102
- tType = dfT_filtered["Type"].iloc[0]
103
- for c in t.Columns:
104
- if not c.Name.startswith("RowNumber-"):
105
- dfC_filt = dfC[
106
- (dfC["Table Name"] == t.Name)
107
- & (dfC["Column Name"] == c.Name)
108
- ]
109
- cName = dfC_filt["Column Name"].iloc[0]
110
- c.Name = cName
111
- if tType == "Table":
112
- c.SourceColumn = cName.replace(" ", "_")
113
- c.IsHidden = bool(dfC_filt["Hidden"].iloc[0])
114
- c.DataType = System.Enum.Parse(
115
- TOM.DataType, dfC_filt["Data Type"].iloc[0]
116
- )
117
- c.DisplayFolder = dfC_filt["Display Folder"].iloc[0]
118
- c.FormatString = dfC_filt["Format String"].iloc[0]
119
- c.SummarizeBy = System.Enum.Parse(
120
- TOM.AggregateFunction,
121
- dfC_filt["Summarize By"].iloc[0],
122
- )
123
- c.DataCategory = dfC_filt["Data Category"].iloc[0]
124
- c.IsKey = bool(dfC_filt["Key"].iloc[0])
125
- sbc = dfC_filt["Sort By Column"].iloc[0]
126
-
127
- if sbc != None:
128
- try:
129
- c.SortByColumn = tom.model.Tables[
130
- t.Name
131
- ].Columns[sbc]
132
- except:
133
- print(
134
- f"{icons.red_dot} Failed to create '{sbc}' as a Sort By Column for the '{c.Name}' in the '{t.Name}' table."
135
- )
136
- print(
137
- f"{icons.green_dot} The '{t.Name}'[{c.Name}] column's properties have been updated."
138
- )
80
+ dyn_connect()
139
81
 
140
- print(f"\n{icons.in_progress} Creating hierarchies...")
141
- dfH_grouped = (
142
- dfH.groupby(
143
- [
144
- "Table Name",
145
- "Hierarchy Name",
146
- "Hierarchy Hidden",
147
- "Hierarchy Description",
82
+ @retry(
83
+ sleep_time=1,
84
+ timeout_error_message=f"{icons.red_dot} Function timed out after 1 minute",
85
+ )
86
+ def dyn_connect2():
87
+ with connect_semantic_model(
88
+ dataset=dataset, readonly=True, workspace=workspace
89
+ ) as tom:
90
+
91
+ tom.model
92
+
93
+ dyn_connect2()
94
+
95
+ with connect_semantic_model(
96
+ dataset=new_dataset, readonly=False, workspace=new_dataset_workspace
97
+ ) as tom:
98
+
99
+ isDirectLake = tom.is_direct_lake()
100
+
101
+ print(f"\n{icons.in_progress} Updating table properties...")
102
+ for t in tom.model.Tables:
103
+ t.IsHidden = bool(dfT.loc[dfT["Name"] == t.Name, "Hidden"].iloc[0])
104
+ t.Description = dfT.loc[dfT["Name"] == t.Name, "Description"].iloc[0]
105
+ t.DataCategory = dfT.loc[dfT["Name"] == t.Name, "Data Category"].iloc[0]
106
+
107
+ print(
108
+ f"{icons.green_dot} The '{t.Name}' table's properties have been updated."
109
+ )
110
+
111
+ print(f"\n{icons.in_progress} Updating column properties...")
112
+ for t in tom.model.Tables:
113
+ if (
114
+ t.Name not in dfP_fp["Table Name"].values
115
+ ): # do not include field parameters
116
+ dfT_filtered = dfT[dfT["Name"] == t.Name]
117
+ tType = dfT_filtered["Type"].iloc[0]
118
+ for c in t.Columns:
119
+ if not c.Name.startswith("RowNumber-"):
120
+ dfC_filt = dfC[
121
+ (dfC["Table Name"] == t.Name)
122
+ & (dfC["Column Name"] == c.Name)
148
123
  ]
149
- )
150
- .agg({"Level Name": list, "Column Name": list})
151
- .reset_index()
152
- )
153
-
154
- for i, r in dfH_grouped.iterrows():
155
- tName = r["Table Name"]
156
- hName = r["Hierarchy Name"]
157
- hDesc = r["Hierarchy Description"]
158
- hHid = bool(r["Hierarchy Hidden"])
159
- cols = r["Column Name"]
160
- lvls = r["Level Name"]
161
-
162
- try:
163
- tom.model.Tables[tName].Hierarchies[hName]
164
- except:
165
- tom.add_hierarchy(
166
- table_name=tName,
167
- hierarchy_name=hName,
168
- hierarchy_description=hDesc,
169
- hierarchy_hidden=hHid,
170
- columns=cols,
171
- levels=lvls,
124
+ cName = dfC_filt["Column Name"].iloc[0]
125
+ c.Name = cName
126
+ # if tType == "Table":
127
+ # c.SourceColumn = cName.replace(" ", "_")
128
+ c.IsHidden = bool(dfC_filt["Hidden"].iloc[0])
129
+ c.DataType = System.Enum.Parse(
130
+ TOM.DataType, dfC_filt["Data Type"].iloc[0]
172
131
  )
173
- print(
174
- f"{icons.green_dot} The '{hName}' hierarchy has been added."
175
- )
176
-
177
- print(f"\n{icons.in_progress} Creating measures...")
178
- for i, r in dfM.iterrows():
179
- tName = r["Table Name"]
180
- mName = r["Measure Name"]
181
- mExpr = r["Measure Expression"]
182
- mHidden = bool(r["Measure Hidden"])
183
- mDF = r["Measure Display Folder"]
184
- mDesc = r["Measure Description"]
185
- mFS = r["Format String"]
186
-
187
- try:
188
- tom.model.Tables[tName].Measures[mName]
189
- except:
190
- tom.add_measure(
191
- table_name=tName,
192
- measure_name=mName,
193
- expression=mExpr,
194
- hidden=mHidden,
195
- display_folder=mDF,
196
- description=mDesc,
197
- format_string=mFS,
132
+ c.DisplayFolder = dfC_filt["Display Folder"].iloc[0]
133
+ c.FormatString = dfC_filt["Format String"].iloc[0]
134
+ c.SummarizeBy = System.Enum.Parse(
135
+ TOM.AggregateFunction,
136
+ dfC_filt["Summarize By"].iloc[0],
198
137
  )
138
+ c.DataCategory = dfC_filt["Data Category"].iloc[0]
139
+ c.IsKey = bool(dfC_filt["Key"].iloc[0])
140
+ sbc = dfC_filt["Sort By Column"].iloc[0]
141
+
142
+ if sbc is not None:
143
+ if any(
144
+ o.Name == sbc and o.Parent.Name == c.Parent.Name
145
+ for o in tom.all_columns()
146
+ ):
147
+ c.SortByColumn = tom.model.Tables[t.Name].Columns[sbc]
148
+ else:
149
+ print(
150
+ f"{icons.red_dot} Failed to create '{sbc}' as a Sort By Column for the '{c.Name}' in the '{t.Name}' table."
151
+ )
199
152
  print(
200
- f"{icons.green_dot} The '{mName}' measure has been added."
153
+ f"{icons.green_dot} The '{t.Name}'[{c.Name}] column's properties have been updated."
201
154
  )
202
155
 
203
- for cgName in dfCI["Calculation Group Name"].unique():
204
-
205
- isHidden = bool(
206
- dfCI.loc[
207
- (dfCI["Calculation Group Name"] == cgName), "Hidden"
208
- ].iloc[0]
156
+ print(f"\n{icons.in_progress} Creating hierarchies...")
157
+ dfH_grouped = (
158
+ dfH.groupby(
159
+ [
160
+ "Table Name",
161
+ "Hierarchy Name",
162
+ "Hierarchy Hidden",
163
+ "Hierarchy Description",
164
+ ]
165
+ )
166
+ .agg({"Level Name": list, "Column Name": list})
167
+ .reset_index()
168
+ )
169
+
170
+ for i, r in dfH_grouped.iterrows():
171
+ tName = r["Table Name"]
172
+ hName = r["Hierarchy Name"]
173
+ hDesc = r["Hierarchy Description"]
174
+ hHid = bool(r["Hierarchy Hidden"])
175
+ cols = r["Column Name"]
176
+ lvls = r["Level Name"]
177
+
178
+ missing_columns = []
179
+ for col in cols:
180
+ if not any(
181
+ c.Name == col
182
+ for t in tom.model.Tables
183
+ if t.Name == tName
184
+ for c in t.Columns
185
+ ):
186
+ missing_columns.append(col)
187
+
188
+ if any(
189
+ t.Name == tName and h.Name == hName
190
+ for t in tom.model.Tables
191
+ for h in t.Hierarchies
192
+ ):
193
+ print(
194
+ f"{icons.warning} The '{hName}' hierarchy within the '{tName}' table already exists."
195
+ )
196
+ elif len(missing_columns) > 0:
197
+ print(
198
+ f"{icons.red_dot} The '{hName}' hierarchy within the '{tName}' table cannot be created as the {missing_columns} column)s) do not exist."
199
+ )
200
+ else:
201
+ tom.add_hierarchy(
202
+ table_name=tName,
203
+ hierarchy_name=hName,
204
+ hierarchy_description=hDesc,
205
+ hierarchy_hidden=hHid,
206
+ columns=cols,
207
+ levels=lvls,
208
+ )
209
+ print(f"{icons.green_dot} The '{hName}' hierarchy has been added.")
210
+
211
+ print(f"\n{icons.in_progress} Creating measures...")
212
+ for i, r in dfM.iterrows():
213
+ tName = r["Table Name"]
214
+ mName = r["Measure Name"]
215
+ mExpr = r["Measure Expression"]
216
+ mHidden = bool(r["Measure Hidden"])
217
+ mDF = r["Measure Display Folder"]
218
+ mDesc = r["Measure Description"]
219
+ mFS = r["Format String"]
220
+
221
+ if not any(m.Name == mName for m in tom.all_measures()):
222
+ tom.add_measure(
223
+ table_name=tName,
224
+ measure_name=mName,
225
+ expression=mExpr,
226
+ hidden=mHidden,
227
+ display_folder=mDF,
228
+ description=mDesc,
229
+ format_string=mFS,
230
+ )
231
+ print(f"{icons.green_dot} The '{mName}' measure has been added.")
232
+ print(f"\n{icons.in_progress} Creating calculation groups...")
233
+ for cgName in dfCI["Calculation Group Name"].unique():
234
+
235
+ isHidden = bool(
236
+ dfCI.loc[(dfCI["Calculation Group Name"] == cgName), "Hidden"].iloc[0]
237
+ )
238
+ prec = int(
239
+ dfCI.loc[(dfCI["Calculation Group Name"] == cgName), "Precedence"].iloc[
240
+ 0
241
+ ]
242
+ )
243
+ desc = dfCI.loc[
244
+ (dfCI["Calculation Group Name"] == cgName), "Description"
245
+ ].iloc[0]
246
+
247
+ if not any(t.Name == cgName for t in tom.model.Tables):
248
+ tom.add_calculation_group(
249
+ name=cgName,
250
+ description=desc,
251
+ precedence=prec,
252
+ hidden=isHidden,
253
+ )
254
+ print(
255
+ f"{icons.green_dot} The '{cgName}' calculation group has been added."
256
+ )
257
+ tom.model.DiscourageImplicitMeasures = True
258
+
259
+ # print(
260
+ # f"\n{icons.in_progress} Updating calculation group column names..."
261
+ # )
262
+ dfC_filt = dfC[(dfC["Table Name"] == cgName) & (dfC["Hidden"] == False)]
263
+ colName = dfC_filt["Column Name"].iloc[0]
264
+ tom.model.Tables[cgName].Columns["Name"].Name = colName
265
+
266
+ calcItems = dfCI.loc[
267
+ dfCI["Calculation Group Name"] == cgName,
268
+ "Calculation Item Name",
269
+ ].unique()
270
+
271
+ print(f"\n{icons.in_progress} Creating calculation items...")
272
+ for calcItem in calcItems:
273
+ ordinal = int(
274
+ dfCI.loc[
275
+ (dfCI["Calculation Group Name"] == cgName)
276
+ & (dfCI["Calculation Item Name"] == calcItem),
277
+ "Ordinal",
278
+ ].iloc[0]
279
+ )
280
+ expr = dfCI.loc[
281
+ (dfCI["Calculation Group Name"] == cgName)
282
+ & (dfCI["Calculation Item Name"] == calcItem),
283
+ "Expression",
284
+ ].iloc[0]
285
+ fse = dfCI.loc[
286
+ (dfCI["Calculation Group Name"] == cgName)
287
+ & (dfCI["Calculation Item Name"] == calcItem),
288
+ "Format String Expression",
289
+ ].iloc[0]
290
+
291
+ if not any(
292
+ ci.CalculationGroup.Parent.Name == cgName and ci.Name == calcItem
293
+ for ci in tom.all_calculation_items()
294
+ ):
295
+ tom.add_calculation_item(
296
+ table_name=cgName,
297
+ calculation_item_name=calcItem,
298
+ expression=expr,
299
+ format_string_expression=fse,
300
+ ordinal=ordinal,
209
301
  )
210
- prec = int(
211
- dfCI.loc[
212
- (dfCI["Calculation Group Name"] == cgName), "Precedence"
213
- ].iloc[0]
302
+ print(
303
+ f"{icons.green_dot} The '{calcItem}' has been added to the '{cgName}' calculation group."
214
304
  )
215
- desc = dfCI.loc[
216
- (dfCI["Calculation Group Name"] == cgName), "Description"
217
- ].iloc[0]
218
305
 
219
- try:
220
- tom.model.Tables[cgName]
221
- except:
222
- tom.add_calculation_group(
223
- name=cgName,
224
- description=desc,
225
- precedence=prec,
226
- hidden=isHidden,
227
- )
228
- print(
229
- f"{icons.green_dot} The '{cgName}' calculation group has been added."
230
- )
231
- tom.model.DiscourageImplicitMeasures = True
306
+ print(f"\n{icons.in_progress} Creating relationships...")
307
+ with connect_semantic_model(
308
+ dataset=dataset, readonly=True, workspace=workspace
309
+ ) as tom_old:
232
310
 
233
- print(
234
- f"\n{icons.in_progress} Updating calculation group column name..."
235
- )
236
- dfC_filt = dfC[
237
- (dfC["Table Name"] == cgName) & (dfC["Hidden"] == False)
238
- ]
239
- colName = dfC_filt["Column Name"].iloc[0]
240
- tom.model.Tables[cgName].Columns["Name"].Name = colName
241
-
242
- calcItems = dfCI.loc[
243
- dfCI["Calculation Group Name"] == cgName,
244
- "Calculation Item Name",
245
- ].unique()
246
-
247
- print(f"\n{icons.in_progress} Creating calculation items...")
248
- for calcItem in calcItems:
249
- ordinal = int(
250
- dfCI.loc[
251
- (dfCI["Calculation Group Name"] == cgName)
252
- & (dfCI["Calculation Item Name"] == calcItem),
253
- "Ordinal",
254
- ].iloc[0]
255
- )
256
- expr = dfCI.loc[
257
- (dfCI["Calculation Group Name"] == cgName)
258
- & (dfCI["Calculation Item Name"] == calcItem),
259
- "Expression",
260
- ].iloc[0]
261
- fse = dfCI.loc[
262
- (dfCI["Calculation Group Name"] == cgName)
263
- & (dfCI["Calculation Item Name"] == calcItem),
264
- "Format String Expression",
265
- ].iloc[0]
266
- try:
267
- tom.model.Tables[cgName].CalculationGroup.CalculationItems[
268
- calcItem
269
- ]
270
- except:
271
- tom.add_calculation_item(
272
- table_name=cgName,
273
- calculation_item_name=calcItem,
274
- expression=expr,
275
- format_string_expression=fse,
276
- ordinal=ordinal,
277
- )
278
- print(
279
- f"{icons.green_dot} The '{calcItem}' has been added to the '{cgName}' calculation group."
280
- )
281
-
282
- print(f"\n{icons.in_progress} Creating relationships...")
283
- for index, row in dfR.iterrows():
284
- fromTable = row["From Table"]
285
- fromColumn = row["From Column"]
286
- toTable = row["To Table"]
287
- toColumn = row["To Column"]
288
- isActive = row["Active"]
289
- cfb = row["Cross Filtering Behavior"]
290
- sfb = row["Security Filtering Behavior"]
291
- rori = row["Rely On Referential Integrity"]
292
- mult = row["Multiplicity"]
293
-
294
- card_mapping = {"m": "Many", "1": "One", "0": "None"}
295
-
296
- fromCard = card_mapping.get(mult[0])
297
- toCard = card_mapping.get(mult[-1])
298
-
299
- relName = create_relationship_name(
300
- fromTable, fromColumn, toTable, toColumn
311
+ for r in tom_old.model.Relationships:
312
+ relName = create_relationship_name(
313
+ r.FromTable.Name, r.FromColumn.Name, r.ToTable.Name, r.ToColumn.Name
314
+ )
315
+
316
+ # Relationship already exists
317
+ if any(
318
+ rel.FromTable.Name == r.FromTable.Name
319
+ and rel.FromColumn.Name == r.FromColumn.Name
320
+ and rel.ToTable.Name == r.ToTable.Name
321
+ and rel.ToColumn.Name == r.ToColumn.Name
322
+ for rel in tom.model.Relationships
323
+ ):
324
+ print(
325
+ f"{icons.warning} The {relName} relationship was not created as it already exists in the '{new_dataset}' semantic model within the '{new_dataset_workspace}' workspace."
301
326
  )
302
327
 
303
- if any(
304
- r.FromTable.Name == fromTable
305
- and r.FromColumn.Name == fromColumn
306
- and r.ToTable.Name == toTable
307
- and r.ToColumn.Name == toColumn
308
- for r in tom.model.Relationships
309
- ):
310
- print(
311
- f"{icons.yellow_dot} {relName} already exists as a relationship in the semantic model."
328
+ # Direct Lake with incompatible column data types
329
+ elif isDirectLake and r.FromColumn.DataType != r.ToColumn.DataType:
330
+ print(
331
+ f"{icons.warning} The {relName} relationship was not created as Direct Lake does not support relationships based on columns with different data types."
332
+ )
333
+ # Direct Lake using DateTime columns
334
+ elif isDirectLake and (
335
+ r.FromColumn.DataType == TOM.DataType.DateTime
336
+ or r.ToColumn.DataType == TOM.DataType.DateTime
337
+ ):
338
+ print(
339
+ f"{icons.red_dot} The {relName} relationship was not created as Direct Lake does not support relationships based on columns of DateTime data type."
340
+ )
341
+ # Columns do not exist in the new semantic model
342
+ elif not any(
343
+ c.Name == r.FromColumn.Name and c.Parent.Name == r.FromTable.Name
344
+ for c in tom.all_columns()
345
+ ) or not any(
346
+ c.Name == r.ToColumn.Name and c.Parent.Name == r.ToTable.Name
347
+ for c in tom.all_columns()
348
+ ):
349
+ # Direct lake and based on calculated column
350
+ if isDirectLake and (
351
+ any(
352
+ c.Name == r.FromColumn.Name
353
+ and c.Parent.Name == r.FromTable.Name
354
+ for c in tom_old.all_calculated_columns()
312
355
  )
313
- elif isDirectLake and any(
314
- r.FromTable.Name == fromTable
315
- and r.FromColumn.Name == fromColumn
316
- and r.ToTable.Name == toTable
317
- and r.ToColumn.Name == toColumn
318
- and (
319
- r.FromColumn.DataType == "DateTime"
320
- or r.ToColumn.DataType == "DateTime"
356
+ or any(
357
+ c.Name == r.ToColumn.Name
358
+ and c.Parent.Name == r.ToTable.Name
359
+ for c in tom_old.all_calculated_columns()
321
360
  )
322
- for r in tom.model.Relationships
323
361
  ):
324
362
  print(
325
- f"{icons.yellow_dot} {relName} was not created since relationships based on DateTime columns are not supported."
363
+ f"{icons.red_dot} The {relName} relationship was not created as the necssary column(s) do not exist. This is due to Direct Lake not supporting calculated columns."
326
364
  )
327
- elif isDirectLake and any(
328
- r.FromTable.Name == fromTable
329
- and r.FromColumn.Name == fromColumn
330
- and r.ToTable.Name == toTable
331
- and r.ToColumn.Name == toColumn
332
- and (r.FromColumn.DataType != r.ToColumn.DataType)
333
- for r in tom.model.Relationships
365
+ elif not any(
366
+ c.Name == r.FromColumn.Name
367
+ and c.Parent.Name == r.FromTable.Name
368
+ for c in tom.all_columns()
334
369
  ):
335
370
  print(
336
- f"{icons.yellow_dot} {relName} was not created since columns used in a relationship must have the same data type."
337
- )
338
- else:
339
- try:
340
- tom.add_relationship(
341
- from_table=fromTable,
342
- from_column=fromColumn,
343
- to_table=toTable,
344
- to_column=toColumn,
345
- from_cardinality=fromCard,
346
- to_cardinality=toCard,
347
- cross_filtering_behavior=cfb,
348
- security_filtering_behavior=sfb,
349
- rely_on_referential_integrity=rori,
350
- is_active=isActive,
351
- )
352
-
353
- print(
354
- f"{icons.green_dot} The {relName} relationship has been added."
355
- )
356
- except:
357
- print(
358
- f"{icons.red_dot} The {relName} relationship was not added."
359
- )
360
-
361
- print(f"\n{icons.in_progress} Creating roles...")
362
- for index, row in dfRole.iterrows():
363
- roleName = row["Role"]
364
- roleDesc = row["Description"]
365
- modPerm = row["Model Permission"]
366
-
367
- try:
368
- tom.model.Roles[roleName]
369
- except:
370
- tom.add_role(
371
- role_name=roleName,
372
- model_permission=modPerm,
373
- description=roleDesc,
371
+ f"{icons.red_dot} The {relName} relationship cannot be created because the {format_dax_object_name(r.FromTable.Name, r.FromColumn.Name)} column is not available in the '{new_dataset}' semantic model within the '{new_dataset_workspace}' workspace."
374
372
  )
373
+ elif not any(
374
+ c.Name == r.ToColumn.Name and c.Parent.Name == r.ToTable.Name
375
+ for c in tom.all_columns()
376
+ ):
375
377
  print(
376
- f"{icons.green_dot} The '{roleName}' role has been added."
377
- )
378
-
379
- print(f"\n{icons.in_progress} Creating row level security...")
380
- for index, row in dfRLS.iterrows():
381
- roleName = row["Role"]
382
- tName = row["Table"]
383
- expr = row["Filter Expression"]
384
-
385
- try:
386
- tom.set_rls(
387
- role_name=roleName, table_name=tName, filter_expression=expr
388
- )
389
- print(
390
- f"{icons.green_dot} Row level security for the '{tName}' table within the '{roleName}' role has been set."
391
- )
392
- except:
393
- print(
394
- f"{icons.red_dot} Row level security for the '{tName}' table within the '{roleName}' role was not set."
378
+ f"{icons.red_dot} The {relName} relationship cannot be created because the {format_dax_object_name(r.ToTable.Name, r.ToColumn.Name)} column is not available in the '{new_dataset}' semantic model within the '{new_dataset_workspace}' workspace."
395
379
  )
380
+ else:
381
+ tom.add_relationship(
382
+ from_table=r.FromTable.Name,
383
+ from_column=r.FromColumn.Name,
384
+ to_table=r.ToTable.Name,
385
+ to_column=r.ToColumn.Name,
386
+ from_cardinality=str(r.FromCardinality),
387
+ to_cardinality=str(r.ToCardinality),
388
+ cross_filtering_behavior=str(r.CrossFilteringBehavior),
389
+ security_filtering_behavior=str(r.SecurityFilteringBehavior),
390
+ rely_on_referential_integrity=r.RelyOnReferentialIntegrity,
391
+ is_active=r.IsActive,
392
+ )
393
+ print(
394
+ f"{icons.green_dot} The {relName} relationship has been added."
395
+ )
396
396
 
397
- print(f"\n{icons.in_progress} Creating perspectives...")
398
- for pName in dfP["Perspective Name"].unique():
397
+ print(f"\n{icons.in_progress} Creating roles...")
398
+ for index, row in dfRole.iterrows():
399
+ roleName = row["Role"]
400
+ roleDesc = row["Description"]
401
+ modPerm = row["Model Permission"]
402
+
403
+ if not any(r.Name == roleName for r in tom.model.Roles):
404
+ tom.add_role(
405
+ role_name=roleName,
406
+ model_permission=modPerm,
407
+ description=roleDesc,
408
+ )
409
+ print(f"{icons.green_dot} The '{roleName}' role has been added.")
399
410
 
400
- try:
401
- tom.model.Perspectives[pName]
402
- except:
403
- tom.add_perspective(perspective_name=pName)
404
- print(
405
- f"{icons.green_dot} The '{pName}' perspective has been added."
406
- )
411
+ print(f"\n{icons.in_progress} Creating row level security...")
412
+ for index, row in dfRLS.iterrows():
413
+ roleName = row["Role"]
414
+ tName = row["Table"]
415
+ expr = row["Filter Expression"]
407
416
 
408
- print(f"\n{icons.in_progress} Adding objects to perspectives...")
409
- for index, row in dfP.iterrows():
410
- pName = row["Perspective Name"]
411
- tName = row["Table Name"]
412
- oName = row["Object Name"]
413
- oType = row["Object Type"]
414
- tType = dfT.loc[(dfT["Name"] == tName), "Type"].iloc[0]
415
-
416
- try:
417
- if oType == "Table":
418
- tom.add_to_perspective(
419
- object=tom.model.Tables[tName], perspective_name=pName
420
- )
421
- elif oType == "Column":
422
- tom.add_to_perspective(
423
- object=tom.model.Tables[tName].Columns[oName],
424
- perspective_name=pName,
425
- )
426
- elif oType == "Measure":
427
- tom.add_to_perspective(
428
- object=tom.model.Tables[tName].Measures[oName],
429
- perspective_name=pName,
430
- )
431
- elif oType == "Hierarchy":
432
- tom.add_to_perspective(
433
- object=tom.model.Tables[tName].Hierarchies[oName],
434
- perspective_name=pName,
435
- )
436
- except:
437
- pass
438
-
439
- print(f"\n{icons.in_progress} Creating translation languages...")
440
- for trName in dfTranslation["Culture Name"].unique():
441
- try:
442
- tom.model.Cultures[trName]
443
- except:
444
- tom.add_translation(trName)
445
- print(
446
- f"{icons.green_dot} The '{trName}' translation language has been added."
447
- )
417
+ if any(t.Name == tName for t in tom.model.Tables):
418
+ tom.set_rls(
419
+ role_name=roleName, table_name=tName, filter_expression=expr
420
+ )
421
+ print(
422
+ f"{icons.green_dot} Row level security for the '{tName}' table within the '{roleName}' role has been set."
423
+ )
424
+ else:
425
+ print(
426
+ f"{icons.red_dot} Row level security for the '{tName}' table within the '{roleName}' role was not set."
427
+ )
448
428
 
449
- print(f"\n{icons.in_progress} Creating translation values...")
450
- for index, row in dfTranslation.iterrows():
451
- trName = row["Culture Name"]
452
- tName = row["Table Name"]
453
- oName = row["Object Name"]
454
- oType = row["Object Type"]
455
- translation = row["Translation"]
456
- prop = row["Property"]
457
-
458
- if prop == "Caption":
459
- prop = "Name"
460
- elif prop == "DisplayFolder":
461
- prop = "Display Folder"
462
-
463
- try:
464
- if oType == "Table":
465
- tom.set_translation(
466
- object=tom.model.Tables[tName],
467
- language=trName,
468
- property=prop,
469
- value=translation,
470
- )
471
- elif oType == "Column":
472
- tom.set_translation(
473
- object=tom.model.Tables[tName].Columns[oName],
474
- language=trName,
475
- property=prop,
476
- value=translation,
477
- )
478
- elif oType == "Measure":
479
- tom.set_translation(
480
- object=tom.model.Tables[tName].Measures[oName],
481
- language=trName,
482
- property=prop,
483
- value=translation,
484
- )
485
- elif oType == "Hierarchy":
486
- tom.set_translation(
487
- object=tom.model.Tables[tName].Hierarchies[oName],
488
- language=trName,
489
- property=prop,
490
- value=translation,
491
- )
492
- elif oType == "Level":
493
-
494
- pattern = r"\[([^]]+)\]"
495
- matches = re.findall(pattern, oName)
496
- lName = matches[0]
497
-
498
- pattern = r"'([^']+)'"
499
- matches = re.findall(pattern, oName)
500
- hName = matches[0]
501
- tom.set_translation(
502
- object=tom.model.Tables[tName]
503
- .Hierarchies[hName]
504
- .Levels[lName],
505
- language=trName,
506
- property=prop,
507
- value=translation,
508
- )
509
- except:
510
- pass
429
+ print(f"\n{icons.in_progress} Creating perspectives...")
430
+ for pName in dfP["Perspective Name"].unique():
431
+
432
+ if not any(p.Name == pName for p in tom.model.Perspectives):
433
+ tom.add_perspective(perspective_name=pName)
434
+ print(f"{icons.green_dot} The '{pName}' perspective has been added.")
435
+
436
+ print(f"\n{icons.in_progress} Adding objects to perspectives...")
437
+ for index, row in dfP.iterrows():
438
+ pName = row["Perspective Name"]
439
+ tName = row["Table Name"]
440
+ oName = row["Object Name"]
441
+ oType = row["Object Type"]
442
+ tType = dfT.loc[(dfT["Name"] == tName), "Type"].iloc[0]
443
+
444
+ try:
445
+ if oType == "Table":
446
+ tom.add_to_perspective(
447
+ object=tom.model.Tables[tName], perspective_name=pName
448
+ )
449
+ elif oType == "Column":
450
+ tom.add_to_perspective(
451
+ object=tom.model.Tables[tName].Columns[oName],
452
+ perspective_name=pName,
453
+ )
454
+ elif oType == "Measure":
455
+ tom.add_to_perspective(
456
+ object=tom.model.Tables[tName].Measures[oName],
457
+ perspective_name=pName,
458
+ )
459
+ elif oType == "Hierarchy":
460
+ tom.add_to_perspective(
461
+ object=tom.model.Tables[tName].Hierarchies[oName],
462
+ perspective_name=pName,
463
+ )
464
+ except Exception:
465
+ pass
511
466
 
467
+ print(f"\n{icons.in_progress} Creating translation languages...")
468
+ for trName in dfTranslation["Culture Name"].unique():
469
+ if not any(c.Name == trName for c in tom.model.Cultures):
470
+ tom.add_translation(trName)
512
471
  print(
513
- f"\n{icons.green_dot} Migration of objects from '{dataset}' -> '{new_dataset}' is complete."
472
+ f"{icons.green_dot} The '{trName}' translation language has been added."
514
473
  )
515
474
 
516
- except Exception as e:
517
- if datetime.datetime.now() - start_time > timeout:
518
- break
519
- time.sleep(1)
475
+ print(f"\n{icons.in_progress} Creating translation values...")
476
+ for index, row in dfTranslation.iterrows():
477
+ trName = row["Culture Name"]
478
+ tName = row["Table Name"]
479
+ oName = row["Object Name"]
480
+ oType = row["Object Type"]
481
+ translation = row["Translation"]
482
+ prop = row["Property"]
483
+
484
+ if prop == "Caption":
485
+ prop = "Name"
486
+ elif prop == "DisplayFolder":
487
+ prop = "Display Folder"
488
+
489
+ try:
490
+ if oType == "Table":
491
+ tom.set_translation(
492
+ object=tom.model.Tables[tName],
493
+ language=trName,
494
+ property=prop,
495
+ value=translation,
496
+ )
497
+ elif oType == "Column":
498
+ tom.set_translation(
499
+ object=tom.model.Tables[tName].Columns[oName],
500
+ language=trName,
501
+ property=prop,
502
+ value=translation,
503
+ )
504
+ elif oType == "Measure":
505
+ tom.set_translation(
506
+ object=tom.model.Tables[tName].Measures[oName],
507
+ language=trName,
508
+ property=prop,
509
+ value=translation,
510
+ )
511
+ elif oType == "Hierarchy":
512
+ tom.set_translation(
513
+ object=tom.model.Tables[tName].Hierarchies[oName],
514
+ language=trName,
515
+ property=prop,
516
+ value=translation,
517
+ )
518
+ elif oType == "Level":
519
+
520
+ pattern = r"\[([^]]+)\]"
521
+ matches = re.findall(pattern, oName)
522
+ lName = matches[0]
523
+
524
+ pattern = r"'([^']+)'"
525
+ matches = re.findall(pattern, oName)
526
+ hName = matches[0]
527
+ tom.set_translation(
528
+ object=tom.model.Tables[tName].Hierarchies[hName].Levels[lName],
529
+ language=trName,
530
+ property=prop,
531
+ value=translation,
532
+ )
533
+ except Exception:
534
+ pass
535
+
536
+ print(
537
+ f"\n{icons.green_dot} Migration of objects from '{dataset}' -> '{new_dataset}' is complete."
538
+ )