azpaddypy 0.3.4__py3-none-any.whl → 0.3.6__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/__init__.py +2 -0
- azpaddypy/resources/__init__.py +12 -0
- azpaddypy/resources/keyvault.py +530 -0
- {azpaddypy-0.3.4.dist-info → azpaddypy-0.3.6.dist-info}/METADATA +5 -6
- azpaddypy-0.3.6.dist-info/RECORD +11 -0
- azpaddypy/test_function/__init__.py +0 -112
- azpaddypy/test_function/function_app.py +0 -129
- azpaddypy-0.3.4.dist-info/RECORD +0 -10
- {azpaddypy-0.3.4.dist-info → azpaddypy-0.3.6.dist-info}/WHEEL +0 -0
- {azpaddypy-0.3.4.dist-info → azpaddypy-0.3.6.dist-info}/licenses/LICENSE +0 -0
- {azpaddypy-0.3.4.dist-info → azpaddypy-0.3.6.dist-info}/top_level.txt +0 -0
azpaddypy/__init__.py
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
"""Azure resources package for azpaddypy.
|
2
|
+
|
3
|
+
This package contains modules for interacting with various Azure resources
|
4
|
+
including Key Vault, Storage, and other Azure services.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .keyvault import AzureKeyVault, create_azure_keyvault
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"AzureKeyVault",
|
11
|
+
"create_azure_keyvault",
|
12
|
+
]
|
@@ -0,0 +1,530 @@
|
|
1
|
+
from typing import Optional, Dict, Any, Union, List
|
2
|
+
from azure.keyvault.secrets import SecretClient
|
3
|
+
from azure.keyvault.keys import KeyClient
|
4
|
+
from azure.keyvault.certificates import CertificateClient
|
5
|
+
from azure.core.exceptions import ResourceNotFoundError, ClientAuthenticationError
|
6
|
+
from azure.core.credentials import TokenCredential
|
7
|
+
from ..mgmt.logging import AzureLogger
|
8
|
+
from ..mgmt.identity import AzureIdentity
|
9
|
+
|
10
|
+
|
11
|
+
class AzureKeyVault:
|
12
|
+
"""Azure Key Vault management with comprehensive secret, key, and certificate operations.
|
13
|
+
|
14
|
+
Provides standardized Azure Key Vault operations using Azure SDK clients
|
15
|
+
with integrated logging, error handling, and OpenTelemetry tracing support.
|
16
|
+
Supports operations for secrets, keys, and certificates with proper
|
17
|
+
authentication and authorization handling.
|
18
|
+
|
19
|
+
Attributes:
|
20
|
+
vault_url: Azure Key Vault URL
|
21
|
+
service_name: Service identifier for logging and tracing
|
22
|
+
service_version: Service version for context
|
23
|
+
logger: AzureLogger instance for structured logging
|
24
|
+
credential: Azure credential for authentication
|
25
|
+
secret_client: Azure Key Vault SecretClient instance
|
26
|
+
key_client: Azure Key Vault KeyClient instance
|
27
|
+
certificate_client: Azure Key Vault CertificateClient instance
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
vault_url: str,
|
33
|
+
credential: Optional[TokenCredential] = None,
|
34
|
+
azure_identity: Optional[AzureIdentity] = None,
|
35
|
+
service_name: str = "azure_keyvault",
|
36
|
+
service_version: str = "1.0.0",
|
37
|
+
logger: Optional[AzureLogger] = None,
|
38
|
+
connection_string: Optional[str] = None,
|
39
|
+
enable_secrets: bool = True,
|
40
|
+
enable_keys: bool = True,
|
41
|
+
enable_certificates: bool = True,
|
42
|
+
):
|
43
|
+
"""Initialize Azure Key Vault with comprehensive configuration.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
vault_url: Azure Key Vault URL (e.g., https://vault.vault.azure.net/)
|
47
|
+
credential: Azure credential for authentication
|
48
|
+
azure_identity: AzureIdentity instance for credential management
|
49
|
+
service_name: Service name for tracing context
|
50
|
+
service_version: Service version for metadata
|
51
|
+
logger: Optional AzureLogger instance
|
52
|
+
connection_string: Application Insights connection string
|
53
|
+
enable_secrets: Enable secret operations client
|
54
|
+
enable_keys: Enable key operations client
|
55
|
+
enable_certificates: Enable certificate operations client
|
56
|
+
|
57
|
+
Raises:
|
58
|
+
ValueError: If neither credential nor azure_identity is provided
|
59
|
+
Exception: If client initialization fails
|
60
|
+
"""
|
61
|
+
self.vault_url = vault_url
|
62
|
+
self.service_name = service_name
|
63
|
+
self.service_version = service_version
|
64
|
+
self.enable_secrets = enable_secrets
|
65
|
+
self.enable_keys = enable_keys
|
66
|
+
self.enable_certificates = enable_certificates
|
67
|
+
|
68
|
+
# Initialize logger - use provided instance or create new one
|
69
|
+
if logger is not None:
|
70
|
+
self.logger = logger
|
71
|
+
else:
|
72
|
+
self.logger = AzureLogger(
|
73
|
+
service_name=service_name,
|
74
|
+
service_version=service_version,
|
75
|
+
connection_string=connection_string,
|
76
|
+
enable_console_logging=True,
|
77
|
+
)
|
78
|
+
|
79
|
+
# Setup credential
|
80
|
+
if azure_identity is not None:
|
81
|
+
self.credential = azure_identity.get_credential()
|
82
|
+
self.azure_identity = azure_identity
|
83
|
+
elif credential is not None:
|
84
|
+
self.credential = credential
|
85
|
+
self.azure_identity = None
|
86
|
+
else:
|
87
|
+
raise ValueError("Either 'credential' or 'azure_identity' must be provided")
|
88
|
+
|
89
|
+
# Initialize clients
|
90
|
+
self.secret_client = None
|
91
|
+
self.key_client = None
|
92
|
+
self.certificate_client = None
|
93
|
+
|
94
|
+
self._setup_clients()
|
95
|
+
|
96
|
+
self.logger.info(
|
97
|
+
f"Azure Key Vault initialized for service '{service_name}' v{service_version}",
|
98
|
+
extra={
|
99
|
+
"vault_url": vault_url,
|
100
|
+
"secrets_enabled": enable_secrets,
|
101
|
+
"keys_enabled": enable_keys,
|
102
|
+
"certificates_enabled": enable_certificates,
|
103
|
+
}
|
104
|
+
)
|
105
|
+
|
106
|
+
def _setup_clients(self):
|
107
|
+
"""Initialize Key Vault clients based on enabled features.
|
108
|
+
|
109
|
+
Raises:
|
110
|
+
Exception: If client initialization fails
|
111
|
+
"""
|
112
|
+
try:
|
113
|
+
if self.enable_secrets:
|
114
|
+
self.secret_client = SecretClient(
|
115
|
+
vault_url=self.vault_url,
|
116
|
+
credential=self.credential
|
117
|
+
)
|
118
|
+
self.logger.debug("SecretClient initialized successfully")
|
119
|
+
|
120
|
+
if self.enable_keys:
|
121
|
+
self.key_client = KeyClient(
|
122
|
+
vault_url=self.vault_url,
|
123
|
+
credential=self.credential
|
124
|
+
)
|
125
|
+
self.logger.debug("KeyClient initialized successfully")
|
126
|
+
|
127
|
+
if self.enable_certificates:
|
128
|
+
self.certificate_client = CertificateClient(
|
129
|
+
vault_url=self.vault_url,
|
130
|
+
credential=self.credential
|
131
|
+
)
|
132
|
+
self.logger.debug("CertificateClient initialized successfully")
|
133
|
+
|
134
|
+
except Exception as e:
|
135
|
+
self.logger.error(
|
136
|
+
f"Failed to initialize Key Vault clients: {e}",
|
137
|
+
exc_info=True
|
138
|
+
)
|
139
|
+
raise
|
140
|
+
|
141
|
+
# Secret Operations
|
142
|
+
def get_secret(
|
143
|
+
self,
|
144
|
+
secret_name: str,
|
145
|
+
version: Optional[str] = None,
|
146
|
+
**kwargs
|
147
|
+
) -> Optional[str]:
|
148
|
+
"""Retrieve a secret from Azure Key Vault.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
secret_name: Name of the secret
|
152
|
+
version: Optional specific version of the secret
|
153
|
+
**kwargs: Additional parameters for the secret retrieval
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
Secret value if found, None if not found
|
157
|
+
|
158
|
+
Raises:
|
159
|
+
RuntimeError: If secret client is not initialized
|
160
|
+
Exception: If secret retrieval fails for reasons other than not found
|
161
|
+
"""
|
162
|
+
with self.logger.create_span(
|
163
|
+
"AzureKeyVault.get_secret",
|
164
|
+
attributes={
|
165
|
+
"service.name": self.service_name,
|
166
|
+
"operation.type": "secret_retrieval",
|
167
|
+
"keyvault.secret_name": secret_name,
|
168
|
+
"keyvault.version": version or "latest",
|
169
|
+
"keyvault.vault_url": self.vault_url
|
170
|
+
}
|
171
|
+
):
|
172
|
+
if self.secret_client is None:
|
173
|
+
error_msg = "Secret client not initialized. Enable secrets during initialization."
|
174
|
+
self.logger.error(error_msg)
|
175
|
+
raise RuntimeError(error_msg)
|
176
|
+
|
177
|
+
try:
|
178
|
+
self.logger.debug(
|
179
|
+
"Retrieving secret from Key Vault",
|
180
|
+
extra={
|
181
|
+
"secret_name": secret_name,
|
182
|
+
"version": version,
|
183
|
+
"vault_url": self.vault_url
|
184
|
+
}
|
185
|
+
)
|
186
|
+
|
187
|
+
secret = self.secret_client.get_secret(secret_name, version=version, **kwargs)
|
188
|
+
|
189
|
+
self.logger.info(
|
190
|
+
"Secret retrieved successfully",
|
191
|
+
extra={
|
192
|
+
"secret_name": secret_name,
|
193
|
+
"version": secret.properties.version if secret.properties else None,
|
194
|
+
"content_type": secret.properties.content_type if secret.properties else None
|
195
|
+
}
|
196
|
+
)
|
197
|
+
|
198
|
+
return secret.value
|
199
|
+
|
200
|
+
except ResourceNotFoundError:
|
201
|
+
self.logger.warning(
|
202
|
+
f"Secret '{secret_name}' not found in Key Vault",
|
203
|
+
extra={"secret_name": secret_name, "version": version}
|
204
|
+
)
|
205
|
+
return None
|
206
|
+
except ClientAuthenticationError as e:
|
207
|
+
self.logger.error(
|
208
|
+
f"Authentication failed for secret '{secret_name}': {e}",
|
209
|
+
extra={"secret_name": secret_name},
|
210
|
+
exc_info=True
|
211
|
+
)
|
212
|
+
raise
|
213
|
+
except Exception as e:
|
214
|
+
self.logger.error(
|
215
|
+
f"Failed to retrieve secret '{secret_name}': {e}",
|
216
|
+
extra={"secret_name": secret_name, "version": version},
|
217
|
+
exc_info=True
|
218
|
+
)
|
219
|
+
raise
|
220
|
+
|
221
|
+
def set_secret(
|
222
|
+
self,
|
223
|
+
secret_name: str,
|
224
|
+
secret_value: str,
|
225
|
+
content_type: Optional[str] = None,
|
226
|
+
tags: Optional[Dict[str, str]] = None,
|
227
|
+
**kwargs
|
228
|
+
) -> bool:
|
229
|
+
"""Set a secret in Azure Key Vault.
|
230
|
+
|
231
|
+
Args:
|
232
|
+
secret_name: Name of the secret
|
233
|
+
secret_value: Value of the secret
|
234
|
+
content_type: Optional content type for the secret
|
235
|
+
tags: Optional tags for the secret
|
236
|
+
**kwargs: Additional parameters for secret creation
|
237
|
+
|
238
|
+
Returns:
|
239
|
+
True if secret was set successfully
|
240
|
+
|
241
|
+
Raises:
|
242
|
+
RuntimeError: If secret client is not initialized
|
243
|
+
Exception: If secret creation fails
|
244
|
+
"""
|
245
|
+
with self.logger.create_span(
|
246
|
+
"AzureKeyVault.set_secret",
|
247
|
+
attributes={
|
248
|
+
"service.name": self.service_name,
|
249
|
+
"operation.type": "secret_creation",
|
250
|
+
"keyvault.secret_name": secret_name,
|
251
|
+
"keyvault.content_type": content_type or "text/plain",
|
252
|
+
"keyvault.vault_url": self.vault_url
|
253
|
+
}
|
254
|
+
):
|
255
|
+
if self.secret_client is None:
|
256
|
+
error_msg = "Secret client not initialized. Enable secrets during initialization."
|
257
|
+
self.logger.error(error_msg)
|
258
|
+
raise RuntimeError(error_msg)
|
259
|
+
|
260
|
+
try:
|
261
|
+
self.logger.debug(
|
262
|
+
"Setting secret in Key Vault",
|
263
|
+
extra={
|
264
|
+
"secret_name": secret_name,
|
265
|
+
"content_type": content_type,
|
266
|
+
"has_tags": tags is not None,
|
267
|
+
"vault_url": self.vault_url
|
268
|
+
}
|
269
|
+
)
|
270
|
+
|
271
|
+
secret = self.secret_client.set_secret(
|
272
|
+
secret_name,
|
273
|
+
secret_value,
|
274
|
+
content_type=content_type,
|
275
|
+
tags=tags,
|
276
|
+
**kwargs
|
277
|
+
)
|
278
|
+
|
279
|
+
self.logger.info(
|
280
|
+
"Secret set successfully",
|
281
|
+
extra={
|
282
|
+
"secret_name": secret_name,
|
283
|
+
"version": secret.properties.version if secret.properties else None,
|
284
|
+
"content_type": secret.properties.content_type if secret.properties else None
|
285
|
+
}
|
286
|
+
)
|
287
|
+
|
288
|
+
return True
|
289
|
+
|
290
|
+
except Exception as e:
|
291
|
+
self.logger.error(
|
292
|
+
f"Failed to set secret '{secret_name}': {e}",
|
293
|
+
extra={"secret_name": secret_name, "content_type": content_type},
|
294
|
+
exc_info=True
|
295
|
+
)
|
296
|
+
raise
|
297
|
+
|
298
|
+
def delete_secret(self, secret_name: str, **kwargs) -> bool:
|
299
|
+
"""Delete a secret from Azure Key Vault.
|
300
|
+
|
301
|
+
Args:
|
302
|
+
secret_name: Name of the secret to delete
|
303
|
+
**kwargs: Additional parameters for secret deletion
|
304
|
+
|
305
|
+
Returns:
|
306
|
+
True if secret was deleted successfully
|
307
|
+
|
308
|
+
Raises:
|
309
|
+
RuntimeError: If secret client is not initialized
|
310
|
+
Exception: If secret deletion fails
|
311
|
+
"""
|
312
|
+
with self.logger.create_span(
|
313
|
+
"AzureKeyVault.delete_secret",
|
314
|
+
attributes={
|
315
|
+
"service.name": self.service_name,
|
316
|
+
"operation.type": "secret_deletion",
|
317
|
+
"keyvault.secret_name": secret_name,
|
318
|
+
"keyvault.vault_url": self.vault_url
|
319
|
+
}
|
320
|
+
):
|
321
|
+
if self.secret_client is None:
|
322
|
+
error_msg = "Secret client not initialized. Enable secrets during initialization."
|
323
|
+
self.logger.error(error_msg)
|
324
|
+
raise RuntimeError(error_msg)
|
325
|
+
|
326
|
+
try:
|
327
|
+
self.logger.debug(
|
328
|
+
"Deleting secret from Key Vault",
|
329
|
+
extra={"secret_name": secret_name, "vault_url": self.vault_url}
|
330
|
+
)
|
331
|
+
|
332
|
+
self.secret_client.begin_delete_secret(secret_name, **kwargs)
|
333
|
+
|
334
|
+
self.logger.info(
|
335
|
+
"Secret deletion initiated successfully",
|
336
|
+
extra={"secret_name": secret_name}
|
337
|
+
)
|
338
|
+
|
339
|
+
return True
|
340
|
+
|
341
|
+
except ResourceNotFoundError:
|
342
|
+
self.logger.warning(
|
343
|
+
f"Secret '{secret_name}' not found for deletion",
|
344
|
+
extra={"secret_name": secret_name}
|
345
|
+
)
|
346
|
+
return False
|
347
|
+
except Exception as e:
|
348
|
+
self.logger.error(
|
349
|
+
f"Failed to delete secret '{secret_name}': {e}",
|
350
|
+
extra={"secret_name": secret_name},
|
351
|
+
exc_info=True
|
352
|
+
)
|
353
|
+
raise
|
354
|
+
|
355
|
+
def list_secrets(self, **kwargs) -> List[str]:
|
356
|
+
"""List all secrets in the Key Vault.
|
357
|
+
|
358
|
+
Args:
|
359
|
+
**kwargs: Additional parameters for listing secrets
|
360
|
+
|
361
|
+
Returns:
|
362
|
+
List of secret names
|
363
|
+
|
364
|
+
Raises:
|
365
|
+
RuntimeError: If secret client is not initialized
|
366
|
+
Exception: If listing secrets fails
|
367
|
+
"""
|
368
|
+
with self.logger.create_span(
|
369
|
+
"AzureKeyVault.list_secrets",
|
370
|
+
attributes={
|
371
|
+
"service.name": self.service_name,
|
372
|
+
"operation.type": "secret_listing",
|
373
|
+
"keyvault.vault_url": self.vault_url
|
374
|
+
}
|
375
|
+
):
|
376
|
+
if self.secret_client is None:
|
377
|
+
error_msg = "Secret client not initialized. Enable secrets during initialization."
|
378
|
+
self.logger.error(error_msg)
|
379
|
+
raise RuntimeError(error_msg)
|
380
|
+
|
381
|
+
try:
|
382
|
+
self.logger.debug(
|
383
|
+
"Listing secrets from Key Vault",
|
384
|
+
extra={"vault_url": self.vault_url}
|
385
|
+
)
|
386
|
+
|
387
|
+
secret_names = []
|
388
|
+
for secret_property in self.secret_client.list_properties_of_secrets(**kwargs):
|
389
|
+
secret_names.append(secret_property.name)
|
390
|
+
|
391
|
+
self.logger.info(
|
392
|
+
"Secrets listed successfully",
|
393
|
+
extra={"secret_count": len(secret_names)}
|
394
|
+
)
|
395
|
+
|
396
|
+
return secret_names
|
397
|
+
|
398
|
+
except Exception as e:
|
399
|
+
self.logger.error(
|
400
|
+
f"Failed to list secrets: {e}",
|
401
|
+
exc_info=True
|
402
|
+
)
|
403
|
+
raise
|
404
|
+
|
405
|
+
def test_connection(self) -> bool:
|
406
|
+
"""Test connection to Key Vault by attempting to list secrets.
|
407
|
+
|
408
|
+
Returns:
|
409
|
+
True if connection is successful, False otherwise
|
410
|
+
"""
|
411
|
+
with self.logger.create_span(
|
412
|
+
"AzureKeyVault.test_connection",
|
413
|
+
attributes={
|
414
|
+
"service.name": self.service_name,
|
415
|
+
"operation.type": "connection_test",
|
416
|
+
"keyvault.vault_url": self.vault_url
|
417
|
+
}
|
418
|
+
):
|
419
|
+
try:
|
420
|
+
self.logger.debug(
|
421
|
+
"Testing Key Vault connection",
|
422
|
+
extra={"vault_url": self.vault_url}
|
423
|
+
)
|
424
|
+
|
425
|
+
if self.secret_client is not None:
|
426
|
+
# Try to list secrets (limited to 1) to test connection
|
427
|
+
list(self.secret_client.list_properties_of_secrets(max_page_size=1))
|
428
|
+
elif self.key_client is not None:
|
429
|
+
# Try to list keys if secrets are disabled
|
430
|
+
list(self.key_client.list_properties_of_keys(max_page_size=1))
|
431
|
+
elif self.certificate_client is not None:
|
432
|
+
# Try to list certificates if keys are disabled
|
433
|
+
list(self.certificate_client.list_properties_of_certificates(max_page_size=1))
|
434
|
+
else:
|
435
|
+
self.logger.error("No clients available for connection testing")
|
436
|
+
return False
|
437
|
+
|
438
|
+
self.logger.info("Key Vault connection test successful")
|
439
|
+
return True
|
440
|
+
|
441
|
+
except Exception as e:
|
442
|
+
self.logger.warning(
|
443
|
+
f"Key Vault connection test failed: {e}",
|
444
|
+
extra={"vault_url": self.vault_url}
|
445
|
+
)
|
446
|
+
return False
|
447
|
+
|
448
|
+
def set_correlation_id(self, correlation_id: str):
|
449
|
+
"""Set correlation ID for request/transaction tracking.
|
450
|
+
|
451
|
+
Args:
|
452
|
+
correlation_id: Unique identifier for transaction correlation
|
453
|
+
"""
|
454
|
+
self.logger.set_correlation_id(correlation_id)
|
455
|
+
|
456
|
+
def get_correlation_id(self) -> Optional[str]:
|
457
|
+
"""Get current correlation ID.
|
458
|
+
|
459
|
+
Returns:
|
460
|
+
Current correlation ID if set, otherwise None
|
461
|
+
"""
|
462
|
+
return self.logger.get_correlation_id()
|
463
|
+
|
464
|
+
|
465
|
+
def create_azure_keyvault(
|
466
|
+
vault_url: str,
|
467
|
+
credential: Optional[TokenCredential] = None,
|
468
|
+
azure_identity: Optional[AzureIdentity] = None,
|
469
|
+
service_name: str = "azure_keyvault",
|
470
|
+
service_version: str = "1.0.0",
|
471
|
+
logger: Optional[AzureLogger] = None,
|
472
|
+
connection_string: Optional[str] = None,
|
473
|
+
enable_secrets: bool = True,
|
474
|
+
enable_keys: bool = True,
|
475
|
+
enable_certificates: bool = True,
|
476
|
+
) -> AzureKeyVault:
|
477
|
+
"""Factory function to create AzureKeyVault instance.
|
478
|
+
|
479
|
+
Provides a convenient way to create an AzureKeyVault instance with
|
480
|
+
common configuration patterns. If no credential or azure_identity
|
481
|
+
is provided, creates a default AzureIdentity instance.
|
482
|
+
|
483
|
+
Args:
|
484
|
+
vault_url: Azure Key Vault URL
|
485
|
+
credential: Azure credential for authentication
|
486
|
+
azure_identity: AzureIdentity instance for credential management
|
487
|
+
service_name: Service name for tracing context
|
488
|
+
service_version: Service version for metadata
|
489
|
+
logger: Optional AzureLogger instance
|
490
|
+
connection_string: Application Insights connection string
|
491
|
+
enable_secrets: Enable secret operations client
|
492
|
+
enable_keys: Enable key operations client
|
493
|
+
enable_certificates: Enable certificate operations client
|
494
|
+
|
495
|
+
Returns:
|
496
|
+
Configured AzureKeyVault instance
|
497
|
+
|
498
|
+
Example:
|
499
|
+
# Basic usage with default credential
|
500
|
+
kv = create_azure_keyvault("https://vault.vault.azure.net/")
|
501
|
+
|
502
|
+
# With custom service name and specific features
|
503
|
+
kv = create_azure_keyvault(
|
504
|
+
"https://vault.vault.azure.net/",
|
505
|
+
service_name="my_app",
|
506
|
+
enable_keys=False,
|
507
|
+
enable_certificates=False
|
508
|
+
)
|
509
|
+
"""
|
510
|
+
if credential is None and azure_identity is None:
|
511
|
+
# Create default AzureIdentity instance
|
512
|
+
from ..mgmt.identity import create_azure_identity
|
513
|
+
azure_identity = create_azure_identity(
|
514
|
+
service_name=f"{service_name}_identity",
|
515
|
+
service_version=service_version,
|
516
|
+
connection_string=connection_string,
|
517
|
+
)
|
518
|
+
|
519
|
+
return AzureKeyVault(
|
520
|
+
vault_url=vault_url,
|
521
|
+
credential=credential,
|
522
|
+
azure_identity=azure_identity,
|
523
|
+
service_name=service_name,
|
524
|
+
service_version=service_version,
|
525
|
+
logger=logger,
|
526
|
+
connection_string=connection_string,
|
527
|
+
enable_secrets=enable_secrets,
|
528
|
+
enable_keys=enable_keys,
|
529
|
+
enable_certificates=enable_certificates,
|
530
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: azpaddypy
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.6
|
4
4
|
Summary: Comprehensive Python logger for Azure, integrating OpenTelemetry for advanced, structured, and distributed tracing.
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
6
6
|
Classifier: Operating System :: OS Independent
|
@@ -9,9 +9,8 @@ Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
10
10
|
Requires-Dist: azure-monitor-opentelemetry==1.6.10
|
11
11
|
Requires-Dist: azure-functions==1.23.0
|
12
|
-
|
13
|
-
Requires-Dist: azure-
|
14
|
-
Requires-Dist:
|
15
|
-
Requires-Dist:
|
16
|
-
Requires-Dist: anyio==4.9.0; extra == "dev"
|
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
|
17
16
|
Dynamic: license-file
|
@@ -0,0 +1,11 @@
|
|
1
|
+
azpaddypy/__init__.py,sha256=hrWNAh4OHZOvm3Pbhq5eUjO-pSRYn0h0W0J87tc-lNI,45
|
2
|
+
azpaddypy/mgmt/__init__.py,sha256=-jH8Ftx9C8qu4yF5dMVEapVZhNwG7m4QCUjyutesOoY,278
|
3
|
+
azpaddypy/mgmt/identity.py,sha256=mA_krQslMsK_sDob-z-QA0B9khK_JUO2way7xwPopR8,12001
|
4
|
+
azpaddypy/mgmt/logging.py,sha256=pivPsHeySF1Dyx6HKCSos7HBOYyJMVeRP25wYZu3Sno,35117
|
5
|
+
azpaddypy/resources/__init__.py,sha256=Bvt3VK4RqwoxYpoh6EbLXIR18RuFPKaLP6zLL-icyFk,314
|
6
|
+
azpaddypy/resources/keyvault.py,sha256=4J08vLqoLFd1_UUDBji2oG2fatZaPkgnRyT_Z6wHAOc,20312
|
7
|
+
azpaddypy-0.3.6.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
|
8
|
+
azpaddypy-0.3.6.dist-info/METADATA,sha256=jTLf8ztnMvo9Mlzma_k3Ck9kR7XpiDGse6SiPca9zeE,665
|
9
|
+
azpaddypy-0.3.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
10
|
+
azpaddypy-0.3.6.dist-info/top_level.txt,sha256=hsDuboDhT61320ML8X479ezSTwT3rrlDWz1_Z45B2cs,10
|
11
|
+
azpaddypy-0.3.6.dist-info/RECORD,,
|
@@ -1,112 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
import json
|
3
|
-
import time
|
4
|
-
import azure.functions as func
|
5
|
-
from azpaddypy.mgmt.logging import create_function_logger
|
6
|
-
|
7
|
-
# Initialize the logger
|
8
|
-
logger = create_function_logger(
|
9
|
-
function_app_name="test-function-app", function_name="test-function"
|
10
|
-
)
|
11
|
-
|
12
|
-
|
13
|
-
@logger.trace_function(log_args=True, log_result=True)
|
14
|
-
def process_request(req_body: dict) -> dict:
|
15
|
-
"""Process the request body and return a response"""
|
16
|
-
# Simulate some processing time
|
17
|
-
time.sleep(0.1)
|
18
|
-
|
19
|
-
# Log the request processing
|
20
|
-
logger.info(
|
21
|
-
"Processing request",
|
22
|
-
extra={
|
23
|
-
"request_id": req_body.get("request_id", "unknown"),
|
24
|
-
"action": req_body.get("action", "unknown"),
|
25
|
-
},
|
26
|
-
)
|
27
|
-
|
28
|
-
return {
|
29
|
-
"status": "success",
|
30
|
-
"message": "Request processed successfully",
|
31
|
-
"data": req_body,
|
32
|
-
}
|
33
|
-
|
34
|
-
|
35
|
-
def main(req: func.HttpRequest) -> func.HttpResponse:
|
36
|
-
"""Azure Function entry point"""
|
37
|
-
try:
|
38
|
-
# Start timing the request
|
39
|
-
start_time = time.time()
|
40
|
-
|
41
|
-
# Get request details
|
42
|
-
method = req.method
|
43
|
-
url = req.url
|
44
|
-
headers = dict(req.headers)
|
45
|
-
|
46
|
-
# Log the incoming request
|
47
|
-
logger.log_request(
|
48
|
-
method=method,
|
49
|
-
url=url,
|
50
|
-
status_code=200, # We'll update this if there's an error
|
51
|
-
duration_ms=0, # We'll update this at the end
|
52
|
-
extra={"headers": headers, "request_type": "http_trigger"},
|
53
|
-
)
|
54
|
-
|
55
|
-
# Create a span for the entire function execution
|
56
|
-
with logger.create_span("function_execution") as span:
|
57
|
-
# Add request metadata to the span
|
58
|
-
span.set_attribute("http.method", method)
|
59
|
-
span.set_attribute("http.url", url)
|
60
|
-
|
61
|
-
# Parse request body
|
62
|
-
try:
|
63
|
-
req_body = req.get_json()
|
64
|
-
except ValueError:
|
65
|
-
req_body = {}
|
66
|
-
|
67
|
-
# Log the request body
|
68
|
-
logger.info("Received request body", extra={"body": req_body})
|
69
|
-
|
70
|
-
# Process the request
|
71
|
-
result = process_request(req_body)
|
72
|
-
|
73
|
-
# Calculate request duration
|
74
|
-
duration_ms = (time.time() - start_time) * 1000
|
75
|
-
|
76
|
-
# Log successful completion
|
77
|
-
logger.log_function_execution(
|
78
|
-
function_name="main",
|
79
|
-
duration_ms=duration_ms,
|
80
|
-
success=True,
|
81
|
-
extra={"method": method, "url": url},
|
82
|
-
)
|
83
|
-
|
84
|
-
# Return the response
|
85
|
-
return func.HttpResponse(
|
86
|
-
json.dumps(result), mimetype="application/json", status_code=200
|
87
|
-
)
|
88
|
-
|
89
|
-
except Exception as e:
|
90
|
-
# Calculate request duration
|
91
|
-
duration_ms = (time.time() - start_time) * 1000
|
92
|
-
|
93
|
-
# Log the error
|
94
|
-
logger.error(
|
95
|
-
f"Error processing request: {str(e)}",
|
96
|
-
extra={"method": method, "url": url, "error_type": type(e).__name__},
|
97
|
-
)
|
98
|
-
|
99
|
-
# Log failed execution
|
100
|
-
logger.log_function_execution(
|
101
|
-
function_name="main",
|
102
|
-
duration_ms=duration_ms,
|
103
|
-
success=False,
|
104
|
-
extra={"error": str(e), "error_type": type(e).__name__},
|
105
|
-
)
|
106
|
-
|
107
|
-
# Return error response
|
108
|
-
return func.HttpResponse(
|
109
|
-
json.dumps({"status": "error", "message": str(e)}),
|
110
|
-
mimetype="application/json",
|
111
|
-
status_code=500,
|
112
|
-
)
|
@@ -1,129 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
import json
|
3
|
-
import time
|
4
|
-
import asyncio
|
5
|
-
import azure.functions as func
|
6
|
-
from azpaddypy.mgmt.logging import create_function_logger
|
7
|
-
|
8
|
-
app = func.FunctionApp()
|
9
|
-
|
10
|
-
# Initialize the logger
|
11
|
-
logger = create_function_logger(
|
12
|
-
function_app_name="test-function-app", function_name="test-function"
|
13
|
-
)
|
14
|
-
|
15
|
-
|
16
|
-
@logger.trace_function(log_args=True, log_result=True)
|
17
|
-
def process_request(req_body: dict) -> dict:
|
18
|
-
"""Process the request body and return a response"""
|
19
|
-
# Simulate some processing time
|
20
|
-
time.sleep(0.1)
|
21
|
-
|
22
|
-
# Log the request processing
|
23
|
-
logger.info(
|
24
|
-
"Processing request",
|
25
|
-
extra={
|
26
|
-
"request_id": req_body.get("request_id", "unknown"),
|
27
|
-
"action": req_body.get("action", "unknown"),
|
28
|
-
},
|
29
|
-
)
|
30
|
-
|
31
|
-
return {
|
32
|
-
"status": "success",
|
33
|
-
"message": "Request processed successfully",
|
34
|
-
"data": req_body,
|
35
|
-
}
|
36
|
-
|
37
|
-
|
38
|
-
@logger.trace_function(log_args=True, log_result=True)
|
39
|
-
async def process_request_async(req_body: dict) -> dict:
|
40
|
-
"""Process the request body asynchronously and return a response"""
|
41
|
-
# Simulate some async processing time
|
42
|
-
await asyncio.sleep(0.1)
|
43
|
-
|
44
|
-
# Log the request processing
|
45
|
-
logger.info(
|
46
|
-
"Processing async request",
|
47
|
-
extra={
|
48
|
-
"request_id": req_body.get("request_id", "unknown"),
|
49
|
-
"action": req_body.get("action", "unknown"),
|
50
|
-
"is_async": True,
|
51
|
-
},
|
52
|
-
)
|
53
|
-
|
54
|
-
return {
|
55
|
-
"status": "success",
|
56
|
-
"message": "Async request processed successfully",
|
57
|
-
"data": req_body,
|
58
|
-
}
|
59
|
-
|
60
|
-
|
61
|
-
@app.function_name(name="test-function")
|
62
|
-
@app.route(route="test-function", auth_level=func.AuthLevel.ANONYMOUS)
|
63
|
-
async def test_function(req: func.HttpRequest) -> func.HttpResponse:
|
64
|
-
"""Azure Function HTTP trigger that processes requests both synchronously and asynchronously"""
|
65
|
-
start_time = time.time()
|
66
|
-
method = req.method
|
67
|
-
url = str(req.url)
|
68
|
-
|
69
|
-
try:
|
70
|
-
# Get request body
|
71
|
-
req_body = req.get_json()
|
72
|
-
|
73
|
-
# Process request based on the action
|
74
|
-
action = req_body.get("action", "").lower()
|
75
|
-
|
76
|
-
if action == "async":
|
77
|
-
# Process request asynchronously
|
78
|
-
result = await process_request_async(req_body)
|
79
|
-
else:
|
80
|
-
# Process request synchronously
|
81
|
-
result = process_request(req_body)
|
82
|
-
|
83
|
-
# Calculate request duration
|
84
|
-
duration_ms = (time.time() - start_time) * 1000
|
85
|
-
|
86
|
-
# Log successful request
|
87
|
-
logger.log_request(
|
88
|
-
method=method,
|
89
|
-
url=url,
|
90
|
-
status_code=200,
|
91
|
-
duration_ms=duration_ms,
|
92
|
-
extra={
|
93
|
-
"request_id": req_body.get("request_id", "unknown"),
|
94
|
-
"action": action,
|
95
|
-
"is_async": action == "async",
|
96
|
-
},
|
97
|
-
)
|
98
|
-
|
99
|
-
# Return success response
|
100
|
-
return func.HttpResponse(
|
101
|
-
json.dumps(result),
|
102
|
-
mimetype="application/json",
|
103
|
-
status_code=200,
|
104
|
-
)
|
105
|
-
|
106
|
-
except Exception as e:
|
107
|
-
# Calculate request duration
|
108
|
-
duration_ms = (time.time() - start_time) * 1000
|
109
|
-
|
110
|
-
# Log the error
|
111
|
-
logger.error(
|
112
|
-
f"Error processing request: {str(e)}",
|
113
|
-
extra={"method": method, "url": url, "error_type": type(e).__name__},
|
114
|
-
)
|
115
|
-
|
116
|
-
# Log failed execution
|
117
|
-
logger.log_function_execution(
|
118
|
-
function_name="test_function",
|
119
|
-
duration_ms=duration_ms,
|
120
|
-
success=False,
|
121
|
-
extra={"error": str(e), "error_type": type(e).__name__},
|
122
|
-
)
|
123
|
-
|
124
|
-
# Return error response
|
125
|
-
return func.HttpResponse(
|
126
|
-
json.dumps({"status": "error", "message": str(e)}),
|
127
|
-
mimetype="application/json",
|
128
|
-
status_code=500,
|
129
|
-
)
|
azpaddypy-0.3.4.dist-info/RECORD
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
azpaddypy/mgmt/__init__.py,sha256=-jH8Ftx9C8qu4yF5dMVEapVZhNwG7m4QCUjyutesOoY,278
|
2
|
-
azpaddypy/mgmt/identity.py,sha256=mA_krQslMsK_sDob-z-QA0B9khK_JUO2way7xwPopR8,12001
|
3
|
-
azpaddypy/mgmt/logging.py,sha256=pivPsHeySF1Dyx6HKCSos7HBOYyJMVeRP25wYZu3Sno,35117
|
4
|
-
azpaddypy/test_function/__init__.py,sha256=0NjUl36wvUWV79GpRwBFkgkBaC6uDZsTdaSVOIHMFEU,3481
|
5
|
-
azpaddypy/test_function/function_app.py,sha256=6nX54-iq0L1l_hZpD6E744-j79oLxdaldFyWDCpwH7c,3867
|
6
|
-
azpaddypy-0.3.4.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
|
7
|
-
azpaddypy-0.3.4.dist-info/METADATA,sha256=QDqVtpO7pbf_VGJM4e3W6QHV0VtOB3gc6kH8pAoe7jQ,705
|
8
|
-
azpaddypy-0.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
9
|
-
azpaddypy-0.3.4.dist-info/top_level.txt,sha256=hsDuboDhT61320ML8X479ezSTwT3rrlDWz1_Z45B2cs,10
|
10
|
-
azpaddypy-0.3.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|