semantic-link-labs 0.11.1__py3-none-any.whl → 0.11.3__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 (137) hide show
  1. {semantic_link_labs-0.11.1.dist-info → semantic_link_labs-0.11.3.dist-info}/METADATA +7 -6
  2. semantic_link_labs-0.11.3.dist-info/RECORD +212 -0
  3. sempy_labs/__init__.py +65 -71
  4. sempy_labs/_a_lib_info.py +1 -1
  5. sempy_labs/_ai.py +1 -1
  6. sempy_labs/_capacities.py +2 -2
  7. sempy_labs/_capacity_migration.py +5 -5
  8. sempy_labs/_clear_cache.py +1 -1
  9. sempy_labs/_connections.py +2 -2
  10. sempy_labs/_dashboards.py +16 -16
  11. sempy_labs/_data_pipelines.py +1 -1
  12. sempy_labs/_dataflows.py +101 -26
  13. sempy_labs/_dax.py +3 -3
  14. sempy_labs/_delta_analyzer.py +4 -4
  15. sempy_labs/_delta_analyzer_history.py +1 -1
  16. sempy_labs/_deployment_pipelines.py +1 -1
  17. sempy_labs/_environments.py +22 -21
  18. sempy_labs/_eventhouses.py +12 -11
  19. sempy_labs/_eventstreams.py +12 -11
  20. sempy_labs/_external_data_shares.py +78 -23
  21. sempy_labs/_gateways.py +47 -45
  22. sempy_labs/_generate_semantic_model.py +3 -3
  23. sempy_labs/_git.py +1 -1
  24. sempy_labs/_graphQL.py +12 -11
  25. sempy_labs/_helper_functions.py +169 -5
  26. sempy_labs/_job_scheduler.py +56 -54
  27. sempy_labs/_kql_databases.py +16 -17
  28. sempy_labs/_kql_querysets.py +12 -11
  29. sempy_labs/_kusto.py +2 -2
  30. sempy_labs/_labels.py +126 -0
  31. sempy_labs/_list_functions.py +2 -2
  32. sempy_labs/_managed_private_endpoints.py +18 -15
  33. sempy_labs/_mirrored_databases.py +16 -15
  34. sempy_labs/_mirrored_warehouses.py +12 -11
  35. sempy_labs/_ml_experiments.py +11 -10
  36. sempy_labs/_model_auto_build.py +3 -3
  37. sempy_labs/_model_bpa.py +5 -5
  38. sempy_labs/_model_bpa_bulk.py +3 -3
  39. sempy_labs/_model_dependencies.py +1 -1
  40. sempy_labs/_mounted_data_factories.py +12 -12
  41. sempy_labs/_notebooks.py +151 -2
  42. sempy_labs/_one_lake_integration.py +1 -1
  43. sempy_labs/_query_scale_out.py +1 -1
  44. sempy_labs/_refresh_semantic_model.py +1 -1
  45. sempy_labs/_semantic_models.py +30 -28
  46. sempy_labs/_spark.py +1 -1
  47. sempy_labs/_sql.py +1 -1
  48. sempy_labs/_sql_endpoints.py +12 -11
  49. sempy_labs/_sqldatabase.py +15 -15
  50. sempy_labs/_tags.py +11 -10
  51. sempy_labs/_translations.py +1 -1
  52. sempy_labs/_user_delegation_key.py +2 -2
  53. sempy_labs/_vertipaq.py +3 -3
  54. sempy_labs/_vpax.py +1 -1
  55. sempy_labs/_warehouses.py +15 -14
  56. sempy_labs/_workloads.py +1 -1
  57. sempy_labs/_workspace_identity.py +1 -1
  58. sempy_labs/_workspaces.py +14 -13
  59. sempy_labs/admin/__init__.py +18 -18
  60. sempy_labs/admin/_activities.py +46 -46
  61. sempy_labs/admin/_apps.py +28 -26
  62. sempy_labs/admin/_artifacts.py +15 -15
  63. sempy_labs/admin/_basic_functions.py +1 -2
  64. sempy_labs/admin/_capacities.py +84 -82
  65. sempy_labs/admin/_dataflows.py +2 -2
  66. sempy_labs/admin/_datasets.py +50 -48
  67. sempy_labs/admin/_domains.py +25 -19
  68. sempy_labs/admin/_external_data_share.py +24 -22
  69. sempy_labs/admin/_git.py +17 -17
  70. sempy_labs/admin/_items.py +47 -45
  71. sempy_labs/admin/_reports.py +61 -58
  72. sempy_labs/admin/_scanner.py +2 -2
  73. sempy_labs/admin/_shared.py +18 -18
  74. sempy_labs/admin/_tags.py +2 -2
  75. sempy_labs/admin/_tenant.py +57 -51
  76. sempy_labs/admin/_users.py +16 -15
  77. sempy_labs/admin/_workspaces.py +2 -2
  78. sempy_labs/directlake/__init__.py +12 -12
  79. sempy_labs/directlake/_directlake_schema_compare.py +3 -3
  80. sempy_labs/directlake/_directlake_schema_sync.py +9 -7
  81. sempy_labs/directlake/_dl_helper.py +5 -2
  82. sempy_labs/directlake/_generate_shared_expression.py +1 -1
  83. sempy_labs/directlake/_get_directlake_lakehouse.py +1 -1
  84. sempy_labs/directlake/_guardrails.py +1 -1
  85. sempy_labs/directlake/_list_directlake_model_calc_tables.py +3 -3
  86. sempy_labs/directlake/_show_unsupported_directlake_objects.py +1 -1
  87. sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +3 -3
  88. sempy_labs/directlake/_update_directlake_partition_entity.py +4 -4
  89. sempy_labs/directlake/_warm_cache.py +3 -3
  90. sempy_labs/graph/__init__.py +3 -3
  91. sempy_labs/graph/_groups.py +81 -78
  92. sempy_labs/graph/_teams.py +21 -21
  93. sempy_labs/graph/_users.py +109 -10
  94. sempy_labs/lakehouse/__init__.py +7 -7
  95. sempy_labs/lakehouse/_blobs.py +30 -30
  96. sempy_labs/lakehouse/_get_lakehouse_columns.py +2 -2
  97. sempy_labs/lakehouse/_get_lakehouse_tables.py +29 -27
  98. sempy_labs/lakehouse/_helper.py +38 -1
  99. sempy_labs/lakehouse/_lakehouse.py +16 -7
  100. sempy_labs/lakehouse/_livy_sessions.py +47 -42
  101. sempy_labs/lakehouse/_shortcuts.py +22 -21
  102. sempy_labs/migration/__init__.py +8 -8
  103. sempy_labs/migration/_create_pqt_file.py +2 -2
  104. sempy_labs/migration/_migrate_calctables_to_lakehouse.py +35 -44
  105. sempy_labs/migration/_migrate_calctables_to_semantic_model.py +9 -20
  106. sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +5 -9
  107. sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +11 -20
  108. sempy_labs/migration/_migration_validation.py +1 -2
  109. sempy_labs/migration/_refresh_calc_tables.py +2 -2
  110. sempy_labs/mirrored_azure_databricks_catalog/__init__.py +2 -2
  111. sempy_labs/mirrored_azure_databricks_catalog/_discover.py +40 -40
  112. sempy_labs/mirrored_azure_databricks_catalog/_refresh_catalog_metadata.py +1 -1
  113. sempy_labs/ml_model/__init__.py +23 -0
  114. sempy_labs/ml_model/_functions.py +427 -0
  115. sempy_labs/report/__init__.py +10 -10
  116. sempy_labs/report/_download_report.py +2 -2
  117. sempy_labs/report/_export_report.py +2 -2
  118. sempy_labs/report/_generate_report.py +1 -1
  119. sempy_labs/report/_paginated.py +1 -1
  120. sempy_labs/report/_report_bpa.py +4 -3
  121. sempy_labs/report/_report_functions.py +3 -3
  122. sempy_labs/report/_report_list_functions.py +3 -3
  123. sempy_labs/report/_report_rebind.py +1 -1
  124. sempy_labs/report/_reportwrapper.py +248 -250
  125. sempy_labs/report/_save_report.py +3 -3
  126. sempy_labs/theme/_org_themes.py +19 -6
  127. sempy_labs/tom/__init__.py +1 -1
  128. sempy_labs/tom/_model.py +13 -8
  129. sempy_labs/variable_library/__init__.py +19 -0
  130. sempy_labs/variable_library/_functions.py +403 -0
  131. semantic_link_labs-0.11.1.dist-info/RECORD +0 -210
  132. sempy_labs/_dax_query_view.py +0 -57
  133. sempy_labs/_ml_models.py +0 -110
  134. sempy_labs/_variable_libraries.py +0 -91
  135. {semantic_link_labs-0.11.1.dist-info → semantic_link_labs-0.11.3.dist-info}/WHEEL +0 -0
  136. {semantic_link_labs-0.11.1.dist-info → semantic_link_labs-0.11.3.dist-info}/licenses/LICENSE +0 -0
  137. {semantic_link_labs-0.11.1.dist-info → semantic_link_labs-0.11.3.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  import pandas as pd
2
2
  from uuid import UUID
3
- from sempy_labs._helper_functions import (
3
+ from .._helper_functions import (
4
4
  _is_valid_uuid,
5
5
  _base_api,
6
6
  _create_dataframe,
@@ -75,27 +75,28 @@ def list_groups() -> pd.DataFrame:
75
75
 
76
76
  df = _create_dataframe(columns=columns)
77
77
 
78
- dfs = []
78
+ rows = []
79
79
  for v in result.get("value"):
80
- new_data = {
81
- "Group Id": v.get("id"),
82
- "Group Name": v.get("displayName"),
83
- "Mail": v.get("mail"),
84
- "Description": v.get("description"),
85
- "Classification": v.get("classification"),
86
- "Mail Enabled": v.get("mailEnabled"),
87
- "Security Enabled": v.get("securityEnabled"),
88
- "Created Date Time": v.get("createdDateTime"),
89
- "Expiration Date Time": v.get("expirationDateTime"),
90
- "Renewed Date Time": v.get("renewedDateTime"),
91
- "Deleted Date Time": v.get("deletedDateTime"),
92
- "Visibility": v.get("visibility"),
93
- "Security Identifier": v.get("securityIdentifier"),
94
- }
95
- dfs.append(pd.DataFrame(new_data, index=[0]))
96
-
97
- if dfs:
98
- df = pd.concat(dfs, ignore_index=True)
80
+ rows.append(
81
+ {
82
+ "Group Id": v.get("id"),
83
+ "Group Name": v.get("displayName"),
84
+ "Mail": v.get("mail"),
85
+ "Description": v.get("description"),
86
+ "Classification": v.get("classification"),
87
+ "Mail Enabled": v.get("mailEnabled"),
88
+ "Security Enabled": v.get("securityEnabled"),
89
+ "Created Date Time": v.get("createdDateTime"),
90
+ "Expiration Date Time": v.get("expirationDateTime"),
91
+ "Renewed Date Time": v.get("renewedDateTime"),
92
+ "Deleted Date Time": v.get("deletedDateTime"),
93
+ "Visibility": v.get("visibility"),
94
+ "Security Identifier": v.get("securityIdentifier"),
95
+ }
96
+ )
97
+
98
+ if rows:
99
+ df = pd.DataFrame(rows, columns=list(columns.keys()))
99
100
  _update_dataframe_datatypes(dataframe=df, column_map=columns)
100
101
 
101
102
  return df
@@ -140,28 +141,28 @@ def _get_group(group_id: UUID) -> pd.DataFrame:
140
141
  }
141
142
  df = _create_dataframe(columns=columns)
142
143
 
143
- dfs = []
144
+ rows = []
144
145
  for v in result.get("value"):
145
- new_data = {
146
- "Group Id": v.get("id"),
147
- "Group Name": v.get("displayName"),
148
- "Mail": v.get("mail"),
149
- "Description": v.get("description"),
150
- "Classification": v.get("classification"),
151
- "Mail Enabled": v.get("mailEnabled"),
152
- "Security Enabled": v.get("securityEnabled"),
153
- "Created Date Time": v.get("createdDateTime"),
154
- "Expiration Date Time": v.get("expirationDateTime"),
155
- "Deleted Date Time": v.get("deletedDateTime"),
156
- "Renewed Date Time": v.get("renewedDateTime"),
157
- "Visibility": v.get("visibility"),
158
- "Security Identifier": v.get("securityIdentifier"),
159
- }
160
-
161
- dfs.append(pd.DataFrame(new_data, index=[0]))
162
-
163
- if dfs:
164
- df = pd.concat(dfs, ignore_index=True)
146
+ rows.append(
147
+ {
148
+ "Group Id": v.get("id"),
149
+ "Group Name": v.get("displayName"),
150
+ "Mail": v.get("mail"),
151
+ "Description": v.get("description"),
152
+ "Classification": v.get("classification"),
153
+ "Mail Enabled": v.get("mailEnabled"),
154
+ "Security Enabled": v.get("securityEnabled"),
155
+ "Created Date Time": v.get("createdDateTime"),
156
+ "Expiration Date Time": v.get("expirationDateTime"),
157
+ "Deleted Date Time": v.get("deletedDateTime"),
158
+ "Renewed Date Time": v.get("renewedDateTime"),
159
+ "Visibility": v.get("visibility"),
160
+ "Security Identifier": v.get("securityIdentifier"),
161
+ }
162
+ )
163
+
164
+ if rows:
165
+ df = pd.DataFrame(rows, columns=list(columns.keys()))
165
166
  _update_dataframe_datatypes(dataframe=df, column_map=columns)
166
167
 
167
168
  return df
@@ -207,25 +208,26 @@ def list_group_members(group: str | UUID) -> pd.DataFrame:
207
208
 
208
209
  df = _create_dataframe(columns=columns)
209
210
 
210
- dfs = []
211
+ rows = []
211
212
  for v in result.get("value"):
212
- new_data = {
213
- "Member Id": v.get("id"),
214
- "Member Name": v.get("displayName"),
215
- "User Principal Name": v.get("userPrincipalName"),
216
- "Mail": v.get("mail"),
217
- "Job Title": v.get("jobTitle"),
218
- "Office Location": v.get("officeLocation"),
219
- "Mobile Phone": v.get("mobilePhone"),
220
- "Business Phones": str(v.get("businessPhones")),
221
- "Preferred Language": v.get("preferredLanguage"),
222
- "Given Name": v.get("givenName"),
223
- "Surname": v.get("surname"),
224
- }
225
- dfs.append(pd.DataFrame(new_data, index=[0]))
226
-
227
- if dfs:
228
- df = pd.concat(dfs, ignore_index=True)
213
+ rows.append(
214
+ {
215
+ "Member Id": v.get("id"),
216
+ "Member Name": v.get("displayName"),
217
+ "User Principal Name": v.get("userPrincipalName"),
218
+ "Mail": v.get("mail"),
219
+ "Job Title": v.get("jobTitle"),
220
+ "Office Location": v.get("officeLocation"),
221
+ "Mobile Phone": v.get("mobilePhone"),
222
+ "Business Phones": str(v.get("businessPhones")),
223
+ "Preferred Language": v.get("preferredLanguage"),
224
+ "Given Name": v.get("givenName"),
225
+ "Surname": v.get("surname"),
226
+ }
227
+ )
228
+
229
+ if rows:
230
+ df = pd.DataFrame(rows, columns=list(columns.keys()))
229
231
 
230
232
  return df
231
233
 
@@ -270,25 +272,26 @@ def list_group_owners(group: str | UUID) -> pd.DataFrame:
270
272
 
271
273
  df = _create_dataframe(columns=columns)
272
274
 
273
- dfs = []
275
+ rows = []
274
276
  for v in result.get("value"):
275
- new_data = {
276
- "Owner Id": v.get("id"),
277
- "Owner Name": v.get("displayName"),
278
- "User Principal Name": v.get("userPrincipalName"),
279
- "Mail": v.get("mail"),
280
- "Job Title": v.get("jobTitle"),
281
- "Office Location": v.get("officeLocation"),
282
- "Mobile Phone": v.get("mobilePhone"),
283
- "Business Phones": str(v.get("businessPhones")),
284
- "Preferred Language": v.get("preferredLanguage"),
285
- "Given Name": v.get("givenName"),
286
- "Surname": v.get("surname"),
287
- }
288
- dfs.append(pd.DataFrame(new_data, index=[0]))
289
-
290
- if dfs:
291
- df = pd.concat(dfs, ignore_index=True)
277
+ rows.append(
278
+ {
279
+ "Owner Id": v.get("id"),
280
+ "Owner Name": v.get("displayName"),
281
+ "User Principal Name": v.get("userPrincipalName"),
282
+ "Mail": v.get("mail"),
283
+ "Job Title": v.get("jobTitle"),
284
+ "Office Location": v.get("officeLocation"),
285
+ "Mobile Phone": v.get("mobilePhone"),
286
+ "Business Phones": str(v.get("businessPhones")),
287
+ "Preferred Language": v.get("preferredLanguage"),
288
+ "Given Name": v.get("givenName"),
289
+ "Surname": v.get("surname"),
290
+ }
291
+ )
292
+
293
+ if rows:
294
+ df = pd.DataFrame(rows, columns=list(columns.keys()))
292
295
 
293
296
  return df
294
297
 
@@ -1,7 +1,7 @@
1
1
  import pandas as pd
2
2
  from uuid import UUID
3
3
  from sempy._utils._log import log
4
- from sempy_labs._helper_functions import (
4
+ from .._helper_functions import (
5
5
  _base_api,
6
6
  _create_dataframe,
7
7
  _update_dataframe_datatypes,
@@ -42,27 +42,27 @@ def list_teams() -> pd.DataFrame:
42
42
 
43
43
  df = _create_dataframe(columns=columns)
44
44
 
45
- dfs = []
45
+ rows = []
46
46
  for v in result.get("value"):
47
- new_data = {
48
- "Team Id": v.get("id"),
49
- "Team Name": v.get("displayName"),
50
- "Description": v.get("description"),
51
- "Creation Date Time": v.get("createdDateTime"),
52
- "Classification": v.get("classification"),
53
- "Specialization": v.get("specialization"),
54
- "Visibility": v.get("visibility"),
55
- "Web Url": v.get("webUrl"),
56
- "Archived": v.get("isArchived"),
57
- "Favorite By Me": v.get("isFavoriteByMe"),
58
- "Discoverable By Me": v.get("isDiscoverableByMe"),
59
- "Member Count": v.get("memberCount"),
60
- }
61
-
62
- dfs.append(pd.DataFrame(new_data, index=[0]))
63
-
64
- if dfs:
65
- df = pd.concat(dfs, ignore_index=True)
47
+ rows.append(
48
+ {
49
+ "Team Id": v.get("id"),
50
+ "Team Name": v.get("displayName"),
51
+ "Description": v.get("description"),
52
+ "Creation Date Time": v.get("createdDateTime"),
53
+ "Classification": v.get("classification"),
54
+ "Specialization": v.get("specialization"),
55
+ "Visibility": v.get("visibility"),
56
+ "Web Url": v.get("webUrl"),
57
+ "Archived": v.get("isArchived"),
58
+ "Favorite By Me": v.get("isFavoriteByMe"),
59
+ "Discoverable By Me": v.get("isDiscoverableByMe"),
60
+ "Member Count": v.get("memberCount"),
61
+ }
62
+ )
63
+
64
+ if rows:
65
+ df = pd.DataFrame(rows, columns=list(columns.keys()))
66
66
  _update_dataframe_datatypes(dataframe=df, column_map=columns)
67
67
 
68
68
  return df
@@ -1,11 +1,14 @@
1
1
  import pandas as pd
2
+ import os
3
+ import base64
2
4
  from uuid import UUID
3
5
  import sempy_labs._icons as icons
4
- from typing import List
5
- from sempy_labs._helper_functions import (
6
+ from typing import List, Literal, Optional
7
+ from .._helper_functions import (
6
8
  _is_valid_uuid,
7
9
  _base_api,
8
10
  _create_dataframe,
11
+ _mount,
9
12
  )
10
13
  from sempy._utils._log import log
11
14
 
@@ -130,8 +133,11 @@ def send_mail(
130
133
  subject: str,
131
134
  to_recipients: str | List[str],
132
135
  content: str,
133
- content_type: str = "Text",
134
- cc_recipients: str | List[str] = None,
136
+ content_type: Literal["Text", "HTML"] = "Text",
137
+ cc_recipients: Optional[str | List[str]] = None,
138
+ bcc_recipients: Optional[str | List[str]] = None,
139
+ priority: Literal["Normal", "High", "Low"] = "Normal",
140
+ attachments: Optional[str | List[str]] = None,
135
141
  ):
136
142
  """
137
143
  Sends an email to the specified recipients.
@@ -150,16 +156,25 @@ def send_mail(
150
156
  The email address of the recipients.
151
157
  content : str
152
158
  The email content.
153
- content_type : str, default="Text"
159
+ content_type : Literal["Text", "HTML"], default="Text"
154
160
  The email content type. Options: "Text" or "HTML".
155
161
  cc_recipients : str | List[str], default=None
156
162
  The email address of the CC recipients.
163
+ bcc_recipients : str | List[str], default=None
164
+ The email address of the BCC recipients.
165
+ priority : Literal["Normal", "High", "Low"], default="Normal"
166
+ The email priority.
167
+ attachments : str | List[str], default=None
168
+ The abfss path or a list of the abfss paths of the attachments to include in the email.
157
169
  """
158
170
 
159
- if content_type.lower() == "html":
160
- content_type = "HTML"
161
- else:
162
- content_type = "Text"
171
+ content_type = "HTML" if content_type.lower() == "html" else "Text"
172
+
173
+ priority = priority.capitalize()
174
+ if priority not in ["Normal", "High", "Low"]:
175
+ raise ValueError(
176
+ f"{icons.red_dot} Invalid priority: {priority}. Options are: Normal, High, Low."
177
+ )
163
178
 
164
179
  user_id = resolve_user_id(user=user)
165
180
 
@@ -178,6 +193,11 @@ def send_mail(
178
193
  if cc_recipients
179
194
  else None
180
195
  )
196
+ bcc_email_addresses = (
197
+ [{"emailAddress": {"address": email}} for email in bcc_recipients]
198
+ if bcc_recipients
199
+ else None
200
+ )
181
201
 
182
202
  payload = {
183
203
  "message": {
@@ -187,12 +207,84 @@ def send_mail(
187
207
  "content": content,
188
208
  },
189
209
  "toRecipients": to_email_addresses,
210
+ "importance": priority,
190
211
  },
191
212
  }
192
213
 
193
214
  if cc_email_addresses:
194
215
  payload["message"]["ccRecipients"] = cc_email_addresses
195
216
 
217
+ if bcc_email_addresses:
218
+ payload["message"]["bccRecipients"] = bcc_email_addresses
219
+
220
+ # if follow_up_flag:
221
+ # payload["message"]["flag"] = {"flagStatus": "flagged"}
222
+
223
+ content_types = {
224
+ ".txt": "text/plain",
225
+ ".pdf": "application/pdf",
226
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
227
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
228
+ ".csv": "text/csv",
229
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
230
+ ".jpg": "image/jpeg",
231
+ ".jpeg": "image/jpeg",
232
+ ".png": "image/png",
233
+ ".gif": "image/gif",
234
+ ".bmp": "image/bmp",
235
+ ".zip": "application/zip",
236
+ ".json": "application/json",
237
+ ".xml": "application/xml",
238
+ ".html": "text/html",
239
+ ".bim": "application/json",
240
+ ".pbix": "application/vnd.ms-powerbi.report",
241
+ ".pbip": "application/vnd.ms-powerbi.report",
242
+ ".pbit": "application/vnd.ms-powerbi.report",
243
+ ".vpax": "application/zip",
244
+ ".geojson": "application/geo+json",
245
+ }
246
+
247
+ def file_path_to_content_bytes(file_path):
248
+
249
+ path_parts = file_path.split("abfss://")[1].split("@")
250
+ workspace = path_parts[0]
251
+
252
+ rest = path_parts[1].split(".microsoft.com/")[1]
253
+ lakehouse, *file_parts = rest.split("/")
254
+ if lakehouse.endswith(".Lakehouse"):
255
+ lakehouse = lakehouse.removesuffix(".Lakehouse")
256
+ relative_path = os.path.join(*file_parts)
257
+
258
+ local_path = _mount(lakehouse, workspace)
259
+ full_path = os.path.join(local_path, relative_path)
260
+
261
+ with open(full_path, "rb") as file:
262
+ return base64.b64encode(file.read()).decode("utf-8")
263
+
264
+ if isinstance(attachments, str):
265
+ attachments = [attachments]
266
+ if attachments:
267
+ attachments_list = []
268
+ for attach_path in attachments:
269
+ content_bytes = file_path_to_content_bytes(attach_path)
270
+ file_extension = os.path.splitext(attach_path)[1]
271
+ content_type = content_types.get(file_extension)
272
+ if not content_type:
273
+ raise ValueError(
274
+ f"{icons.red_dot} Unsupported file type: {file_extension}. Supported types are: {', '.join(content_types.keys())}."
275
+ )
276
+ attachments_list.append(
277
+ {
278
+ "@odata.type": "#microsoft.graph.fileAttachment",
279
+ "name": attach_path.split("/")[-1],
280
+ "contentType": content_type,
281
+ "contentBytes": content_bytes,
282
+ }
283
+ )
284
+
285
+ # Add to payload
286
+ payload["message"]["attachments"] = attachments_list
287
+
196
288
  _base_api(
197
289
  request=f"users/{user_id}/sendMail",
198
290
  client="graph",
@@ -201,4 +293,11 @@ def send_mail(
201
293
  method="post",
202
294
  )
203
295
 
204
- print(f"{icons.green_dot} The email has been sent to {to_recipients}.")
296
+ printout = f"{icons.green_dot} The email has been sent to {to_recipients}"
297
+ if cc_recipients:
298
+ printout += f" and CCed to {cc_recipients}"
299
+ if bcc_recipients:
300
+ printout += f" and BCCed to {bcc_recipients}"
301
+ if attachments:
302
+ printout += f" with {len(attachments)} attachment(s)"
303
+ print(f"{printout}.")
@@ -1,30 +1,30 @@
1
- from sempy_labs.lakehouse._get_lakehouse_columns import (
1
+ from ._get_lakehouse_columns import (
2
2
  get_lakehouse_columns,
3
3
  )
4
- from sempy_labs.lakehouse._get_lakehouse_tables import (
4
+ from ._get_lakehouse_tables import (
5
5
  get_lakehouse_tables,
6
6
  )
7
- from sempy_labs.lakehouse._lakehouse import (
7
+ from ._lakehouse import (
8
8
  lakehouse_attached,
9
9
  optimize_lakehouse_tables,
10
10
  vacuum_lakehouse_tables,
11
11
  run_table_maintenance,
12
12
  )
13
- from sempy_labs.lakehouse._shortcuts import (
13
+ from ._shortcuts import (
14
14
  # create_shortcut,
15
15
  create_shortcut_onelake,
16
16
  delete_shortcut,
17
17
  reset_shortcut_cache,
18
18
  list_shortcuts,
19
19
  )
20
- from sempy_labs.lakehouse._blobs import (
20
+ from ._blobs import (
21
21
  recover_lakehouse_object,
22
22
  list_blobs,
23
23
  )
24
- from sempy_labs.lakehouse._livy_sessions import (
24
+ from ._livy_sessions import (
25
25
  list_livy_sessions,
26
26
  )
27
- from sempy_labs.lakehouse._helper import (
27
+ from ._helper import (
28
28
  is_v_ordered,
29
29
  delete_lakehouse,
30
30
  update_lakehouse,
@@ -1,4 +1,4 @@
1
- from sempy_labs._helper_functions import (
1
+ from .._helper_functions import (
2
2
  resolve_workspace_id,
3
3
  resolve_lakehouse_id,
4
4
  _xml_to_dict,
@@ -146,7 +146,7 @@ def list_blobs(
146
146
  uses_pagination=True,
147
147
  )
148
148
 
149
- dfs = []
149
+ rows = []
150
150
  for root in responses:
151
151
  response_json = _xml_to_dict(root)
152
152
 
@@ -159,34 +159,34 @@ def list_blobs(
159
159
 
160
160
  for blob in blobs:
161
161
  p = blob.get("Properties", {})
162
- new_data = {
163
- "Blob Name": blob.get("Name"),
164
- "Is Deleted": blob.get("Deleted", False),
165
- "Deletion Id": blob.get("DeletionId"),
166
- "Creation Time": p.get("Creation-Time"),
167
- "Expiry Time": p.get("Expiry-Time"),
168
- "Etag": p.get("Etag"),
169
- "Resource Type": p.get("ResourceType"),
170
- "Content Length": p.get("Content-Length"),
171
- "Content Type": p.get("Content-Type"),
172
- "Content Encoding": p.get("Content-Encoding"),
173
- "Content Language": p.get("Content-Language"),
174
- "Content CRC64": p.get("Content-CRC64"),
175
- "Content MD5": p.get("Content-MD5"),
176
- "Cache Control": p.get("Cache-Control"),
177
- "Content Disposition": p.get("Content-Disposition"),
178
- "Blob Type": p.get("BlobType"),
179
- "Access Tier": p.get("AccessTier"),
180
- "Access Tier Inferred": p.get("AccessTierInferred"),
181
- "Server Encrypted": p.get("ServerEncrypted"),
182
- "Deleted Time": p.get("DeletedTime"),
183
- "Remaining Retention Days": p.get("RemainingRetentionDays"),
184
- }
185
-
186
- dfs.append(pd.DataFrame(new_data, index=[0]))
187
-
188
- if dfs:
189
- df = pd.concat(dfs, ignore_index=True)
162
+ rows.append(
163
+ {
164
+ "Blob Name": blob.get("Name"),
165
+ "Is Deleted": blob.get("Deleted", False),
166
+ "Deletion Id": blob.get("DeletionId"),
167
+ "Creation Time": p.get("Creation-Time"),
168
+ "Expiry Time": p.get("Expiry-Time"),
169
+ "Etag": p.get("Etag"),
170
+ "Resource Type": p.get("ResourceType"),
171
+ "Content Length": p.get("Content-Length"),
172
+ "Content Type": p.get("Content-Type"),
173
+ "Content Encoding": p.get("Content-Encoding"),
174
+ "Content Language": p.get("Content-Language"),
175
+ "Content CRC64": p.get("Content-CRC64"),
176
+ "Content MD5": p.get("Content-MD5"),
177
+ "Cache Control": p.get("Cache-Control"),
178
+ "Content Disposition": p.get("Content-Disposition"),
179
+ "Blob Type": p.get("BlobType"),
180
+ "Access Tier": p.get("AccessTier"),
181
+ "Access Tier Inferred": p.get("AccessTierInferred"),
182
+ "Server Encrypted": p.get("ServerEncrypted"),
183
+ "Deleted Time": p.get("DeletedTime"),
184
+ "Remaining Retention Days": p.get("RemainingRetentionDays"),
185
+ }
186
+ )
187
+
188
+ if rows:
189
+ df = pd.DataFrame(rows, columns=list(columns.keys()))
190
190
  _update_dataframe_datatypes(dataframe=df, column_map=columns)
191
191
 
192
192
  return df
@@ -1,6 +1,6 @@
1
1
  import pandas as pd
2
2
  import re
3
- from sempy_labs._helper_functions import (
3
+ from .._helper_functions import (
4
4
  format_dax_object_name,
5
5
  resolve_workspace_name_and_id,
6
6
  resolve_lakehouse_name_and_id,
@@ -38,7 +38,7 @@ def get_lakehouse_columns(
38
38
  pandas.DataFrame
39
39
  Shows the tables/columns within a lakehouse and their properties.
40
40
  """
41
- from sempy_labs.lakehouse._get_lakehouse_tables import get_lakehouse_tables
41
+ from ._get_lakehouse_tables import get_lakehouse_tables
42
42
 
43
43
  columns = {
44
44
  "Workspace Name": "string",