semantic-link-labs 0.12.8__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.
Files changed (243) hide show
  1. semantic_link_labs-0.12.8.dist-info/METADATA +354 -0
  2. semantic_link_labs-0.12.8.dist-info/RECORD +243 -0
  3. semantic_link_labs-0.12.8.dist-info/WHEEL +5 -0
  4. semantic_link_labs-0.12.8.dist-info/licenses/LICENSE +21 -0
  5. semantic_link_labs-0.12.8.dist-info/top_level.txt +1 -0
  6. sempy_labs/__init__.py +606 -0
  7. sempy_labs/_a_lib_info.py +2 -0
  8. sempy_labs/_ai.py +437 -0
  9. sempy_labs/_authentication.py +264 -0
  10. sempy_labs/_bpa_translation/_model/_translations_am-ET.po +869 -0
  11. sempy_labs/_bpa_translation/_model/_translations_ar-AE.po +908 -0
  12. sempy_labs/_bpa_translation/_model/_translations_bg-BG.po +968 -0
  13. sempy_labs/_bpa_translation/_model/_translations_ca-ES.po +963 -0
  14. sempy_labs/_bpa_translation/_model/_translations_cs-CZ.po +943 -0
  15. sempy_labs/_bpa_translation/_model/_translations_da-DK.po +945 -0
  16. sempy_labs/_bpa_translation/_model/_translations_de-DE.po +988 -0
  17. sempy_labs/_bpa_translation/_model/_translations_el-GR.po +993 -0
  18. sempy_labs/_bpa_translation/_model/_translations_es-ES.po +971 -0
  19. sempy_labs/_bpa_translation/_model/_translations_fa-IR.po +933 -0
  20. sempy_labs/_bpa_translation/_model/_translations_fi-FI.po +942 -0
  21. sempy_labs/_bpa_translation/_model/_translations_fr-FR.po +994 -0
  22. sempy_labs/_bpa_translation/_model/_translations_ga-IE.po +967 -0
  23. sempy_labs/_bpa_translation/_model/_translations_he-IL.po +902 -0
  24. sempy_labs/_bpa_translation/_model/_translations_hi-IN.po +944 -0
  25. sempy_labs/_bpa_translation/_model/_translations_hu-HU.po +963 -0
  26. sempy_labs/_bpa_translation/_model/_translations_id-ID.po +946 -0
  27. sempy_labs/_bpa_translation/_model/_translations_is-IS.po +939 -0
  28. sempy_labs/_bpa_translation/_model/_translations_it-IT.po +986 -0
  29. sempy_labs/_bpa_translation/_model/_translations_ja-JP.po +846 -0
  30. sempy_labs/_bpa_translation/_model/_translations_ko-KR.po +839 -0
  31. sempy_labs/_bpa_translation/_model/_translations_mt-MT.po +967 -0
  32. sempy_labs/_bpa_translation/_model/_translations_nl-NL.po +978 -0
  33. sempy_labs/_bpa_translation/_model/_translations_pl-PL.po +962 -0
  34. sempy_labs/_bpa_translation/_model/_translations_pt-BR.po +962 -0
  35. sempy_labs/_bpa_translation/_model/_translations_pt-PT.po +957 -0
  36. sempy_labs/_bpa_translation/_model/_translations_ro-RO.po +968 -0
  37. sempy_labs/_bpa_translation/_model/_translations_ru-RU.po +964 -0
  38. sempy_labs/_bpa_translation/_model/_translations_sk-SK.po +952 -0
  39. sempy_labs/_bpa_translation/_model/_translations_sl-SL.po +950 -0
  40. sempy_labs/_bpa_translation/_model/_translations_sv-SE.po +942 -0
  41. sempy_labs/_bpa_translation/_model/_translations_ta-IN.po +976 -0
  42. sempy_labs/_bpa_translation/_model/_translations_te-IN.po +947 -0
  43. sempy_labs/_bpa_translation/_model/_translations_th-TH.po +924 -0
  44. sempy_labs/_bpa_translation/_model/_translations_tr-TR.po +953 -0
  45. sempy_labs/_bpa_translation/_model/_translations_uk-UA.po +961 -0
  46. sempy_labs/_bpa_translation/_model/_translations_zh-CN.po +804 -0
  47. sempy_labs/_bpa_translation/_model/_translations_zu-ZA.po +969 -0
  48. sempy_labs/_capacities.py +1198 -0
  49. sempy_labs/_capacity_migration.py +660 -0
  50. sempy_labs/_clear_cache.py +351 -0
  51. sempy_labs/_connections.py +610 -0
  52. sempy_labs/_dashboards.py +69 -0
  53. sempy_labs/_data_access_security.py +98 -0
  54. sempy_labs/_data_pipelines.py +162 -0
  55. sempy_labs/_dataflows.py +668 -0
  56. sempy_labs/_dax.py +501 -0
  57. sempy_labs/_daxformatter.py +80 -0
  58. sempy_labs/_delta_analyzer.py +467 -0
  59. sempy_labs/_delta_analyzer_history.py +301 -0
  60. sempy_labs/_dictionary_diffs.py +221 -0
  61. sempy_labs/_documentation.py +147 -0
  62. sempy_labs/_domains.py +51 -0
  63. sempy_labs/_eventhouses.py +182 -0
  64. sempy_labs/_external_data_shares.py +230 -0
  65. sempy_labs/_gateways.py +521 -0
  66. sempy_labs/_generate_semantic_model.py +521 -0
  67. sempy_labs/_get_connection_string.py +84 -0
  68. sempy_labs/_git.py +543 -0
  69. sempy_labs/_graphQL.py +90 -0
  70. sempy_labs/_helper_functions.py +2833 -0
  71. sempy_labs/_icons.py +149 -0
  72. sempy_labs/_job_scheduler.py +609 -0
  73. sempy_labs/_kql_databases.py +149 -0
  74. sempy_labs/_kql_querysets.py +124 -0
  75. sempy_labs/_kusto.py +137 -0
  76. sempy_labs/_labels.py +124 -0
  77. sempy_labs/_list_functions.py +1720 -0
  78. sempy_labs/_managed_private_endpoints.py +253 -0
  79. sempy_labs/_mirrored_databases.py +416 -0
  80. sempy_labs/_mirrored_warehouses.py +60 -0
  81. sempy_labs/_ml_experiments.py +113 -0
  82. sempy_labs/_model_auto_build.py +140 -0
  83. sempy_labs/_model_bpa.py +557 -0
  84. sempy_labs/_model_bpa_bulk.py +378 -0
  85. sempy_labs/_model_bpa_rules.py +859 -0
  86. sempy_labs/_model_dependencies.py +343 -0
  87. sempy_labs/_mounted_data_factories.py +123 -0
  88. sempy_labs/_notebooks.py +441 -0
  89. sempy_labs/_one_lake_integration.py +151 -0
  90. sempy_labs/_onelake.py +131 -0
  91. sempy_labs/_query_scale_out.py +433 -0
  92. sempy_labs/_refresh_semantic_model.py +435 -0
  93. sempy_labs/_semantic_models.py +468 -0
  94. sempy_labs/_spark.py +455 -0
  95. sempy_labs/_sql.py +241 -0
  96. sempy_labs/_sql_audit_settings.py +207 -0
  97. sempy_labs/_sql_endpoints.py +214 -0
  98. sempy_labs/_tags.py +201 -0
  99. sempy_labs/_translations.py +43 -0
  100. sempy_labs/_user_delegation_key.py +44 -0
  101. sempy_labs/_utils.py +79 -0
  102. sempy_labs/_vertipaq.py +1021 -0
  103. sempy_labs/_vpax.py +388 -0
  104. sempy_labs/_warehouses.py +234 -0
  105. sempy_labs/_workloads.py +140 -0
  106. sempy_labs/_workspace_identity.py +72 -0
  107. sempy_labs/_workspaces.py +595 -0
  108. sempy_labs/admin/__init__.py +170 -0
  109. sempy_labs/admin/_activities.py +167 -0
  110. sempy_labs/admin/_apps.py +145 -0
  111. sempy_labs/admin/_artifacts.py +65 -0
  112. sempy_labs/admin/_basic_functions.py +463 -0
  113. sempy_labs/admin/_capacities.py +508 -0
  114. sempy_labs/admin/_dataflows.py +45 -0
  115. sempy_labs/admin/_datasets.py +186 -0
  116. sempy_labs/admin/_domains.py +522 -0
  117. sempy_labs/admin/_external_data_share.py +100 -0
  118. sempy_labs/admin/_git.py +72 -0
  119. sempy_labs/admin/_items.py +265 -0
  120. sempy_labs/admin/_labels.py +211 -0
  121. sempy_labs/admin/_reports.py +241 -0
  122. sempy_labs/admin/_scanner.py +118 -0
  123. sempy_labs/admin/_shared.py +82 -0
  124. sempy_labs/admin/_sharing_links.py +110 -0
  125. sempy_labs/admin/_tags.py +131 -0
  126. sempy_labs/admin/_tenant.py +503 -0
  127. sempy_labs/admin/_tenant_keys.py +89 -0
  128. sempy_labs/admin/_users.py +140 -0
  129. sempy_labs/admin/_workspaces.py +236 -0
  130. sempy_labs/deployment_pipeline/__init__.py +23 -0
  131. sempy_labs/deployment_pipeline/_items.py +580 -0
  132. sempy_labs/directlake/__init__.py +57 -0
  133. sempy_labs/directlake/_autosync.py +58 -0
  134. sempy_labs/directlake/_directlake_schema_compare.py +120 -0
  135. sempy_labs/directlake/_directlake_schema_sync.py +161 -0
  136. sempy_labs/directlake/_dl_helper.py +274 -0
  137. sempy_labs/directlake/_generate_shared_expression.py +94 -0
  138. sempy_labs/directlake/_get_directlake_lakehouse.py +62 -0
  139. sempy_labs/directlake/_get_shared_expression.py +34 -0
  140. sempy_labs/directlake/_guardrails.py +96 -0
  141. sempy_labs/directlake/_list_directlake_model_calc_tables.py +70 -0
  142. sempy_labs/directlake/_show_unsupported_directlake_objects.py +90 -0
  143. sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +239 -0
  144. sempy_labs/directlake/_update_directlake_partition_entity.py +259 -0
  145. sempy_labs/directlake/_warm_cache.py +236 -0
  146. sempy_labs/dotnet_lib/dotnet.runtime.config.json +10 -0
  147. sempy_labs/environment/__init__.py +23 -0
  148. sempy_labs/environment/_items.py +212 -0
  149. sempy_labs/environment/_pubstage.py +223 -0
  150. sempy_labs/eventstream/__init__.py +37 -0
  151. sempy_labs/eventstream/_items.py +263 -0
  152. sempy_labs/eventstream/_topology.py +652 -0
  153. sempy_labs/graph/__init__.py +59 -0
  154. sempy_labs/graph/_groups.py +651 -0
  155. sempy_labs/graph/_sensitivity_labels.py +120 -0
  156. sempy_labs/graph/_teams.py +125 -0
  157. sempy_labs/graph/_user_licenses.py +96 -0
  158. sempy_labs/graph/_users.py +516 -0
  159. sempy_labs/graph_model/__init__.py +15 -0
  160. sempy_labs/graph_model/_background_jobs.py +63 -0
  161. sempy_labs/graph_model/_items.py +149 -0
  162. sempy_labs/lakehouse/__init__.py +67 -0
  163. sempy_labs/lakehouse/_blobs.py +247 -0
  164. sempy_labs/lakehouse/_get_lakehouse_columns.py +102 -0
  165. sempy_labs/lakehouse/_get_lakehouse_tables.py +274 -0
  166. sempy_labs/lakehouse/_helper.py +250 -0
  167. sempy_labs/lakehouse/_lakehouse.py +351 -0
  168. sempy_labs/lakehouse/_livy_sessions.py +143 -0
  169. sempy_labs/lakehouse/_materialized_lake_views.py +157 -0
  170. sempy_labs/lakehouse/_partitioning.py +165 -0
  171. sempy_labs/lakehouse/_schemas.py +217 -0
  172. sempy_labs/lakehouse/_shortcuts.py +440 -0
  173. sempy_labs/migration/__init__.py +35 -0
  174. sempy_labs/migration/_create_pqt_file.py +238 -0
  175. sempy_labs/migration/_direct_lake_to_import.py +105 -0
  176. sempy_labs/migration/_migrate_calctables_to_lakehouse.py +398 -0
  177. sempy_labs/migration/_migrate_calctables_to_semantic_model.py +148 -0
  178. sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +533 -0
  179. sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +172 -0
  180. sempy_labs/migration/_migration_validation.py +71 -0
  181. sempy_labs/migration/_refresh_calc_tables.py +131 -0
  182. sempy_labs/mirrored_azure_databricks_catalog/__init__.py +15 -0
  183. sempy_labs/mirrored_azure_databricks_catalog/_discover.py +213 -0
  184. sempy_labs/mirrored_azure_databricks_catalog/_refresh_catalog_metadata.py +45 -0
  185. sempy_labs/ml_model/__init__.py +23 -0
  186. sempy_labs/ml_model/_functions.py +427 -0
  187. sempy_labs/report/_BPAReportTemplate.json +232 -0
  188. sempy_labs/report/__init__.py +55 -0
  189. sempy_labs/report/_bpareporttemplate/.pbi/localSettings.json +9 -0
  190. sempy_labs/report/_bpareporttemplate/.platform +11 -0
  191. sempy_labs/report/_bpareporttemplate/StaticResources/SharedResources/BaseThemes/CY24SU06.json +710 -0
  192. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/page.json +11 -0
  193. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/1b08bce3bebabb0a27a8/visual.json +191 -0
  194. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/2f22ddb70c301693c165/visual.json +438 -0
  195. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/3b1182230aa6c600b43a/visual.json +127 -0
  196. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/58577ba6380c69891500/visual.json +576 -0
  197. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/a2a8fa5028b3b776c96c/visual.json +207 -0
  198. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/adfd47ef30652707b987/visual.json +506 -0
  199. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/b6a80ee459e716e170b1/visual.json +127 -0
  200. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/ce3130a721c020cc3d81/visual.json +513 -0
  201. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/page.json +8 -0
  202. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/visuals/66e60dfb526437cd78d1/visual.json +112 -0
  203. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/page.json +11 -0
  204. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/07deb8bce824e1be37d7/visual.json +513 -0
  205. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0b1c68838818b32ad03b/visual.json +352 -0
  206. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0c171de9d2683d10b930/visual.json +37 -0
  207. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0efa01be0510e40a645e/visual.json +542 -0
  208. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/6bf2f0eb830ab53cc668/visual.json +221 -0
  209. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/88d8141cb8500b60030c/visual.json +127 -0
  210. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/a753273590beed656a03/visual.json +576 -0
  211. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/b8fdc82cddd61ac447bc/visual.json +127 -0
  212. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/page.json +9 -0
  213. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/visuals/ce8532a7e25020271077/visual.json +38 -0
  214. sempy_labs/report/_bpareporttemplate/definition/pages/pages.json +10 -0
  215. sempy_labs/report/_bpareporttemplate/definition/report.json +176 -0
  216. sempy_labs/report/_bpareporttemplate/definition/version.json +4 -0
  217. sempy_labs/report/_bpareporttemplate/definition.pbir +14 -0
  218. sempy_labs/report/_download_report.py +76 -0
  219. sempy_labs/report/_export_report.py +257 -0
  220. sempy_labs/report/_generate_report.py +427 -0
  221. sempy_labs/report/_paginated.py +76 -0
  222. sempy_labs/report/_report_bpa.py +354 -0
  223. sempy_labs/report/_report_bpa_rules.py +115 -0
  224. sempy_labs/report/_report_functions.py +581 -0
  225. sempy_labs/report/_report_helper.py +227 -0
  226. sempy_labs/report/_report_list_functions.py +110 -0
  227. sempy_labs/report/_report_rebind.py +149 -0
  228. sempy_labs/report/_reportwrapper.py +3100 -0
  229. sempy_labs/report/_save_report.py +147 -0
  230. sempy_labs/snowflake_database/__init__.py +10 -0
  231. sempy_labs/snowflake_database/_items.py +105 -0
  232. sempy_labs/sql_database/__init__.py +21 -0
  233. sempy_labs/sql_database/_items.py +201 -0
  234. sempy_labs/sql_database/_mirroring.py +79 -0
  235. sempy_labs/theme/__init__.py +12 -0
  236. sempy_labs/theme/_org_themes.py +129 -0
  237. sempy_labs/tom/__init__.py +3 -0
  238. sempy_labs/tom/_model.py +5977 -0
  239. sempy_labs/variable_library/__init__.py +19 -0
  240. sempy_labs/variable_library/_functions.py +403 -0
  241. sempy_labs/warehouse/__init__.py +28 -0
  242. sempy_labs/warehouse/_items.py +234 -0
  243. sempy_labs/warehouse/_restore_points.py +309 -0
