trovesuite 1.0.1__py3-none-any.whl → 1.0.31__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.
@@ -0,0 +1,529 @@
1
+ from datetime import datetime, timedelta
2
+ from typing import List
3
+ from azure.storage.blob import BlobServiceClient, BlobSasPermissions, generate_blob_sas
4
+ from azure.identity import DefaultAzureCredential, ManagedIdentityCredential
5
+ from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError
6
+ from ..entities.sh_response import Respons
7
+ from .storage_read_dto import (
8
+ StorageContainerCreateServiceReadDto,
9
+ StorageFileUploadServiceReadDto,
10
+ StorageFileUpdateServiceReadDto,
11
+ StorageFileDeleteServiceReadDto,
12
+ StorageFileDownloadServiceReadDto,
13
+ StorageFileUrlServiceReadDto
14
+ )
15
+ from .storage_write_dto import (
16
+ StorageContainerCreateServiceWriteDto,
17
+ StorageFileUploadServiceWriteDto,
18
+ StorageFileUpdateServiceWriteDto,
19
+ StorageFileDeleteServiceWriteDto,
20
+ StorageFileDownloadServiceWriteDto,
21
+ StorageFileUrlServiceWriteDto
22
+ )
23
+
24
+
25
+ class StorageService:
26
+ """
27
+ Azure Storage service for managing blob storage operations using Managed Identity authentication.
28
+ Supports both system-assigned and user-assigned managed identities.
29
+ """
30
+
31
+ @staticmethod
32
+ def _get_credential(managed_identity_client_id: str = None):
33
+ """
34
+ Get Azure credential for authentication.
35
+ - Tries Managed Identity (for Azure environments)
36
+ - Falls back to DefaultAzureCredential (for local dev)
37
+ """
38
+ try:
39
+ if managed_identity_client_id:
40
+ return ManagedIdentityCredential(client_id=managed_identity_client_id)
41
+ else:
42
+ return DefaultAzureCredential(exclude_managed_identity_credential=True)
43
+
44
+ except Exception:
45
+
46
+ # Always fallback to DefaultAzureCredential
47
+ return DefaultAzureCredential(exclude_managed_identity_credential=True)
48
+
49
+
50
+ @staticmethod
51
+ def _get_blob_service_client(storage_account_url: str, managed_identity_client_id: str = None) -> BlobServiceClient:
52
+ """
53
+ Create a BlobServiceClient using Managed Identity.
54
+
55
+ Args:
56
+ storage_account_url: Azure Storage account URL (e.g., https://myaccount.blob.core.windows.net)
57
+ managed_identity_client_id: Optional client ID for user-assigned managed identity
58
+
59
+ Returns:
60
+ BlobServiceClient instance
61
+ """
62
+ credential = StorageService._get_credential(managed_identity_client_id)
63
+ return BlobServiceClient(account_url=storage_account_url, credential=credential)
64
+
65
+ @staticmethod
66
+ def create_container(data: StorageContainerCreateServiceWriteDto) -> Respons[StorageContainerCreateServiceReadDto]:
67
+ """
68
+ Create a new Azure Storage container.
69
+
70
+ Args:
71
+ data: Container creation parameters including storage account URL and container name
72
+
73
+ Returns:
74
+ Response object containing container details or error information
75
+ """
76
+ try:
77
+ blob_service_client = StorageService._get_blob_service_client(
78
+ data.storage_account_url,
79
+ data.managed_identity_client_id
80
+ )
81
+ container_client = blob_service_client.create_container(
82
+ name=data.container_name,
83
+ public_access=data.public_access
84
+ )
85
+
86
+ container_url = container_client.url
87
+
88
+ result = StorageContainerCreateServiceReadDto(
89
+ container_name=data.container_name,
90
+ container_url=container_url
91
+ )
92
+
93
+ return Respons[StorageContainerCreateServiceReadDto](
94
+ detail=f"Container '{data.container_name}' created successfully",
95
+ error=None,
96
+ data=[result],
97
+ status_code=201,
98
+ success=True,
99
+ )
100
+
101
+ except ResourceExistsError:
102
+ return Respons[StorageContainerCreateServiceReadDto](
103
+ detail=f"Container '{data.container_name}' already exists",
104
+ error="Resource already exists",
105
+ data=[],
106
+ status_code=409,
107
+ success=False,
108
+ )
109
+ except Exception as e:
110
+ return Respons[StorageContainerCreateServiceReadDto](
111
+ detail="Failed to create container",
112
+ error=str(e),
113
+ data=[],
114
+ status_code=500,
115
+ success=False,
116
+ )
117
+
118
+ @staticmethod
119
+ def upload_file(data: StorageFileUploadServiceWriteDto) -> Respons[StorageFileUploadServiceReadDto]:
120
+ """
121
+ Upload a file to Azure Storage blob container.
122
+
123
+ Args:
124
+ data: File upload parameters including storage account URL, container name,
125
+ file content, blob name, and optional directory path
126
+
127
+ Returns:
128
+ Response object containing uploaded file details or error information
129
+ """
130
+ try:
131
+ # Construct the full blob name with directory path if provided
132
+ blob_name = data.blob_name
133
+ if data.directory_path:
134
+ # Ensure directory path ends with / and doesn't start with /
135
+ dir_path = data.directory_path.strip('/')
136
+ blob_name = f"{dir_path}/{blob_name}"
137
+
138
+ blob_service_client = StorageService._get_blob_service_client(
139
+ data.storage_account_url,
140
+ data.managed_identity_client_id
141
+ )
142
+ blob_client = blob_service_client.get_blob_client(
143
+ container=data.container_name,
144
+ blob=blob_name
145
+ )
146
+
147
+ # Upload the file
148
+ content_settings = None
149
+ if data.content_type:
150
+ from azure.storage.blob import ContentSettings
151
+ content_settings = ContentSettings(content_type=data.content_type)
152
+
153
+ blob_client.upload_blob(
154
+ data.file_content,
155
+ overwrite=False,
156
+ content_settings=content_settings
157
+ )
158
+
159
+ # Get blob properties
160
+ properties = blob_client.get_blob_properties()
161
+
162
+ result = StorageFileUploadServiceReadDto(
163
+ blob_name=blob_name,
164
+ blob_url=blob_client.url,
165
+ content_type=properties.content_settings.content_type if properties.content_settings else None,
166
+ size=properties.size
167
+ )
168
+
169
+ return Respons[StorageFileUploadServiceReadDto](
170
+ detail=f"File '{blob_name}' uploaded successfully",
171
+ error=None,
172
+ data=[result],
173
+ status_code=201,
174
+ success=True,
175
+ )
176
+
177
+ except ResourceExistsError:
178
+ return Respons[StorageFileUploadServiceReadDto](
179
+ detail=f"File '{blob_name}' already exists",
180
+ error="Resource already exists. Use update_file to modify existing files.",
181
+ data=[],
182
+ status_code=409,
183
+ success=False,
184
+ )
185
+ except Exception as e:
186
+ return Respons[StorageFileUploadServiceReadDto](
187
+ detail="Failed to upload file",
188
+ error=str(e),
189
+ data=[],
190
+ status_code=500,
191
+ success=False,
192
+ )
193
+
194
+ @staticmethod
195
+ def update_file(data: StorageFileUpdateServiceWriteDto) -> Respons[StorageFileUpdateServiceReadDto]:
196
+ """
197
+ Update an existing file in Azure Storage blob container.
198
+
199
+ Args:
200
+ data: File update parameters including storage account URL, container name,
201
+ blob name, and new file content
202
+
203
+ Returns:
204
+ Response object containing updated file details or error information
205
+ """
206
+ try:
207
+ blob_service_client = StorageService._get_blob_service_client(
208
+ data.storage_account_url,
209
+ data.managed_identity_client_id
210
+ )
211
+ blob_client = blob_service_client.get_blob_client(
212
+ container=data.container_name,
213
+ blob=data.blob_name
214
+ )
215
+
216
+ # Upload with overwrite=True to update
217
+ content_settings = None
218
+ if data.content_type:
219
+ from azure.storage.blob import ContentSettings
220
+ content_settings = ContentSettings(content_type=data.content_type)
221
+
222
+ blob_client.upload_blob(
223
+ data.file_content,
224
+ overwrite=True,
225
+ content_settings=content_settings
226
+ )
227
+
228
+ # Get updated properties
229
+ properties = blob_client.get_blob_properties()
230
+
231
+ result = StorageFileUpdateServiceReadDto(
232
+ blob_name=data.blob_name,
233
+ blob_url=blob_client.url,
234
+ updated_at=properties.last_modified.isoformat() if properties.last_modified else None
235
+ )
236
+
237
+ return Respons[StorageFileUpdateServiceReadDto](
238
+ detail=f"File '{data.blob_name}' updated successfully",
239
+ error=None,
240
+ data=[result],
241
+ status_code=200,
242
+ success=True,
243
+ )
244
+
245
+ except ResourceNotFoundError:
246
+ return Respons[StorageFileUpdateServiceReadDto](
247
+ detail=f"File '{data.blob_name}' not found",
248
+ error="Resource not found. Use upload_file to create new files.",
249
+ data=[],
250
+ status_code=404,
251
+ success=False,
252
+ )
253
+ except Exception as e:
254
+ return Respons[StorageFileUpdateServiceReadDto](
255
+ detail="Failed to update file",
256
+ error=str(e),
257
+ data=[],
258
+ status_code=500,
259
+ success=False,
260
+ )
261
+
262
+ @staticmethod
263
+ def delete_file(data: StorageFileDeleteServiceWriteDto) -> Respons[StorageFileDeleteServiceReadDto]:
264
+ """
265
+ Delete a file from Azure Storage blob container.
266
+
267
+ Args:
268
+ data: File delete parameters including storage account URL, container name,
269
+ and blob name
270
+
271
+ Returns:
272
+ Response object containing deletion status or error information
273
+ """
274
+ try:
275
+ blob_service_client = StorageService._get_blob_service_client(
276
+ data.storage_account_url,
277
+ data.managed_identity_client_id
278
+ )
279
+ blob_client = blob_service_client.get_blob_client(
280
+ container=data.container_name,
281
+ blob=data.blob_name
282
+ )
283
+
284
+ # Delete the blob
285
+ blob_client.delete_blob()
286
+
287
+ result = StorageFileDeleteServiceReadDto(
288
+ blob_name=data.blob_name,
289
+ deleted=True
290
+ )
291
+
292
+ return Respons[StorageFileDeleteServiceReadDto](
293
+ detail=f"File '{data.blob_name}' deleted successfully",
294
+ error=None,
295
+ data=[result],
296
+ status_code=200,
297
+ success=True,
298
+ )
299
+
300
+ except ResourceNotFoundError:
301
+ return Respons[StorageFileDeleteServiceReadDto](
302
+ detail=f"File '{data.blob_name}' not found",
303
+ error="Resource not found",
304
+ data=[],
305
+ status_code=404,
306
+ success=False,
307
+ )
308
+ except Exception as e:
309
+ return Respons[StorageFileDeleteServiceReadDto](
310
+ detail="Failed to delete file",
311
+ error=str(e),
312
+ data=[],
313
+ status_code=500,
314
+ success=False,
315
+ )
316
+
317
+ @staticmethod
318
+ def delete_multiple_files(
319
+ storage_account_url: str,
320
+ container_name: str,
321
+ blob_names: List[str],
322
+ managed_identity_client_id: str = None
323
+ ) -> Respons[StorageFileDeleteServiceReadDto]:
324
+ """
325
+ Delete multiple files from Azure Storage blob container.
326
+
327
+ Args:
328
+ storage_account_url: Azure Storage account URL
329
+ container_name: Name of the container
330
+ blob_names: List of blob names to delete
331
+ managed_identity_client_id: Optional client ID for user-assigned managed identity
332
+
333
+ Returns:
334
+ Response object containing deletion status for all files
335
+ """
336
+ try:
337
+ blob_service_client = StorageService._get_blob_service_client(
338
+ storage_account_url,
339
+ managed_identity_client_id
340
+ )
341
+ container_client = blob_service_client.get_container_client(container_name)
342
+
343
+ results = []
344
+ errors = []
345
+
346
+ for blob_name in blob_names:
347
+ try:
348
+ blob_client = container_client.get_blob_client(blob_name)
349
+ blob_client.delete_blob()
350
+ results.append(StorageFileDeleteServiceReadDto(
351
+ blob_name=blob_name,
352
+ deleted=True
353
+ ))
354
+ except ResourceNotFoundError:
355
+ errors.append(f"File '{blob_name}' not found")
356
+ except Exception as e:
357
+ errors.append(f"Failed to delete '{blob_name}': {str(e)}")
358
+
359
+ if errors and not results:
360
+ return Respons[StorageFileDeleteServiceReadDto](
361
+ detail="Failed to delete any files",
362
+ error="; ".join(errors),
363
+ data=[],
364
+ status_code=500,
365
+ success=False,
366
+ )
367
+ elif errors:
368
+ return Respons[StorageFileDeleteServiceReadDto](
369
+ detail=f"Deleted {len(results)} file(s) with {len(errors)} error(s)",
370
+ error="; ".join(errors),
371
+ data=results,
372
+ status_code=207, # Multi-Status
373
+ success=True,
374
+ )
375
+ else:
376
+ return Respons[StorageFileDeleteServiceReadDto](
377
+ detail=f"Successfully deleted {len(results)} file(s)",
378
+ error=None,
379
+ data=results,
380
+ status_code=200,
381
+ success=True,
382
+ )
383
+
384
+ except Exception as e:
385
+ return Respons[StorageFileDeleteServiceReadDto](
386
+ detail="Failed to delete files",
387
+ error=str(e),
388
+ data=[],
389
+ status_code=500,
390
+ success=False,
391
+ )
392
+
393
+ @staticmethod
394
+ def download_file(data: StorageFileDownloadServiceWriteDto) -> Respons[StorageFileDownloadServiceReadDto]:
395
+ """
396
+ Download a file from Azure Storage blob container.
397
+
398
+ Args:
399
+ data: File download parameters including storage account URL, container name,
400
+ and blob name
401
+
402
+ Returns:
403
+ Response object containing file content and metadata or error information
404
+ """
405
+ try:
406
+ blob_service_client = StorageService._get_blob_service_client(
407
+ data.storage_account_url,
408
+ data.managed_identity_client_id
409
+ )
410
+ blob_client = blob_service_client.get_blob_client(
411
+ container=data.container_name,
412
+ blob=data.blob_name
413
+ )
414
+
415
+ # Download the blob
416
+ download_stream = blob_client.download_blob()
417
+ file_content = download_stream.readall()
418
+
419
+ # Get blob properties
420
+ properties = blob_client.get_blob_properties()
421
+
422
+ result = StorageFileDownloadServiceReadDto(
423
+ blob_name=data.blob_name,
424
+ content=file_content,
425
+ content_type=properties.content_settings.content_type if properties.content_settings else None,
426
+ size=properties.size
427
+ )
428
+
429
+ return Respons[StorageFileDownloadServiceReadDto](
430
+ detail=f"File '{data.blob_name}' downloaded successfully",
431
+ error=None,
432
+ data=[result],
433
+ status_code=200,
434
+ success=True,
435
+ )
436
+
437
+ except ResourceNotFoundError:
438
+ return Respons[StorageFileDownloadServiceReadDto](
439
+ detail=f"File '{data.blob_name}' not found",
440
+ error="Resource not found",
441
+ data=[],
442
+ status_code=404,
443
+ success=False,
444
+ )
445
+ except Exception as e:
446
+ return Respons[StorageFileDownloadServiceReadDto](
447
+ detail="Failed to download file",
448
+ error=str(e),
449
+ data=[],
450
+ status_code=500,
451
+ success=False,
452
+ )
453
+
454
+ @staticmethod
455
+ def get_file_url(data: StorageFileUrlServiceWriteDto) -> Respons[StorageFileUrlServiceReadDto]:
456
+ """
457
+ Generate a presigned URL (SAS token) for a file in Azure Storage blob container.
458
+ Note: This requires the storage account to have a shared key available.
459
+
460
+ Args:
461
+ data: URL generation parameters including storage account URL, container name,
462
+ blob name, and expiry time in hours
463
+
464
+ Returns:
465
+ Response object containing presigned URL or error information
466
+ """
467
+ try:
468
+ blob_service_client = StorageService._get_blob_service_client(
469
+ data.storage_account_url,
470
+ data.managed_identity_client_id
471
+ )
472
+ blob_client = blob_service_client.get_blob_client(
473
+ container=data.container_name,
474
+ blob=data.blob_name
475
+ )
476
+
477
+ # Check if blob exists
478
+ if not blob_client.exists():
479
+ return Respons[StorageFileUrlServiceReadDto](
480
+ detail=f"File '{data.blob_name}' not found",
481
+ error="Resource not found",
482
+ data=[],
483
+ status_code=404,
484
+ success=False,
485
+ )
486
+
487
+ # Generate user delegation key for SAS token (works with Managed Identity)
488
+ # This requires the managed identity to have "Storage Blob Delegator" role
489
+ delegation_key = blob_service_client.get_user_delegation_key(
490
+ key_start_time=datetime.utcnow(),
491
+ key_expiry_time=datetime.utcnow() + timedelta(hours=data.expiry_hours or 1)
492
+ )
493
+
494
+ # Generate SAS token using user delegation key
495
+ from azure.storage.blob import generate_blob_sas, BlobSasPermissions
496
+ sas_token = generate_blob_sas(
497
+ account_name=blob_service_client.account_name,
498
+ container_name=data.container_name,
499
+ blob_name=data.blob_name,
500
+ user_delegation_key=delegation_key,
501
+ permission=BlobSasPermissions(read=True),
502
+ expiry=datetime.utcnow() + timedelta(hours=data.expiry_hours or 1)
503
+ )
504
+
505
+ # Construct the full URL with SAS token
506
+ presigned_url = f"{blob_client.url}?{sas_token}"
507
+
508
+ result = StorageFileUrlServiceReadDto(
509
+ blob_name=data.blob_name,
510
+ presigned_url=presigned_url,
511
+ expires_in_hours=data.expiry_hours or 1
512
+ )
513
+
514
+ return Respons[StorageFileUrlServiceReadDto](
515
+ detail=f"Presigned URL generated for '{data.blob_name}'",
516
+ error=None,
517
+ data=[result],
518
+ status_code=200,
519
+ success=True,
520
+ )
521
+
522
+ except Exception as e:
523
+ return Respons[StorageFileUrlServiceReadDto](
524
+ detail="Failed to generate presigned URL",
525
+ error=str(e),
526
+ data=[],
527
+ status_code=500,
528
+ success=False,
529
+ )
@@ -0,0 +1,70 @@
1
+ from pydantic import BaseModel
2
+ from .storage_base import (
3
+ StorageConnectionBase,
4
+ StorageFileUploadBase,
5
+ StorageFileUpdateBase,
6
+ StorageFileDeleteBase,
7
+ StorageFileDownloadBase,
8
+ StorageFileUrlBase,
9
+ StorageContainerCreateBase
10
+ )
11
+
12
+
13
+ # Container Creation
14
+
15
+ class StorageContainerCreateControllerWriteDto(StorageContainerCreateBase):
16
+ pass
17
+
18
+
19
+ class StorageContainerCreateServiceWriteDto(StorageContainerCreateControllerWriteDto):
20
+ pass
21
+
22
+
23
+ # File Upload
24
+
25
+ class StorageFileUploadControllerWriteDto(StorageFileUploadBase):
26
+ pass
27
+
28
+
29
+ class StorageFileUploadServiceWriteDto(StorageFileUploadControllerWriteDto):
30
+ pass
31
+
32
+
33
+ # File Update
34
+
35
+ class StorageFileUpdateControllerWriteDto(StorageFileUpdateBase):
36
+ pass
37
+
38
+
39
+ class StorageFileUpdateServiceWriteDto(StorageFileUpdateControllerWriteDto):
40
+ pass
41
+
42
+
43
+ # File Delete
44
+
45
+ class StorageFileDeleteControllerWriteDto(StorageFileDeleteBase):
46
+ pass
47
+
48
+
49
+ class StorageFileDeleteServiceWriteDto(StorageFileDeleteControllerWriteDto):
50
+ pass
51
+
52
+
53
+ # File Download
54
+
55
+ class StorageFileDownloadControllerWriteDto(StorageFileDownloadBase):
56
+ pass
57
+
58
+
59
+ class StorageFileDownloadServiceWriteDto(StorageFileDownloadControllerWriteDto):
60
+ pass
61
+
62
+
63
+ # File URL
64
+
65
+ class StorageFileUrlControllerWriteDto(StorageFileUrlBase):
66
+ pass
67
+
68
+
69
+ class StorageFileUrlServiceWriteDto(StorageFileUrlControllerWriteDto):
70
+ pass
@@ -5,7 +5,9 @@ Utility functions and helpers for TroveSuite services.
5
5
  """
6
6
 
7
7
  from .helper import Helper
8
+ from . import templates
8
9
 
9
10
  __all__ = [
10
- "Helper"
11
+ "Helper",
12
+ "templates",
11
13
  ]