azpaddypy 0.5.9__py3-none-any.whl → 0.5.11__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.
- azpaddypy/resources/__init__.py +3 -0
- azpaddypy/resources/storage.py +830 -0
- azpaddypy-0.5.11.dist-info/METADATA +19 -0
- azpaddypy-0.5.11.dist-info/RECORD +13 -0
- azpaddypy-0.5.9.dist-info/METADATA +0 -16
- azpaddypy-0.5.9.dist-info/RECORD +0 -12
- {azpaddypy-0.5.9.dist-info → azpaddypy-0.5.11.dist-info}/WHEEL +0 -0
- {azpaddypy-0.5.9.dist-info → azpaddypy-0.5.11.dist-info}/licenses/LICENSE +0 -0
- {azpaddypy-0.5.9.dist-info → azpaddypy-0.5.11.dist-info}/top_level.txt +0 -0
azpaddypy/resources/__init__.py
CHANGED
@@ -5,8 +5,11 @@ including Key Vault, Storage, and other Azure services.
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
from .keyvault import AzureKeyVault, create_azure_keyvault
|
8
|
+
from .storage import AzureStorage, create_azure_storage
|
8
9
|
|
9
10
|
__all__ = [
|
10
11
|
"AzureKeyVault",
|
11
12
|
"create_azure_keyvault",
|
13
|
+
"AzureStorage",
|
14
|
+
"create_azure_storage",
|
12
15
|
]
|
@@ -0,0 +1,830 @@
|
|
1
|
+
from typing import Optional, Dict, Any, Union, List, BinaryIO
|
2
|
+
from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient
|
3
|
+
from azure.storage.fileshare import ShareServiceClient, ShareClient, ShareFileClient
|
4
|
+
from azure.storage.queue import QueueServiceClient, QueueClient
|
5
|
+
from azure.core.exceptions import ResourceNotFoundError, ClientAuthenticationError, ResourceExistsError
|
6
|
+
from azure.core.credentials import TokenCredential
|
7
|
+
from ..mgmt.logging import AzureLogger
|
8
|
+
from ..mgmt.identity import AzureIdentity
|
9
|
+
import io
|
10
|
+
from datetime import datetime
|
11
|
+
|
12
|
+
|
13
|
+
class AzureStorage:
|
14
|
+
"""Azure Storage Account management with comprehensive blob, file, and queue operations.
|
15
|
+
|
16
|
+
Provides standardized Azure Storage operations using Azure SDK clients
|
17
|
+
with integrated logging, error handling, and OpenTelemetry tracing support.
|
18
|
+
Supports operations for blob storage, file shares, and queues with proper
|
19
|
+
authentication and authorization handling. Container and queue creation
|
20
|
+
is handled by infrastructure as code.
|
21
|
+
|
22
|
+
Attributes:
|
23
|
+
account_url: Azure Storage Account URL
|
24
|
+
service_name: Service identifier for logging and tracing
|
25
|
+
service_version: Service version for context
|
26
|
+
logger: AzureLogger instance for structured logging
|
27
|
+
credential: Azure credential for authentication
|
28
|
+
blob_service_client: Azure Blob Service Client instance
|
29
|
+
file_service_client: Azure File Share Service Client instance
|
30
|
+
queue_service_client: Azure Queue Service Client instance
|
31
|
+
"""
|
32
|
+
|
33
|
+
def __init__(
|
34
|
+
self,
|
35
|
+
account_url: str,
|
36
|
+
credential: Optional[TokenCredential] = None,
|
37
|
+
azure_identity: Optional[AzureIdentity] = None,
|
38
|
+
service_name: str = "azure_storage",
|
39
|
+
service_version: str = "1.0.0",
|
40
|
+
logger: Optional[AzureLogger] = None,
|
41
|
+
connection_string: Optional[str] = None,
|
42
|
+
enable_blob_storage: bool = True,
|
43
|
+
enable_file_storage: bool = True,
|
44
|
+
enable_queue_storage: bool = True,
|
45
|
+
):
|
46
|
+
"""Initialize Azure Storage with comprehensive configuration.
|
47
|
+
|
48
|
+
Args:
|
49
|
+
account_url: Azure Storage Account URL (e.g., https://account.blob.core.windows.net/)
|
50
|
+
credential: Azure credential for authentication
|
51
|
+
azure_identity: AzureIdentity instance for credential management
|
52
|
+
service_name: Service name for tracing context
|
53
|
+
service_version: Service version for metadata
|
54
|
+
logger: Optional AzureLogger instance
|
55
|
+
connection_string: Application Insights connection string
|
56
|
+
enable_blob_storage: Enable blob storage operations client
|
57
|
+
enable_file_storage: Enable file storage operations client
|
58
|
+
enable_queue_storage: Enable queue storage operations client
|
59
|
+
|
60
|
+
Raises:
|
61
|
+
ValueError: If neither credential nor azure_identity is provided
|
62
|
+
Exception: If client initialization fails
|
63
|
+
"""
|
64
|
+
self.account_url = account_url
|
65
|
+
self.service_name = service_name
|
66
|
+
self.service_version = service_version
|
67
|
+
self.enable_blob_storage = enable_blob_storage
|
68
|
+
self.enable_file_storage = enable_file_storage
|
69
|
+
self.enable_queue_storage = enable_queue_storage
|
70
|
+
|
71
|
+
# Initialize logger - use provided instance or create new one
|
72
|
+
if logger is not None:
|
73
|
+
self.logger = logger
|
74
|
+
else:
|
75
|
+
self.logger = AzureLogger(
|
76
|
+
service_name=service_name,
|
77
|
+
service_version=service_version,
|
78
|
+
connection_string=connection_string,
|
79
|
+
enable_console_logging=True,
|
80
|
+
)
|
81
|
+
|
82
|
+
# Setup credential
|
83
|
+
if azure_identity is not None:
|
84
|
+
self.credential = azure_identity.get_credential()
|
85
|
+
self.azure_identity = azure_identity
|
86
|
+
elif credential is not None:
|
87
|
+
self.credential = credential
|
88
|
+
self.azure_identity = None
|
89
|
+
else:
|
90
|
+
raise ValueError("Either 'credential' or 'azure_identity' must be provided")
|
91
|
+
|
92
|
+
# Initialize clients
|
93
|
+
self.blob_service_client = None
|
94
|
+
self.file_service_client = None
|
95
|
+
self.queue_service_client = None
|
96
|
+
|
97
|
+
self._setup_clients()
|
98
|
+
|
99
|
+
self.logger.info(
|
100
|
+
f"Azure Storage initialized for service '{service_name}' v{service_version}",
|
101
|
+
extra={
|
102
|
+
"account_url": account_url,
|
103
|
+
"blob_enabled": enable_blob_storage,
|
104
|
+
"file_enabled": enable_file_storage,
|
105
|
+
"queue_enabled": enable_queue_storage,
|
106
|
+
}
|
107
|
+
)
|
108
|
+
|
109
|
+
def _setup_clients(self):
|
110
|
+
"""Initialize Storage clients based on enabled features.
|
111
|
+
|
112
|
+
Raises:
|
113
|
+
Exception: If client initialization fails
|
114
|
+
"""
|
115
|
+
try:
|
116
|
+
if self.enable_blob_storage:
|
117
|
+
self.blob_service_client = BlobServiceClient(
|
118
|
+
account_url=self.account_url,
|
119
|
+
credential=self.credential
|
120
|
+
)
|
121
|
+
self.logger.debug("BlobServiceClient initialized successfully")
|
122
|
+
|
123
|
+
if self.enable_file_storage:
|
124
|
+
# Convert blob URL to file URL
|
125
|
+
file_url = self.account_url.replace('.blob.', '.file.')
|
126
|
+
self.file_service_client = ShareServiceClient(
|
127
|
+
account_url=file_url,
|
128
|
+
credential=self.credential,
|
129
|
+
token_intent='backup'
|
130
|
+
)
|
131
|
+
self.logger.debug("ShareServiceClient initialized successfully")
|
132
|
+
|
133
|
+
if self.enable_queue_storage:
|
134
|
+
# Convert blob URL to queue URL
|
135
|
+
queue_url = self.account_url.replace('.blob.', '.queue.')
|
136
|
+
self.queue_service_client = QueueServiceClient(
|
137
|
+
account_url=queue_url,
|
138
|
+
credential=self.credential
|
139
|
+
)
|
140
|
+
self.logger.debug("QueueServiceClient initialized successfully")
|
141
|
+
|
142
|
+
except Exception as e:
|
143
|
+
self.logger.error(
|
144
|
+
f"Failed to initialize Storage clients: {e}",
|
145
|
+
exc_info=True
|
146
|
+
)
|
147
|
+
raise
|
148
|
+
|
149
|
+
# Blob Storage Operations
|
150
|
+
def upload_blob(
|
151
|
+
self,
|
152
|
+
container_name: str,
|
153
|
+
blob_name: str,
|
154
|
+
data: Union[bytes, str, BinaryIO],
|
155
|
+
overwrite: bool = False,
|
156
|
+
metadata: Optional[Dict[str, str]] = None,
|
157
|
+
content_type: Optional[str] = None,
|
158
|
+
**kwargs
|
159
|
+
) -> bool:
|
160
|
+
"""Upload a blob to Azure Blob Storage.
|
161
|
+
|
162
|
+
Args:
|
163
|
+
container_name: Name of the container
|
164
|
+
blob_name: Name of the blob
|
165
|
+
data: Data to upload (bytes, string, or file-like object)
|
166
|
+
overwrite: Whether to overwrite existing blob
|
167
|
+
metadata: Optional metadata for the blob
|
168
|
+
content_type: Optional content type for the blob
|
169
|
+
**kwargs: Additional parameters for blob upload
|
170
|
+
|
171
|
+
Returns:
|
172
|
+
True if blob was uploaded successfully
|
173
|
+
|
174
|
+
Raises:
|
175
|
+
RuntimeError: If blob service client is not initialized
|
176
|
+
Exception: If blob upload fails
|
177
|
+
"""
|
178
|
+
with self.logger.create_span(
|
179
|
+
"AzureStorage.upload_blob",
|
180
|
+
attributes={
|
181
|
+
"service.name": self.service_name,
|
182
|
+
"operation.type": "blob_upload",
|
183
|
+
"storage.container_name": container_name,
|
184
|
+
"storage.blob_name": blob_name,
|
185
|
+
"storage.account_url": self.account_url
|
186
|
+
}
|
187
|
+
):
|
188
|
+
if self.blob_service_client is None:
|
189
|
+
error_msg = "Blob service client not initialized. Enable blob storage during initialization."
|
190
|
+
self.logger.error(error_msg)
|
191
|
+
raise RuntimeError(error_msg)
|
192
|
+
|
193
|
+
try:
|
194
|
+
self.logger.debug(
|
195
|
+
"Uploading blob to Azure Storage",
|
196
|
+
extra={
|
197
|
+
"container_name": container_name,
|
198
|
+
"blob_name": blob_name,
|
199
|
+
"overwrite": overwrite,
|
200
|
+
"has_metadata": metadata is not None,
|
201
|
+
"content_type": content_type
|
202
|
+
}
|
203
|
+
)
|
204
|
+
|
205
|
+
blob_client = self.blob_service_client.get_blob_client(
|
206
|
+
container=container_name,
|
207
|
+
blob=blob_name
|
208
|
+
)
|
209
|
+
|
210
|
+
blob_client.upload_blob(
|
211
|
+
data,
|
212
|
+
overwrite=overwrite,
|
213
|
+
metadata=metadata,
|
214
|
+
content_type=content_type,
|
215
|
+
**kwargs
|
216
|
+
)
|
217
|
+
|
218
|
+
self.logger.info(
|
219
|
+
"Blob uploaded successfully",
|
220
|
+
extra={
|
221
|
+
"container_name": container_name,
|
222
|
+
"blob_name": blob_name,
|
223
|
+
"overwrite": overwrite
|
224
|
+
}
|
225
|
+
)
|
226
|
+
|
227
|
+
return True
|
228
|
+
|
229
|
+
except Exception as e:
|
230
|
+
self.logger.error(
|
231
|
+
f"Failed to upload blob '{blob_name}': {e}",
|
232
|
+
extra={
|
233
|
+
"container_name": container_name,
|
234
|
+
"blob_name": blob_name
|
235
|
+
},
|
236
|
+
exc_info=True
|
237
|
+
)
|
238
|
+
raise
|
239
|
+
|
240
|
+
def download_blob(
|
241
|
+
self,
|
242
|
+
container_name: str,
|
243
|
+
blob_name: str,
|
244
|
+
**kwargs
|
245
|
+
) -> Optional[bytes]:
|
246
|
+
"""Download a blob from Azure Blob Storage.
|
247
|
+
|
248
|
+
Args:
|
249
|
+
container_name: Name of the container
|
250
|
+
blob_name: Name of the blob
|
251
|
+
**kwargs: Additional parameters for blob download
|
252
|
+
|
253
|
+
Returns:
|
254
|
+
Blob data as bytes if found, None if not found
|
255
|
+
|
256
|
+
Raises:
|
257
|
+
RuntimeError: If blob service client is not initialized
|
258
|
+
Exception: If blob download fails for reasons other than not found
|
259
|
+
"""
|
260
|
+
with self.logger.create_span(
|
261
|
+
"AzureStorage.download_blob",
|
262
|
+
attributes={
|
263
|
+
"service.name": self.service_name,
|
264
|
+
"operation.type": "blob_download",
|
265
|
+
"storage.container_name": container_name,
|
266
|
+
"storage.blob_name": blob_name,
|
267
|
+
"storage.account_url": self.account_url
|
268
|
+
}
|
269
|
+
):
|
270
|
+
if self.blob_service_client is None:
|
271
|
+
error_msg = "Blob service client not initialized. Enable blob storage during initialization."
|
272
|
+
self.logger.error(error_msg)
|
273
|
+
raise RuntimeError(error_msg)
|
274
|
+
|
275
|
+
try:
|
276
|
+
self.logger.debug(
|
277
|
+
"Downloading blob from Azure Storage",
|
278
|
+
extra={
|
279
|
+
"container_name": container_name,
|
280
|
+
"blob_name": blob_name
|
281
|
+
}
|
282
|
+
)
|
283
|
+
|
284
|
+
blob_client = self.blob_service_client.get_blob_client(
|
285
|
+
container=container_name,
|
286
|
+
blob=blob_name
|
287
|
+
)
|
288
|
+
|
289
|
+
blob_data = blob_client.download_blob(**kwargs)
|
290
|
+
content = blob_data.readall()
|
291
|
+
|
292
|
+
self.logger.info(
|
293
|
+
"Blob downloaded successfully",
|
294
|
+
extra={
|
295
|
+
"container_name": container_name,
|
296
|
+
"blob_name": blob_name,
|
297
|
+
"content_length": len(content) if content else 0
|
298
|
+
}
|
299
|
+
)
|
300
|
+
|
301
|
+
return content
|
302
|
+
|
303
|
+
except ResourceNotFoundError:
|
304
|
+
self.logger.warning(
|
305
|
+
f"Blob '{blob_name}' not found in container '{container_name}'",
|
306
|
+
extra={"container_name": container_name, "blob_name": blob_name}
|
307
|
+
)
|
308
|
+
return None
|
309
|
+
except Exception as e:
|
310
|
+
self.logger.error(
|
311
|
+
f"Failed to download blob '{blob_name}': {e}",
|
312
|
+
extra={
|
313
|
+
"container_name": container_name,
|
314
|
+
"blob_name": blob_name
|
315
|
+
},
|
316
|
+
exc_info=True
|
317
|
+
)
|
318
|
+
raise
|
319
|
+
|
320
|
+
def delete_blob(
|
321
|
+
self,
|
322
|
+
container_name: str,
|
323
|
+
blob_name: str,
|
324
|
+
**kwargs
|
325
|
+
) -> bool:
|
326
|
+
"""Delete a blob from Azure Blob Storage.
|
327
|
+
|
328
|
+
Args:
|
329
|
+
container_name: Name of the container
|
330
|
+
blob_name: Name of the blob to delete
|
331
|
+
**kwargs: Additional parameters for blob deletion
|
332
|
+
|
333
|
+
Returns:
|
334
|
+
True if blob was deleted successfully
|
335
|
+
|
336
|
+
Raises:
|
337
|
+
RuntimeError: If blob service client is not initialized
|
338
|
+
Exception: If blob deletion fails
|
339
|
+
"""
|
340
|
+
with self.logger.create_span(
|
341
|
+
"AzureStorage.delete_blob",
|
342
|
+
attributes={
|
343
|
+
"service.name": self.service_name,
|
344
|
+
"operation.type": "blob_deletion",
|
345
|
+
"storage.container_name": container_name,
|
346
|
+
"storage.blob_name": blob_name,
|
347
|
+
"storage.account_url": self.account_url
|
348
|
+
}
|
349
|
+
):
|
350
|
+
if self.blob_service_client is None:
|
351
|
+
error_msg = "Blob service client not initialized. Enable blob storage during initialization."
|
352
|
+
self.logger.error(error_msg)
|
353
|
+
raise RuntimeError(error_msg)
|
354
|
+
|
355
|
+
try:
|
356
|
+
self.logger.debug(
|
357
|
+
"Deleting blob from Azure Storage",
|
358
|
+
extra={
|
359
|
+
"container_name": container_name,
|
360
|
+
"blob_name": blob_name
|
361
|
+
}
|
362
|
+
)
|
363
|
+
|
364
|
+
blob_client = self.blob_service_client.get_blob_client(
|
365
|
+
container=container_name,
|
366
|
+
blob=blob_name
|
367
|
+
)
|
368
|
+
|
369
|
+
blob_client.delete_blob(**kwargs)
|
370
|
+
|
371
|
+
self.logger.info(
|
372
|
+
"Blob deleted successfully",
|
373
|
+
extra={
|
374
|
+
"container_name": container_name,
|
375
|
+
"blob_name": blob_name
|
376
|
+
}
|
377
|
+
)
|
378
|
+
|
379
|
+
return True
|
380
|
+
|
381
|
+
except ResourceNotFoundError:
|
382
|
+
self.logger.warning(
|
383
|
+
f"Blob '{blob_name}' not found in container '{container_name}' for deletion",
|
384
|
+
extra={"container_name": container_name, "blob_name": blob_name}
|
385
|
+
)
|
386
|
+
return False
|
387
|
+
except Exception as e:
|
388
|
+
self.logger.error(
|
389
|
+
f"Failed to delete blob '{blob_name}': {e}",
|
390
|
+
extra={
|
391
|
+
"container_name": container_name,
|
392
|
+
"blob_name": blob_name
|
393
|
+
},
|
394
|
+
exc_info=True
|
395
|
+
)
|
396
|
+
raise
|
397
|
+
|
398
|
+
def list_blobs(
|
399
|
+
self,
|
400
|
+
container_name: str,
|
401
|
+
name_starts_with: Optional[str] = None,
|
402
|
+
**kwargs
|
403
|
+
) -> List[str]:
|
404
|
+
"""List blobs in a container.
|
405
|
+
|
406
|
+
Args:
|
407
|
+
container_name: Name of the container
|
408
|
+
name_starts_with: Optional prefix to filter blob names
|
409
|
+
**kwargs: Additional parameters for listing blobs
|
410
|
+
|
411
|
+
Returns:
|
412
|
+
List of blob names
|
413
|
+
|
414
|
+
Raises:
|
415
|
+
RuntimeError: If blob service client is not initialized
|
416
|
+
Exception: If listing blobs fails
|
417
|
+
"""
|
418
|
+
with self.logger.create_span(
|
419
|
+
"AzureStorage.list_blobs",
|
420
|
+
attributes={
|
421
|
+
"service.name": self.service_name,
|
422
|
+
"operation.type": "blob_listing",
|
423
|
+
"storage.container_name": container_name,
|
424
|
+
"storage.account_url": self.account_url
|
425
|
+
}
|
426
|
+
):
|
427
|
+
if self.blob_service_client is None:
|
428
|
+
error_msg = "Blob service client not initialized. Enable blob storage during initialization."
|
429
|
+
self.logger.error(error_msg)
|
430
|
+
raise RuntimeError(error_msg)
|
431
|
+
|
432
|
+
try:
|
433
|
+
self.logger.debug(
|
434
|
+
"Listing blobs from Azure Storage",
|
435
|
+
extra={
|
436
|
+
"container_name": container_name,
|
437
|
+
"name_starts_with": name_starts_with
|
438
|
+
}
|
439
|
+
)
|
440
|
+
|
441
|
+
container_client = self.blob_service_client.get_container_client(container_name)
|
442
|
+
|
443
|
+
blob_names = []
|
444
|
+
for blob in container_client.list_blobs(
|
445
|
+
name_starts_with=name_starts_with,
|
446
|
+
**kwargs
|
447
|
+
):
|
448
|
+
blob_names.append(blob.name)
|
449
|
+
|
450
|
+
self.logger.info(
|
451
|
+
"Blobs listed successfully",
|
452
|
+
extra={
|
453
|
+
"container_name": container_name,
|
454
|
+
"blob_count": len(blob_names)
|
455
|
+
}
|
456
|
+
)
|
457
|
+
|
458
|
+
return blob_names
|
459
|
+
|
460
|
+
except Exception as e:
|
461
|
+
self.logger.error(
|
462
|
+
f"Failed to list blobs in container '{container_name}': {e}",
|
463
|
+
extra={"container_name": container_name},
|
464
|
+
exc_info=True
|
465
|
+
)
|
466
|
+
raise
|
467
|
+
|
468
|
+
|
469
|
+
|
470
|
+
# Queue Operations
|
471
|
+
def send_message(
|
472
|
+
self,
|
473
|
+
queue_name: str,
|
474
|
+
content: str,
|
475
|
+
visibility_timeout: Optional[int] = None,
|
476
|
+
time_to_live: Optional[int] = None,
|
477
|
+
**kwargs
|
478
|
+
) -> bool:
|
479
|
+
"""Send a message to an Azure Storage Queue.
|
480
|
+
|
481
|
+
Args:
|
482
|
+
queue_name: Name of the queue
|
483
|
+
content: Message content
|
484
|
+
visibility_timeout: Optional visibility timeout in seconds
|
485
|
+
time_to_live: Optional time to live in seconds
|
486
|
+
**kwargs: Additional parameters for message sending
|
487
|
+
|
488
|
+
Returns:
|
489
|
+
True if message was sent successfully
|
490
|
+
|
491
|
+
Raises:
|
492
|
+
RuntimeError: If queue service client is not initialized
|
493
|
+
Exception: If message sending fails
|
494
|
+
"""
|
495
|
+
with self.logger.create_span(
|
496
|
+
"AzureStorage.send_message",
|
497
|
+
attributes={
|
498
|
+
"service.name": self.service_name,
|
499
|
+
"operation.type": "queue_send_message",
|
500
|
+
"storage.queue_name": queue_name,
|
501
|
+
"storage.account_url": self.account_url
|
502
|
+
}
|
503
|
+
):
|
504
|
+
if self.queue_service_client is None:
|
505
|
+
error_msg = "Queue service client not initialized. Enable queue storage during initialization."
|
506
|
+
self.logger.error(error_msg)
|
507
|
+
raise RuntimeError(error_msg)
|
508
|
+
|
509
|
+
try:
|
510
|
+
self.logger.debug(
|
511
|
+
"Sending message to Azure Storage Queue",
|
512
|
+
extra={
|
513
|
+
"queue_name": queue_name,
|
514
|
+
"message_length": len(content),
|
515
|
+
"visibility_timeout": visibility_timeout,
|
516
|
+
"time_to_live": time_to_live
|
517
|
+
}
|
518
|
+
)
|
519
|
+
|
520
|
+
queue_client = self.queue_service_client.get_queue_client(queue_name)
|
521
|
+
|
522
|
+
queue_client.send_message(
|
523
|
+
content=content,
|
524
|
+
visibility_timeout=visibility_timeout,
|
525
|
+
time_to_live=time_to_live,
|
526
|
+
**kwargs
|
527
|
+
)
|
528
|
+
|
529
|
+
self.logger.info(
|
530
|
+
"Message sent successfully",
|
531
|
+
extra={"queue_name": queue_name}
|
532
|
+
)
|
533
|
+
|
534
|
+
return True
|
535
|
+
|
536
|
+
except Exception as e:
|
537
|
+
self.logger.error(
|
538
|
+
f"Failed to send message to queue '{queue_name}': {e}",
|
539
|
+
extra={"queue_name": queue_name},
|
540
|
+
exc_info=True
|
541
|
+
)
|
542
|
+
raise
|
543
|
+
|
544
|
+
def receive_messages(
|
545
|
+
self,
|
546
|
+
queue_name: str,
|
547
|
+
messages_per_page: int = 1,
|
548
|
+
visibility_timeout: Optional[int] = None,
|
549
|
+
**kwargs
|
550
|
+
) -> List[Dict[str, Any]]:
|
551
|
+
"""Receive messages from an Azure Storage Queue.
|
552
|
+
|
553
|
+
Args:
|
554
|
+
queue_name: Name of the queue
|
555
|
+
messages_per_page: Number of messages to receive per page
|
556
|
+
visibility_timeout: Optional visibility timeout in seconds
|
557
|
+
**kwargs: Additional parameters for message receiving
|
558
|
+
|
559
|
+
Returns:
|
560
|
+
List of message dictionaries containing message data
|
561
|
+
|
562
|
+
Raises:
|
563
|
+
RuntimeError: If queue service client is not initialized
|
564
|
+
Exception: If message receiving fails
|
565
|
+
"""
|
566
|
+
with self.logger.create_span(
|
567
|
+
"AzureStorage.receive_messages",
|
568
|
+
attributes={
|
569
|
+
"service.name": self.service_name,
|
570
|
+
"operation.type": "queue_receive_messages",
|
571
|
+
"storage.queue_name": queue_name,
|
572
|
+
"storage.account_url": self.account_url
|
573
|
+
}
|
574
|
+
):
|
575
|
+
if self.queue_service_client is None:
|
576
|
+
error_msg = "Queue service client not initialized. Enable queue storage during initialization."
|
577
|
+
self.logger.error(error_msg)
|
578
|
+
raise RuntimeError(error_msg)
|
579
|
+
|
580
|
+
try:
|
581
|
+
self.logger.debug(
|
582
|
+
"Receiving messages from Azure Storage Queue",
|
583
|
+
extra={
|
584
|
+
"queue_name": queue_name,
|
585
|
+
"messages_per_page": messages_per_page,
|
586
|
+
"visibility_timeout": visibility_timeout
|
587
|
+
}
|
588
|
+
)
|
589
|
+
|
590
|
+
queue_client = self.queue_service_client.get_queue_client(queue_name)
|
591
|
+
|
592
|
+
messages = []
|
593
|
+
for message_page in queue_client.receive_messages(
|
594
|
+
messages_per_page=messages_per_page,
|
595
|
+
visibility_timeout=visibility_timeout,
|
596
|
+
**kwargs
|
597
|
+
):
|
598
|
+
for message in message_page:
|
599
|
+
messages.append({
|
600
|
+
"id": message.id,
|
601
|
+
"content": message.content,
|
602
|
+
"dequeue_count": message.dequeue_count,
|
603
|
+
"insertion_time": message.insertion_time,
|
604
|
+
"expiration_time": message.expiration_time,
|
605
|
+
"pop_receipt": message.pop_receipt
|
606
|
+
})
|
607
|
+
|
608
|
+
self.logger.info(
|
609
|
+
"Messages received successfully",
|
610
|
+
extra={
|
611
|
+
"queue_name": queue_name,
|
612
|
+
"message_count": len(messages)
|
613
|
+
}
|
614
|
+
)
|
615
|
+
|
616
|
+
return messages
|
617
|
+
|
618
|
+
except Exception as e:
|
619
|
+
self.logger.error(
|
620
|
+
f"Failed to receive messages from queue '{queue_name}': {e}",
|
621
|
+
extra={"queue_name": queue_name},
|
622
|
+
exc_info=True
|
623
|
+
)
|
624
|
+
raise
|
625
|
+
|
626
|
+
def delete_message(
|
627
|
+
self,
|
628
|
+
queue_name: str,
|
629
|
+
message_id: str,
|
630
|
+
pop_receipt: str,
|
631
|
+
**kwargs
|
632
|
+
) -> bool:
|
633
|
+
"""Delete a message from an Azure Storage Queue.
|
634
|
+
|
635
|
+
Args:
|
636
|
+
queue_name: Name of the queue
|
637
|
+
message_id: ID of the message to delete
|
638
|
+
pop_receipt: Pop receipt of the message
|
639
|
+
**kwargs: Additional parameters for message deletion
|
640
|
+
|
641
|
+
Returns:
|
642
|
+
True if message was deleted successfully
|
643
|
+
|
644
|
+
Raises:
|
645
|
+
RuntimeError: If queue service client is not initialized
|
646
|
+
Exception: If message deletion fails
|
647
|
+
"""
|
648
|
+
with self.logger.create_span(
|
649
|
+
"AzureStorage.delete_message",
|
650
|
+
attributes={
|
651
|
+
"service.name": self.service_name,
|
652
|
+
"operation.type": "queue_delete_message",
|
653
|
+
"storage.queue_name": queue_name,
|
654
|
+
"storage.message_id": message_id,
|
655
|
+
"storage.account_url": self.account_url
|
656
|
+
}
|
657
|
+
):
|
658
|
+
if self.queue_service_client is None:
|
659
|
+
error_msg = "Queue service client not initialized. Enable queue storage during initialization."
|
660
|
+
self.logger.error(error_msg)
|
661
|
+
raise RuntimeError(error_msg)
|
662
|
+
|
663
|
+
try:
|
664
|
+
self.logger.debug(
|
665
|
+
"Deleting message from Azure Storage Queue",
|
666
|
+
extra={
|
667
|
+
"queue_name": queue_name,
|
668
|
+
"message_id": message_id
|
669
|
+
}
|
670
|
+
)
|
671
|
+
|
672
|
+
queue_client = self.queue_service_client.get_queue_client(queue_name)
|
673
|
+
|
674
|
+
queue_client.delete_message(
|
675
|
+
message=message_id,
|
676
|
+
pop_receipt=pop_receipt,
|
677
|
+
**kwargs
|
678
|
+
)
|
679
|
+
|
680
|
+
self.logger.info(
|
681
|
+
"Message deleted successfully",
|
682
|
+
extra={
|
683
|
+
"queue_name": queue_name,
|
684
|
+
"message_id": message_id
|
685
|
+
}
|
686
|
+
)
|
687
|
+
|
688
|
+
return True
|
689
|
+
|
690
|
+
except Exception as e:
|
691
|
+
self.logger.error(
|
692
|
+
f"Failed to delete message '{message_id}' from queue '{queue_name}': {e}",
|
693
|
+
extra={
|
694
|
+
"queue_name": queue_name,
|
695
|
+
"message_id": message_id
|
696
|
+
},
|
697
|
+
exc_info=True
|
698
|
+
)
|
699
|
+
raise
|
700
|
+
|
701
|
+
|
702
|
+
|
703
|
+
def test_connection(self) -> bool:
|
704
|
+
"""Test connection to Azure Storage by attempting to list containers.
|
705
|
+
|
706
|
+
Returns:
|
707
|
+
True if connection is successful, False otherwise
|
708
|
+
"""
|
709
|
+
with self.logger.create_span(
|
710
|
+
"AzureStorage.test_connection",
|
711
|
+
attributes={
|
712
|
+
"service.name": self.service_name,
|
713
|
+
"operation.type": "connection_test",
|
714
|
+
"storage.account_url": self.account_url
|
715
|
+
}
|
716
|
+
):
|
717
|
+
try:
|
718
|
+
self.logger.debug(
|
719
|
+
"Testing Azure Storage connection",
|
720
|
+
extra={"account_url": self.account_url}
|
721
|
+
)
|
722
|
+
|
723
|
+
if self.blob_service_client is not None:
|
724
|
+
# Try to list containers (limited to 1) to test connection
|
725
|
+
list(self.blob_service_client.list_containers(results_per_page=1))
|
726
|
+
elif self.queue_service_client is not None:
|
727
|
+
# Try to list queues if blob storage is disabled
|
728
|
+
list(self.queue_service_client.list_queues(results_per_page=1))
|
729
|
+
elif self.file_service_client is not None:
|
730
|
+
# Try to list shares if queues are disabled
|
731
|
+
list(self.file_service_client.list_shares(results_per_page=1))
|
732
|
+
else:
|
733
|
+
self.logger.error("No clients available for connection testing")
|
734
|
+
return False
|
735
|
+
|
736
|
+
self.logger.info("Azure Storage connection test successful")
|
737
|
+
return True
|
738
|
+
|
739
|
+
except Exception as e:
|
740
|
+
self.logger.warning(
|
741
|
+
f"Azure Storage connection test failed: {e}",
|
742
|
+
extra={"account_url": self.account_url}
|
743
|
+
)
|
744
|
+
return False
|
745
|
+
|
746
|
+
def set_correlation_id(self, correlation_id: str):
|
747
|
+
"""Set correlation ID for request/transaction tracking.
|
748
|
+
|
749
|
+
Args:
|
750
|
+
correlation_id: Unique identifier for transaction correlation
|
751
|
+
"""
|
752
|
+
self.logger.set_correlation_id(correlation_id)
|
753
|
+
|
754
|
+
def get_correlation_id(self) -> Optional[str]:
|
755
|
+
"""Get current correlation ID.
|
756
|
+
|
757
|
+
Returns:
|
758
|
+
Current correlation ID if set, otherwise None
|
759
|
+
"""
|
760
|
+
return self.logger.get_correlation_id()
|
761
|
+
|
762
|
+
|
763
|
+
def create_azure_storage(
|
764
|
+
account_url: str,
|
765
|
+
credential: Optional[TokenCredential] = None,
|
766
|
+
azure_identity: Optional[AzureIdentity] = None,
|
767
|
+
service_name: str = "azure_storage",
|
768
|
+
service_version: str = "1.0.0",
|
769
|
+
logger: Optional[AzureLogger] = None,
|
770
|
+
connection_string: Optional[str] = None,
|
771
|
+
enable_blob_storage: bool = True,
|
772
|
+
enable_file_storage: bool = True,
|
773
|
+
enable_queue_storage: bool = True,
|
774
|
+
) -> AzureStorage:
|
775
|
+
"""Factory function to create AzureStorage instance.
|
776
|
+
|
777
|
+
Provides a convenient way to create an AzureStorage instance with
|
778
|
+
common configuration patterns. If no credential or azure_identity
|
779
|
+
is provided, creates a default AzureIdentity instance.
|
780
|
+
|
781
|
+
Args:
|
782
|
+
account_url: Azure Storage Account URL
|
783
|
+
credential: Azure credential for authentication
|
784
|
+
azure_identity: AzureIdentity instance for credential management
|
785
|
+
service_name: Service name for tracing context
|
786
|
+
service_version: Service version for metadata
|
787
|
+
logger: Optional AzureLogger instance
|
788
|
+
connection_string: Application Insights connection string
|
789
|
+
enable_blob_storage: Enable blob storage operations client
|
790
|
+
enable_file_storage: Enable file storage operations client
|
791
|
+
enable_queue_storage: Enable queue storage operations client
|
792
|
+
|
793
|
+
Returns:
|
794
|
+
Configured AzureStorage instance
|
795
|
+
|
796
|
+
Example:
|
797
|
+
# Basic usage with default credential
|
798
|
+
storage = create_azure_storage("https://account.blob.core.windows.net/")
|
799
|
+
|
800
|
+
# With custom service name and specific features
|
801
|
+
storage = create_azure_storage(
|
802
|
+
"https://account.blob.core.windows.net/",
|
803
|
+
service_name="my_app",
|
804
|
+
enable_file_storage=False,
|
805
|
+
enable_queue_storage=False
|
806
|
+
)
|
807
|
+
|
808
|
+
# Note: Containers and queues should be created via infrastructure as code
|
809
|
+
"""
|
810
|
+
if credential is None and azure_identity is None:
|
811
|
+
# Create default AzureIdentity instance
|
812
|
+
from ..mgmt.identity import create_azure_identity
|
813
|
+
azure_identity = create_azure_identity(
|
814
|
+
service_name=f"{service_name}_identity",
|
815
|
+
service_version=service_version,
|
816
|
+
connection_string=connection_string,
|
817
|
+
)
|
818
|
+
|
819
|
+
return AzureStorage(
|
820
|
+
account_url=account_url,
|
821
|
+
credential=credential,
|
822
|
+
azure_identity=azure_identity,
|
823
|
+
service_name=service_name,
|
824
|
+
service_version=service_version,
|
825
|
+
logger=logger,
|
826
|
+
connection_string=connection_string,
|
827
|
+
enable_blob_storage=enable_blob_storage,
|
828
|
+
enable_file_storage=enable_file_storage,
|
829
|
+
enable_queue_storage=enable_queue_storage,
|
830
|
+
)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: azpaddypy
|
3
|
+
Version: 0.5.11
|
4
|
+
Summary: Comprehensive Python logger for Azure, integrating OpenTelemetry for advanced, structured, and distributed tracing.
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
6
|
+
Classifier: Operating System :: OS Independent
|
7
|
+
Requires-Python: >=3.11
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
License-File: LICENSE
|
10
|
+
Requires-Dist: azure-monitor-opentelemetry<2.0.0,>=1.6.4
|
11
|
+
Requires-Dist: azure-functions<2.0.0,>=1.21.3
|
12
|
+
Requires-Dist: azure-identity<2.0.0,>=1.20.0
|
13
|
+
Requires-Dist: azure-keyvault-secrets<5.0.0,>=4.9.0
|
14
|
+
Requires-Dist: azure-keyvault-keys<5.0.0,>=4.9.0
|
15
|
+
Requires-Dist: azure-keyvault-certificates<5.0.0,>=4.9.0
|
16
|
+
Requires-Dist: azure-storage-blob<13.0.0,>=12.20.0
|
17
|
+
Requires-Dist: azure-storage-file-share<13.0.0,>=12.17.0
|
18
|
+
Requires-Dist: azure-storage-queue<13.0.0,>=12.12.0
|
19
|
+
Dynamic: license-file
|
@@ -0,0 +1,13 @@
|
|
1
|
+
azpaddypy/__init__.py,sha256=hrWNAh4OHZOvm3Pbhq5eUjO-pSRYn0h0W0J87tc-lNI,45
|
2
|
+
azpaddypy/mgmt/__init__.py,sha256=waW9EAnTFDh2530ieQX1Z0r0Z-ZKHRwabVDfapjfN58,441
|
3
|
+
azpaddypy/mgmt/identity.py,sha256=mA_krQslMsK_sDob-z-QA0B9khK_JUO2way7xwPopR8,12001
|
4
|
+
azpaddypy/mgmt/local_env_manager.py,sha256=WHXJXHQFGwkgr96YkEtqpgFUxmCjBa5pArbNxZUSKQk,7762
|
5
|
+
azpaddypy/mgmt/logging.py,sha256=iuoYxwliBFmHKEZh30FYlGBFwlhic_yFmOi18gFjTEE,36962
|
6
|
+
azpaddypy/resources/__init__.py,sha256=KKqntqNJZW39aic_V6CeZmJ5VCZO-GDIn7HCWfOsJyk,422
|
7
|
+
azpaddypy/resources/keyvault.py,sha256=4J08vLqoLFd1_UUDBji2oG2fatZaPkgnRyT_Z6wHAOc,20312
|
8
|
+
azpaddypy/resources/storage.py,sha256=MnajBW8xtiOQ0YLwgcNLUml-ZiG2_od0ICT9m9Yin6c,31601
|
9
|
+
azpaddypy-0.5.11.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
|
10
|
+
azpaddypy-0.5.11.dist-info/METADATA,sha256=RefqG2ilkoIXTkUl2weT7BQhvfHawknmde3xoWyskA4,867
|
11
|
+
azpaddypy-0.5.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
12
|
+
azpaddypy-0.5.11.dist-info/top_level.txt,sha256=hsDuboDhT61320ML8X479ezSTwT3rrlDWz1_Z45B2cs,10
|
13
|
+
azpaddypy-0.5.11.dist-info/RECORD,,
|
@@ -1,16 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: azpaddypy
|
3
|
-
Version: 0.5.9
|
4
|
-
Summary: Comprehensive Python logger for Azure, integrating OpenTelemetry for advanced, structured, and distributed tracing.
|
5
|
-
Classifier: Programming Language :: Python :: 3
|
6
|
-
Classifier: Operating System :: OS Independent
|
7
|
-
Requires-Python: >=3.11
|
8
|
-
Description-Content-Type: text/markdown
|
9
|
-
License-File: LICENSE
|
10
|
-
Requires-Dist: azure-monitor-opentelemetry==1.6.10
|
11
|
-
Requires-Dist: azure-functions==1.23.0
|
12
|
-
Requires-Dist: azure-identity==1.23.0
|
13
|
-
Requires-Dist: azure-keyvault-secrets==4.10.0
|
14
|
-
Requires-Dist: azure-keyvault-keys==4.10.0
|
15
|
-
Requires-Dist: azure-keyvault-certificates==4.10.0
|
16
|
-
Dynamic: license-file
|
azpaddypy-0.5.9.dist-info/RECORD
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
azpaddypy/__init__.py,sha256=hrWNAh4OHZOvm3Pbhq5eUjO-pSRYn0h0W0J87tc-lNI,45
|
2
|
-
azpaddypy/mgmt/__init__.py,sha256=waW9EAnTFDh2530ieQX1Z0r0Z-ZKHRwabVDfapjfN58,441
|
3
|
-
azpaddypy/mgmt/identity.py,sha256=mA_krQslMsK_sDob-z-QA0B9khK_JUO2way7xwPopR8,12001
|
4
|
-
azpaddypy/mgmt/local_env_manager.py,sha256=WHXJXHQFGwkgr96YkEtqpgFUxmCjBa5pArbNxZUSKQk,7762
|
5
|
-
azpaddypy/mgmt/logging.py,sha256=iuoYxwliBFmHKEZh30FYlGBFwlhic_yFmOi18gFjTEE,36962
|
6
|
-
azpaddypy/resources/__init__.py,sha256=Bvt3VK4RqwoxYpoh6EbLXIR18RuFPKaLP6zLL-icyFk,314
|
7
|
-
azpaddypy/resources/keyvault.py,sha256=4J08vLqoLFd1_UUDBji2oG2fatZaPkgnRyT_Z6wHAOc,20312
|
8
|
-
azpaddypy-0.5.9.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
|
9
|
-
azpaddypy-0.5.9.dist-info/METADATA,sha256=5PkaGLqbgBlFbaVjAt6hokzh1n6FtyGjnXhF5bsfx-I,665
|
10
|
-
azpaddypy-0.5.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
-
azpaddypy-0.5.9.dist-info/top_level.txt,sha256=hsDuboDhT61320ML8X479ezSTwT3rrlDWz1_Z45B2cs,10
|
12
|
-
azpaddypy-0.5.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|