@@ -0,0 +1,398 @@
1
+ import sempy
2
+ import sempy.fabric as fabric
3
+ import pandas as pd
4
+ import re
5
+ from sempy_labs.lakehouse._get_lakehouse_tables import get_lakehouse_tables
6
+ from sempy_labs._helper_functions import (
7
+ retry,
8
+ generate_guid,
9
+ save_as_delta_table,
10
+ resolve_lakehouse_name_and_id,
11
+ resolve_workspace_name_and_id,
12
+ )
13
+ from sempy_labs.tom import connect_semantic_model
14
+ from typing import Optional
15
+ from sempy._utils._log import log
16
+ import sempy_labs._icons as icons
17
+ from uuid import UUID
18
+
19
+
20
+ @log
21
+ def migrate_calc_tables_to_lakehouse(
22
+ dataset: str,
23
+ new_dataset: str,
24
+ workspace: Optional[str | UUID] = None,
25
+ new_dataset_workspace: Optional[str | UUID] = None,
26
+ lakehouse: Optional[str | UUID] = None,
27
+ lakehouse_workspace: Optional[str | UUID] = None,
28
+ ):
29
+ """
30
+ Creates delta tables in your lakehouse based on the DAX expression of a calculated table in an import/DirectQuery semantic model.
31
+ The DAX expression encapsulating the calculated table logic is stored in the new Direct Lake semantic model as model annotations.
32
+
33
+ Parameters
34
+ ----------
35
+ dataset : str
36
+ Name of the import/DirectQuery semantic model.
37
+ new_dataset : str
38
+ Name of the Direct Lake semantic model.
39
+ workspace : str | uuid.UUID, default=None
40
+ The Fabric workspace name in which the import/DirectQuery semantic model exists.
41
+ Defaults to None which resolves to the workspace of the attached lakehouse
42
+ or if no lakehouse attached, resolves to the workspace of the notebook.
43
+ new_dataset_workspace : str | uuid.UUID
44
+ The Fabric workspace name in which the Direct Lake semantic model will be created.
45
+ Defaults to None which resolves to the workspace of the attached lakehouse
46
+ or if no lakehouse attached, resolves to the workspace of the notebook.
47
+ lakehouse : str | uuid.UUID, default=None
48
+ The Fabric lakehouse used by the Direct Lake semantic model.
49
+ Defaults to None which resolves to the lakehouse attached to the notebook.
50
+ lakehouse_workspace : str | uuid.UUID, default=None
51
+ The Fabric workspace used by the lakehouse.
52
+ Defaults to None which resolves to the workspace of the attached lakehouse
53
+ or if no lakehouse attached, resolves to the workspace of the notebook.
54
+ """
55
+
56
+ if dataset == new_dataset:
57
+ raise ValueError(
58
+ f"{icons.red_dot} The 'dataset' and 'new_dataset' parameters are both set to '{dataset}'. These parameters must be set to different values."
59
+ )
60
+
61
+ (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace)
62
+ (new_dataset_workspace_name, new_dataset_workspace_id) = (
63
+ resolve_workspace_name_and_id(new_dataset_workspace)
64
+ )
65
+ (lakehouse_workspace_id, lakehouse_workspace_name) = resolve_workspace_name_and_id(
66
+ lakehouse_workspace
67
+ )
68
+ (lakehouse_name, lakehouse_id) = resolve_lakehouse_name_and_id(
69
+ lakehouse, lakehouse_workspace
70
+ )
71
+
72
+ dfP = fabric.list_partitions(dataset=dataset, workspace=workspace)
73
+ dfP_filt = dfP[(dfP["Source Type"] == "Calculated")]
74
+ dfP_filt = dfP_filt[
75
+ ~dfP_filt["Query"].str.contains("NAMEOF")
76
+ ] # Remove field parameters
77
+ # dfC_CalcColumn = dfC[dfC['Type'] == 'Calculated']
78
+ lakeTables = get_lakehouse_tables(lakehouse, lakehouse_workspace)
79
+
80
+ # Do not execute the function if lakehouse tables already exist with the same name
81
+ killFunction = False
82
+ for i, r in dfP_filt.iterrows():
83
+ tName = r["Table Name"]
84
+ dtName = tName.replace(" ", "_")
85
+
86
+ if dtName in lakeTables["Table Name"].values:
87
+ print(
88
+ f"{icons.red_dot} The '{tName}' table already exists as '{dtName}' in the '{lakehouse_name}' lakehouse in the '{lakehouse_workspace_name}' workspace."
89
+ )
90
+ killFunction = True
91
+
92
+ if killFunction:
93
+ return
94
+
95
+ if len(dfP_filt) == 0:
96
+ print(
97
+ f"{icons.yellow_dot} The '{dataset}' semantic model in the '{workspace_name}' workspace has no calculated tables."
98
+ )
99
+ return
100
+
101
+ with connect_semantic_model(
102
+ dataset=dataset, workspace=workspace, readonly=True
103
+ ) as tom:
104
+ for t in tom.model.Tables:
105
+ if tom.is_auto_date_table(table_name=t.Name):
106
+ print(
107
+ f"{icons.yellow_dot} The '{t.Name}' table is an auto-datetime table and is not supported in the Direct Lake migration process. "
108
+ "Please create a proper Date/Calendar table in your lakehoues and use it in your Direct Lake model."
109
+ )
110
+ else:
111
+ for p in t.Partitions:
112
+ if str(p.SourceType) == "Calculated":
113
+ query = p.Source.Expression
114
+ if "NAMEOF" not in query: # exclude field parameters
115
+ daxQuery = ""
116
+ if query.lower().startswith("calendar") and any(
117
+ str(c.Type) == "Calculated" for c in t.Columns
118
+ ):
119
+ daxQuery = f"ADDCOLUMNS(\n{query},"
120
+ for c in t.Columns:
121
+ if str(c.Type) == "Calculated":
122
+ expr = c.Expression
123
+ expr = expr.replace(f"'{t.Name}'", "").replace(
124
+ f"{t.Name}[Date]", "[Date]"
125
+ )
126
+ expr = expr.replace(
127
+ "[MonthNo]", "MONTH([Date])"
128
+ ).replace(
129
+ "[QuarterNo]",
130
+ "INT((MONTH([Date]) + 2) / 3)",
131
+ )
132
+ daxQuery = f'{daxQuery}\n"{c.Name}",{expr},'
133
+ daxQuery = "EVALUATE\n" + daxQuery.rstrip(",") + "\n)"
134
+ else:
135
+ daxQuery = f"EVALUATE\n{query}"
136
+ daxQueryTopN = (
137
+ daxQuery.replace("EVALUATE\n", "EVALUATE\nTOPN(1,")
138
+ + ")"
139
+ )
140
+
141
+ try:
142
+ df = fabric.evaluate_dax(
143
+ dataset=dataset,
144
+ dax_string=daxQueryTopN,
145
+ workspace=workspace,
146
+ )
147
+
148
+ for col in df.columns:
149
+ pattern = r"\[([^\]]+)\]"
150
+
151
+ matches = re.findall(pattern, col)
152
+ new_column_name = matches[0].replace(" ", "")
153
+
154
+ df.rename(
155
+ columns={col: new_column_name},
156
+ inplace=True,
157
+ )
158
+
159
+ try:
160
+ dataType = next(
161
+ str(c.DataType)
162
+ for c in tom.model.Tables[t.Name].Columns
163
+ if str(c.Type) == "CalculatedTableColumn"
164
+ and c.SourceColumn == col
165
+ )
166
+ except Exception:
167
+ dataType = next(
168
+ str(c.DataType)
169
+ for c in tom.model.Tables[t.Name].Columns
170
+ if str(c.Type) == "Calculated"
171
+ and c.Name == new_column_name
172
+ )
173
+ if dataType == "Int64":
174
+ df[new_column_name] = df[
175
+ new_column_name
176
+ ].astype(int)
177
+ elif dataType in ["Decimal", "Double"]:
178
+ df[new_column_name] = df[
179
+ new_column_name
180
+ ].astype(float)
181
+ elif dataType == "Boolean":
182
+ df[new_column_name] = df[
183
+ new_column_name
184
+ ].astype(bool)
185
+ elif dataType == "DateTime":
186
+ df[new_column_name] = pd.to_datetime(
187
+ df[new_column_name]
188
+ )
189
+
190
+ delta_table_name = t.Name.replace(" ", "_").lower()
191
+
192
+ save_as_delta_table(
193
+ dataframe=df,
194
+ delta_table_name=delta_table_name,
195
+ lakehouse=lakehouse,
196
+ workspace=lakehouse_workspace,
197
+ write_mode="overwrite",
198
+ )
199
+
200
+ @retry(
201
+ sleep_time=1,
202
+ timeout_error_message=f"{icons.red_dot} Function timed out after 1 minute",
203
+ )
204
+ def dyn_connect():
205
+ with connect_semantic_model(
206
+ dataset=new_dataset,
207
+ readonly=True,
208
+ workspace=new_dataset_workspace,
209
+ ) as tom2:
210
+
211
+ tom2.model
212
+
213
+ dyn_connect()
214
+
215
+ with connect_semantic_model(
216
+ dataset=new_dataset,
217
+ readonly=False,
218
+ workspace=new_dataset_workspace,
219
+ ) as tom2:
220
+ tom2.set_annotation(
221
+ object=tom2.model,
222
+ name=t.Name,
223
+ value=daxQuery,
224
+ )
225
+
226
+ print(
227
+ f"{icons.green_dot} Calculated table '{t.Name}' has been created as delta table '{delta_table_name.lower()}' "
228
+ f"in the '{lakehouse_name}' lakehouse within the '{lakehouse_workspace_name}' workspace."
229
+ )
230
+ except Exception as e:
231
+ print(
232
+ f"{icons.red_dot} Failed to create calculated table '{t.Name}' as a delta table in the lakehouse."
233
+ )
234
+ print(e)
235
+
236
+
237
+ @log
238
+ def migrate_field_parameters(
239
+ dataset: str,
240
+ new_dataset: str,
241
+ workspace: Optional[str | UUID] = None,
242
+ new_dataset_workspace: Optional[str | UUID] = None,
243
+ ):
244
+ """
245
+ Migrates field parameters from one semantic model to another.
246
+
247
+ Parameters
248
+ ----------
249
+ dataset : str
250
+ Name of the import/DirectQuery semantic model.
251
+ new_dataset : str
252
+ Name of the Direct Lake semantic model.
253
+ workspace : str | uuid.UUID, default=None
254
+ The Fabric workspace name in which the import/DirectQuery semantic model exists.
255
+ Defaults to None which resolves to the workspace of the attached lakehouse
256
+ or if no lakehouse attached, resolves to the workspace of the notebook.
257
+ new_dataset_workspace : str | uuid.UUID, default=None
258
+ The Fabric workspace name in which the Direct Lake semantic model will be created.
259
+ Defaults to None which resolves to the workspace of the attached lakehouse
260
+ or if no lakehouse attached, resolves to the workspace of the notebook.
261
+ """
262
+
263
+ from sempy_labs import format_dax_object_name
264
+
265
+ sempy.fabric._client._utils._init_analysis_services()
266
+ import Microsoft.AnalysisServices.Tabular as TOM
267
+
268
+ icons.sll_tags.append("DirectLakeMigration")
269
+ fabric.refresh_tom_cache(workspace=workspace)
270
+ (new_dataset_workspace_name, new_dataset_workspace_id) = (
271
+ resolve_workspace_name_and_id(new_dataset_workspace)
272
+ )
273
+
274
+ dfC = fabric.list_columns(dataset=dataset, workspace=workspace)
275
+ dfC["Column Object"] = format_dax_object_name(dfC["Table Name"], dfC["Column Name"])
276
+ dfP = fabric.list_partitions(dataset=dataset, workspace=workspace)
277
+ dfP_filt = dfP[(dfP["Source Type"] == "Calculated")]
278
+ dfP_filt = dfP_filt[
279
+ dfP_filt["Query"].str.contains("NAMEOF")
280
+ ] # Only field parameters
281
+ dfC_CalcColumn = dfC[dfC["Type"] == "Calculated"]
282
+
283
+ if len(dfP_filt) == 0:
284
+ print(
285
+ f"{icons.green_dot} The '{dataset}' semantic model in the '{workspace}' workspace has no field parameters."
286
+ )
287
+ return
288
+
289
+ @retry(
290
+ sleep_time=1,
291
+ timeout_error_message=f"{icons.red_dot} Function timed out after 1 minute",
292
+ )
293
+ def dyn_connect():
294
+ with connect_semantic_model(
295
+ dataset=new_dataset, readonly=True, workspace=new_dataset_workspace
296
+ ) as tom:
297
+
298
+ tom.model
299
+
300
+ dyn_connect()
301
+
302
+ with connect_semantic_model(
303
+ dataset=new_dataset, workspace=new_dataset_workspace, readonly=False
304
+ ) as tom:
305
+
306
+ for i, r in dfP_filt.iterrows():
307
+ tName = r["Table Name"]
308
+ query = r["Query"]
309
+
310
+ # For field parameters, remove calc columns from the query
311
+ rows = query.strip().split("\n")
312
+ filtered_rows = [
313
+ row
314
+ for row in rows
315
+ if not any(
316
+ value in row for value in dfC_CalcColumn["Column Object"].values
317
+ )
318
+ ]
319
+ updated_query_string = "\n".join(filtered_rows)
320
+
321
+ # Remove extra comma
322
+ lines = updated_query_string.strip().split("\n")
323
+ lines[-2] = lines[-2].rstrip(",")
324
+ expr = "\n".join(lines)
325
+
326
+ try:
327
+ par = TOM.Partition()
328
+ par.Name = tName
329
+ par.Mode = TOM.ModeType.Import
330
+
331
+ parSource = TOM.CalculatedPartitionSource()
332
+ par.Source = parSource
333
+ parSource.Expression = expr
334
+
335
+ tbl = TOM.Table()
336
+ tbl.Name = tName
337
+ tbl.LineageTag = generate_guid()
338
+ tbl.Partitions.Add(par)
339
+
340
+ columns = ["Value1", "Value2", "Value3"]
341
+
342
+ for colName in columns:
343
+ col = TOM.CalculatedTableColumn()
344
+ col.Name = colName
345
+ col.SourceColumn = "[" + colName + "]"
346
+ col.DataType = TOM.DataType.String
347
+ col.LineageTag = generate_guid()
348
+
349
+ tbl.Columns.Add(col)
350
+
351
+ tom.model.Tables.Add(tbl)
352
+
353
+ ep = TOM.JsonExtendedProperty()
354
+ ep.Name = "ParameterMetadata"
355
+ ep.Value = '{"version":3,"kind":2}'
356
+
357
+ rcd = TOM.RelatedColumnDetails()
358
+ gpc = TOM.GroupByColumn()
359
+ gpc.GroupingColumn = tom.model.Tables[tName].Columns["Value2"]
360
+ rcd.GroupByColumns.Add(gpc)
361
+
362
+ # Update column properties
363
+ tom.model.Tables[tName].Columns["Value2"].IsHidden = True
364
+ tom.model.Tables[tName].Columns["Value3"].IsHidden = True
365
+ tom.model.Tables[tName].Columns["Value3"].DataType = TOM.DataType.Int64
366
+ tom.model.Tables[tName].Columns["Value1"].SortByColumn = (
367
+ tom.model.Tables[tName].Columns["Value3"]
368
+ )
369
+ tom.model.Tables[tName].Columns["Value2"].SortByColumn = (
370
+ tom.model.Tables[tName].Columns["Value3"]
371
+ )
372
+ tom.model.Tables[tName].Columns["Value2"].ExtendedProperties.Add(ep)
373
+ tom.model.Tables[tName].Columns["Value1"].RelatedColumnDetails = rcd
374
+
375
+ dfC_filt1 = dfC[
376
+ (dfC["Table Name"] == tName) & (dfC["Source"] == "[Value1]")
377
+ ]
378
+ col1 = dfC_filt1["Column Name"].iloc[0]
379
+ dfC_filt2 = dfC[
380
+ (dfC["Table Name"] == tName) & (dfC["Source"] == "[Value2]")
381
+ ]
382
+ col2 = dfC_filt2["Column Name"].iloc[0]
383
+ dfC_filt3 = dfC[
384
+ (dfC["Table Name"] == tName) & (dfC["Source"] == "[Value3]")
385
+ ]
386
+ col3 = dfC_filt3["Column Name"].iloc[0]
387
+
388
+ tom.model.Tables[tName].Columns["Value1"].Name = col1
389
+ tom.model.Tables[tName].Columns["Value2"].Name = col2
390
+ tom.model.Tables[tName].Columns["Value3"].Name = col3
391
+
392
+ print(
393
+ f"{icons.green_dot} The '{tName}' table has been added as a field parameter to the '{new_dataset}' semantic model in the '{new_dataset_workspace_name}' workspace."
394
+ )
395
+ except Exception:
396
+ print(
397
+ f"{icons.red_dot} The '{tName}' table has not been added as a field parameter."
398
+ )
@@ -0,0 +1,148 @@
1
+ import sempy.fabric as fabric
2
+ import re
3
+ from sempy_labs.lakehouse._get_lakehouse_tables import get_lakehouse_tables
4
+ from sempy_labs._helper_functions import (
5
+ format_dax_object_name,
6
+ retry,
7
+ )
8
+ from sempy_labs.tom import connect_semantic_model
9
+ from typing import Optional
10
+ from sempy._utils._log import log
11
+ import sempy_labs._icons as icons
12
+ from uuid import UUID
13
+
14
+
15
+ @log
16
+ def migrate_calc_tables_to_semantic_model(
17
+ dataset: str,
18
+ new_dataset: str,
19
+ workspace: Optional[str | UUID] = None,
20
+ new_dataset_workspace: Optional[str | UUID] = None,
21
+ lakehouse: Optional[str | UUID] = None,
22
+ lakehouse_workspace: Optional[str | UUID] = None,
23
+ ):
24
+ """
25
+ Creates new tables in the Direct Lake semantic model based on the lakehouse tables created using the 'migrate_calc_tables_to_lakehouse' function.
26
+
27
+ Parameters
28
+ ----------
29
+ dataset : str
30
+ Name of the import/DirectQuery semantic model.
31
+ new_dataset : str
32
+ Name of the Direct Lake semantic model.
33
+ workspace : str | uuid.UUID, default=None
34
+ The Fabric workspace name in which the import/DirectQuery semantic model exists.
35
+ Defaults to None which resolves to the workspace of the attached lakehouse
36
+ or if no lakehouse attached, resolves to the workspace of the notebook.
37
+ new_dataset_workspace : str | uuid.UUID, default=None
38
+ The Fabric workspace name in which the Direct Lake semantic model will be created.
39
+ Defaults to None which resolves to the workspace of the attached lakehouse
40
+ or if no lakehouse attached, resolves to the workspace of the notebook.
41
+ lakehouse : str | uuid.UUID, default=None
42
+ The Fabric lakehouse used by the Direct Lake semantic model.
43
+ Defaults to None which resolves to the lakehouse attached to the notebook.
44
+ lakehouse_workspace : str | uuid.UUID, default=None
45
+ The Fabric workspace used by the lakehouse.
46
+ Defaults to None which resolves to the workspace of the attached lakehouse
47
+ or if no lakehouse attached, resolves to the workspace of the notebook.
48
+ """
49
+
50
+ if dataset == new_dataset:
51
+ raise ValueError(
52
+ f"{icons.red_dot} The 'dataset' and 'new_dataset' parameters are both set to '{dataset}'. These parameters must be set to different values."
53
+ )
54
+
55
+ fabric.refresh_tom_cache(workspace=workspace)
56
+
57
+ icons.sll_tags.append("DirectLakeMigration")
58
+
59
+ # Get calc tables but not field parameters
60
+ dfP = fabric.list_partitions(dataset=dataset, workspace=workspace)
61
+ dfP_filt = dfP[(dfP["Source Type"] == "Calculated")]
62
+ dfP_filt = dfP_filt[~dfP_filt["Query"].str.contains("NAMEOF")]
63
+
64
+ dfC = fabric.list_columns(dataset=dataset, workspace=workspace)
65
+ lc = get_lakehouse_tables(lakehouse=lakehouse, workspace=lakehouse_workspace)
66
+ # Get all calc table columns of calc tables not including field parameters
67
+ dfC_filt = dfC[
68
+ (dfC["Table Name"].isin(dfP_filt["Table Name"]))
69
+ ] # & (dfC['Type'] == 'CalculatedTableColumn')]
70
+ # dfA = fabric.list_annotations(new_dataset, new_dataset_workspace)
71
+ # dfA_filt = dfA[(dfA['Object Type'] == 'Model') & ~ (dfA['Annotation Value'].str.contains('NAMEOF'))]
72
+
73
+ if len(dfP_filt) == 0:
74
+ print(
75
+ f"{icons.green_dot} The '{dataset}' semantic model has no calculated tables."
76
+ )
77
+ return
78
+
79
+ @retry(
80
+ sleep_time=1,
81
+ timeout_error_message=f"{icons.red_dot} Function timed out after 1 minute",
82
+ )
83
+ def dyn_connect():
84
+ with connect_semantic_model(
85
+ dataset=new_dataset, readonly=True, workspace=new_dataset_workspace
86
+ ) as tom:
87
+
88
+ tom.model
89
+
90
+ dyn_connect()
91
+
92
+ with connect_semantic_model(
93
+ dataset=new_dataset, readonly=False, workspace=new_dataset_workspace
94
+ ) as tom:
95
+ for tName in dfC_filt["Table Name"].unique():
96
+ if tName.lower() in lc["Table Name"].values:
97
+ if not any(t.Name == tName for t in tom.model.Tables):
98
+ tom.add_table(name=tName)
99
+ tom.add_entity_partition(
100
+ table_name=tName,
101
+ entity_name=tName.replace(" ", "_").lower(),
102
+ )
103
+
104
+ columns_in_table = dfC_filt.loc[
105
+ dfC_filt["Table Name"] == tName, "Column Name"
106
+ ].unique()
107
+
108
+ for cName in columns_in_table:
109
+ scName = dfC.loc[
110
+ (dfC["Table Name"] == tName) & (dfC["Column Name"] == cName),
111
+ "Source",
112
+ ].iloc[0]
113
+ cDataType = dfC.loc[
114
+ (dfC["Table Name"] == tName) & (dfC["Column Name"] == cName),
115
+ "Data Type",
116
+ ].iloc[0]
117
+ # cType = dfC.loc[
118
+ # (dfC["Table Name"] == tName)
119
+ # & (dfC["Column Name"] == cName),
120
+ # "Type",
121
+ # ].iloc[0]
122
+
123
+ # av = tom.get_annotation_value(object = tom.model, name = tName)
124
+
125
+ # if cType == 'CalculatedTableColumn':
126
+ # lakeColumn = scName.replace(' ','_')
127
+ # elif cType == 'Calculated':
128
+ pattern = r"\[([^]]+)\]"
129
+
130
+ matches = re.findall(pattern, scName)
131
+ lakeColumn = matches[0].replace(" ", "")
132
+ if not any(
133
+ c.Name == cName and c.Parent.Name == tName
134
+ for c in tom.all_columns()
135
+ ):
136
+ tom.add_data_column(
137
+ table_name=tName,
138
+ column_name=cName,
139
+ source_column=lakeColumn,
140
+ data_type=cDataType,
141
+ )
142
+ print(
143
+ f"{icons.green_dot} The {format_dax_object_name(tName,cName)} column has been added."
144
+ )
145
+
146
+ print(
147
+ f"\n{icons.green_dot} All viable calculated tables have been added to the model."
148
+ )