azpaddypy 0.2.6__py3-none-any.whl → 0.2.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- azpaddypy/mgmt/__init__.py +6 -2
- azpaddypy/mgmt/identity.py +335 -0
- azpaddypy/mgmt/logging.py +143 -121
- {azpaddypy-0.2.6.dist-info → azpaddypy-0.2.8.dist-info}/METADATA +6 -7
- azpaddypy-0.2.8.dist-info/RECORD +10 -0
- azpaddypy-0.2.6.dist-info/RECORD +0 -9
- {azpaddypy-0.2.6.dist-info → azpaddypy-0.2.8.dist-info}/WHEEL +0 -0
- {azpaddypy-0.2.6.dist-info → azpaddypy-0.2.8.dist-info}/licenses/LICENSE +0 -0
- {azpaddypy-0.2.6.dist-info → azpaddypy-0.2.8.dist-info}/top_level.txt +0 -0
azpaddypy/mgmt/__init__.py
CHANGED
@@ -4,6 +4,10 @@ AzPaddyPy - A standardized Python package for Azure cloud services integration.
|
|
4
4
|
|
5
5
|
__version__ = "0.1.0"
|
6
6
|
|
7
|
-
from azpaddypy.mgmt import
|
7
|
+
from azpaddypy.mgmt.logging import AzureLogger
|
8
|
+
from azpaddypy.mgmt.identity import AzureIdentity
|
8
9
|
|
9
|
-
__all__ = [
|
10
|
+
__all__ = [
|
11
|
+
"AzureLogger",
|
12
|
+
"AzureIdentity",
|
13
|
+
]
|
@@ -0,0 +1,335 @@
|
|
1
|
+
from typing import Optional, Dict, Any, Union
|
2
|
+
from azure.identity import (
|
3
|
+
TokenCachePersistenceOptions,
|
4
|
+
get_bearer_token_provider,
|
5
|
+
DefaultAzureCredential
|
6
|
+
)
|
7
|
+
from azure.core.credentials import AccessToken, TokenCredential
|
8
|
+
from .logging import AzureLogger
|
9
|
+
|
10
|
+
|
11
|
+
class AzureIdentity:
|
12
|
+
"""Azure identity management with token caching and distributed tracing.
|
13
|
+
|
14
|
+
Provides standardized Azure authentication using DefaultAzureCredential
|
15
|
+
with integrated logging, caching, and OpenTelemetry tracing support.
|
16
|
+
Prioritizes Managed Identity, then Environment variables per Azure SDK
|
17
|
+
best practices.
|
18
|
+
|
19
|
+
Attributes:
|
20
|
+
service_name: Service identifier for logging and tracing
|
21
|
+
service_version: Service version for context
|
22
|
+
enable_token_cache: Whether token caching is enabled
|
23
|
+
allow_unencrypted_storage: Whether to allow unencrypted token storage
|
24
|
+
logger: AzureLogger instance for structured logging
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
service_name: str = "azure_identity",
|
30
|
+
service_version: str = "1.0.0",
|
31
|
+
enable_token_cache: bool = True,
|
32
|
+
allow_unencrypted_storage: bool = True,
|
33
|
+
custom_credential_options: Optional[Dict[str, Any]] = None,
|
34
|
+
logger: Optional[AzureLogger] = None,
|
35
|
+
connection_string: Optional[str] = None,
|
36
|
+
):
|
37
|
+
"""Initialize Azure Identity with comprehensive configuration.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
service_name: Service name for tracing context
|
41
|
+
service_version: Service version for metadata
|
42
|
+
enable_token_cache: Enable in-memory token persistence
|
43
|
+
allow_unencrypted_storage: Allow unencrypted token storage
|
44
|
+
custom_credential_options: Additional DefaultAzureCredential options
|
45
|
+
logger: Optional AzureLogger instance
|
46
|
+
connection_string: Application Insights connection string
|
47
|
+
"""
|
48
|
+
self.service_name = service_name
|
49
|
+
self.service_version = service_version
|
50
|
+
self.enable_token_cache = enable_token_cache
|
51
|
+
self.allow_unencrypted_storage = allow_unencrypted_storage
|
52
|
+
|
53
|
+
# Initialize logger - use provided instance or create new one
|
54
|
+
if logger is not None:
|
55
|
+
self.logger = logger
|
56
|
+
else:
|
57
|
+
self.logger = AzureLogger(
|
58
|
+
service_name=service_name,
|
59
|
+
service_version=service_version,
|
60
|
+
connection_string=connection_string,
|
61
|
+
enable_console_logging=True,
|
62
|
+
)
|
63
|
+
|
64
|
+
self._credential = None
|
65
|
+
self._setup_credential(custom_credential_options)
|
66
|
+
|
67
|
+
self.logger.info(
|
68
|
+
f"Azure Identity initialized for service '{service_name}' v{service_version}"
|
69
|
+
)
|
70
|
+
|
71
|
+
def _setup_credential(self, custom_options: Optional[Dict[str, Any]] = None):
|
72
|
+
"""Configure DefaultAzureCredential with appropriate settings.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
custom_options: Additional options for DefaultAzureCredential
|
76
|
+
|
77
|
+
Raises:
|
78
|
+
Exception: If credential initialization fails
|
79
|
+
"""
|
80
|
+
try:
|
81
|
+
credential_options = {}
|
82
|
+
|
83
|
+
# Add token cache configuration if enabled
|
84
|
+
if self.enable_token_cache:
|
85
|
+
token_cache_options = TokenCachePersistenceOptions(
|
86
|
+
allow_unencrypted_storage=self.allow_unencrypted_storage
|
87
|
+
)
|
88
|
+
credential_options["token_cache_persistence_options"] = token_cache_options
|
89
|
+
|
90
|
+
# Merge custom options
|
91
|
+
if custom_options:
|
92
|
+
credential_options.update(custom_options)
|
93
|
+
|
94
|
+
self._credential = DefaultAzureCredential(**credential_options)
|
95
|
+
|
96
|
+
self.logger.debug(
|
97
|
+
"DefaultAzureCredential configured successfully",
|
98
|
+
extra={
|
99
|
+
"token_cache_enabled": self.enable_token_cache,
|
100
|
+
"unencrypted_storage": self.allow_unencrypted_storage,
|
101
|
+
}
|
102
|
+
)
|
103
|
+
|
104
|
+
except Exception as e:
|
105
|
+
self.logger.error(
|
106
|
+
f"Failed to initialize DefaultAzureCredential: {e}",
|
107
|
+
exc_info=True
|
108
|
+
)
|
109
|
+
raise
|
110
|
+
|
111
|
+
def get_credential(self) -> TokenCredential:
|
112
|
+
"""Get the configured DefaultAzureCredential instance.
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
Configured TokenCredential instance
|
116
|
+
|
117
|
+
Raises:
|
118
|
+
RuntimeError: If credential is not initialized
|
119
|
+
"""
|
120
|
+
with self.logger.create_span(
|
121
|
+
"AzureIdentity.get_credential",
|
122
|
+
attributes={
|
123
|
+
"service.name": self.service_name,
|
124
|
+
"operation.type": "credential_retrieval"
|
125
|
+
}
|
126
|
+
):
|
127
|
+
if self._credential is None:
|
128
|
+
error_msg = "Credential not initialized"
|
129
|
+
self.logger.error(error_msg)
|
130
|
+
raise RuntimeError(error_msg)
|
131
|
+
|
132
|
+
self.logger.debug("Retrieving DefaultAzureCredential instance")
|
133
|
+
return self._credential
|
134
|
+
|
135
|
+
def get_token(
|
136
|
+
self,
|
137
|
+
scopes: Union[str, list],
|
138
|
+
**kwargs
|
139
|
+
) -> AccessToken:
|
140
|
+
"""Acquire an access token for specified scopes.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
scopes: Target scope(s) for token request
|
144
|
+
**kwargs: Additional token request parameters
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
AccessToken with token and expiration information
|
148
|
+
|
149
|
+
Raises:
|
150
|
+
RuntimeError: If credential is not initialized
|
151
|
+
Exception: If token acquisition fails
|
152
|
+
"""
|
153
|
+
# Normalize to list format
|
154
|
+
if isinstance(scopes, str):
|
155
|
+
scopes = [scopes]
|
156
|
+
|
157
|
+
with self.logger.create_span(
|
158
|
+
"AzureIdentity.get_token",
|
159
|
+
attributes={
|
160
|
+
"service.name": self.service_name,
|
161
|
+
"operation.type": "token_acquisition",
|
162
|
+
"token.scopes": ", ".join(scopes),
|
163
|
+
"token.scope_count": len(scopes)
|
164
|
+
}
|
165
|
+
):
|
166
|
+
if self._credential is None:
|
167
|
+
raise RuntimeError("Credential not initialized")
|
168
|
+
|
169
|
+
self.logger.debug(
|
170
|
+
"Acquiring access token",
|
171
|
+
extra={
|
172
|
+
"scopes": scopes,
|
173
|
+
"scope_count": len(scopes)
|
174
|
+
}
|
175
|
+
)
|
176
|
+
|
177
|
+
token = self._credential.get_token(*scopes, **kwargs)
|
178
|
+
|
179
|
+
self.logger.info(
|
180
|
+
"Access token acquired successfully",
|
181
|
+
extra={
|
182
|
+
"scopes": scopes,
|
183
|
+
"expires_on": token.expires_on if hasattr(token, 'expires_on') else None
|
184
|
+
}
|
185
|
+
)
|
186
|
+
|
187
|
+
return token
|
188
|
+
|
189
|
+
def get_token_provider(
|
190
|
+
self,
|
191
|
+
scopes: Union[str, list],
|
192
|
+
**kwargs
|
193
|
+
) -> callable:
|
194
|
+
"""Create a bearer token provider for specified scopes.
|
195
|
+
|
196
|
+
Useful for Azure SDK clients that accept token providers.
|
197
|
+
|
198
|
+
Args:
|
199
|
+
scopes: Target scope(s) for the token provider
|
200
|
+
**kwargs: Additional token provider parameters
|
201
|
+
|
202
|
+
Returns:
|
203
|
+
Callable that returns bearer tokens
|
204
|
+
|
205
|
+
Raises:
|
206
|
+
RuntimeError: If credential is not initialized
|
207
|
+
Exception: If token provider creation fails
|
208
|
+
"""
|
209
|
+
# Normalize to list format
|
210
|
+
if isinstance(scopes, str):
|
211
|
+
scopes = [scopes]
|
212
|
+
|
213
|
+
with self.logger.create_span(
|
214
|
+
"AzureIdentity.get_token_provider",
|
215
|
+
attributes={
|
216
|
+
"service.name": self.service_name,
|
217
|
+
"operation.type": "token_provider_creation",
|
218
|
+
"token.scopes": ", ".join(scopes),
|
219
|
+
"token.scope_count": len(scopes)
|
220
|
+
}
|
221
|
+
):
|
222
|
+
if self._credential is None:
|
223
|
+
raise RuntimeError("Credential not initialized")
|
224
|
+
|
225
|
+
self.logger.debug(
|
226
|
+
"Creating bearer token provider",
|
227
|
+
extra={
|
228
|
+
"scopes": scopes,
|
229
|
+
"scope_count": len(scopes)
|
230
|
+
}
|
231
|
+
)
|
232
|
+
|
233
|
+
provider = get_bearer_token_provider(self._credential, *scopes, **kwargs)
|
234
|
+
|
235
|
+
self.logger.info(
|
236
|
+
"Bearer token provider created successfully",
|
237
|
+
extra={
|
238
|
+
"scopes": scopes
|
239
|
+
}
|
240
|
+
)
|
241
|
+
|
242
|
+
return provider
|
243
|
+
|
244
|
+
def test_credential(self, test_scopes: Optional[Union[str, list]] = None) -> bool:
|
245
|
+
"""Test credential by attempting token acquisition.
|
246
|
+
|
247
|
+
Args:
|
248
|
+
test_scopes: Scopes to test with (defaults to Azure Management API)
|
249
|
+
|
250
|
+
Returns:
|
251
|
+
True if credential works, False otherwise
|
252
|
+
"""
|
253
|
+
if test_scopes is None:
|
254
|
+
test_scopes = ["https://management.azure.com/.default"]
|
255
|
+
elif isinstance(test_scopes, str):
|
256
|
+
test_scopes = [test_scopes]
|
257
|
+
|
258
|
+
with self.logger.create_span(
|
259
|
+
"AzureIdentity.test_credential",
|
260
|
+
attributes={
|
261
|
+
"service.name": self.service_name,
|
262
|
+
"operation.type": "credential_test",
|
263
|
+
"test.scopes": ", ".join(test_scopes)
|
264
|
+
}
|
265
|
+
):
|
266
|
+
try:
|
267
|
+
self.logger.info("Testing credential authentication")
|
268
|
+
token = self.get_token(test_scopes)
|
269
|
+
|
270
|
+
if token and hasattr(token, 'token') and token.token:
|
271
|
+
self.logger.info("Credential test successful")
|
272
|
+
return True
|
273
|
+
else:
|
274
|
+
self.logger.warning("Credential test returned empty token")
|
275
|
+
return False
|
276
|
+
|
277
|
+
except Exception as e:
|
278
|
+
self.logger.warning(
|
279
|
+
f"Credential test failed: {e}",
|
280
|
+
extra={"test_scopes": test_scopes}
|
281
|
+
)
|
282
|
+
return False
|
283
|
+
|
284
|
+
def set_correlation_id(self, correlation_id: str):
|
285
|
+
"""Set correlation ID for tracking identity operations.
|
286
|
+
|
287
|
+
Args:
|
288
|
+
correlation_id: Unique identifier for transaction tracking
|
289
|
+
"""
|
290
|
+
self.logger.set_correlation_id(correlation_id)
|
291
|
+
|
292
|
+
def get_correlation_id(self) -> Optional[str]:
|
293
|
+
"""Get the current correlation ID.
|
294
|
+
|
295
|
+
Returns:
|
296
|
+
Current correlation ID if set, otherwise None
|
297
|
+
"""
|
298
|
+
return self.logger.get_correlation_id()
|
299
|
+
|
300
|
+
|
301
|
+
def create_azure_identity(
|
302
|
+
service_name: str = "azure_identity",
|
303
|
+
service_version: str = "1.0.0",
|
304
|
+
enable_token_cache: bool = True,
|
305
|
+
allow_unencrypted_storage: bool = True,
|
306
|
+
custom_credential_options: Optional[Dict[str, Any]] = None,
|
307
|
+
logger: Optional[AzureLogger] = None,
|
308
|
+
connection_string: Optional[str] = None,
|
309
|
+
) -> AzureIdentity:
|
310
|
+
"""Create a configured AzureIdentity instance.
|
311
|
+
|
312
|
+
Factory function providing convenient AzureIdentity instantiation with
|
313
|
+
commonly used settings.
|
314
|
+
|
315
|
+
Args:
|
316
|
+
service_name: Service name for tracing context
|
317
|
+
service_version: Service version for metadata
|
318
|
+
enable_token_cache: Enable in-memory token persistence
|
319
|
+
allow_unencrypted_storage: Allow unencrypted token storage
|
320
|
+
custom_credential_options: Additional DefaultAzureCredential options
|
321
|
+
logger: Optional AzureLogger instance
|
322
|
+
connection_string: Application Insights connection string
|
323
|
+
|
324
|
+
Returns:
|
325
|
+
Configured AzureIdentity instance
|
326
|
+
"""
|
327
|
+
return AzureIdentity(
|
328
|
+
service_name=service_name,
|
329
|
+
service_version=service_version,
|
330
|
+
enable_token_cache=enable_token_cache,
|
331
|
+
allow_unencrypted_storage=allow_unencrypted_storage,
|
332
|
+
custom_credential_options=custom_credential_options,
|
333
|
+
logger=logger,
|
334
|
+
connection_string=connection_string,
|
335
|
+
)
|
azpaddypy/mgmt/logging.py
CHANGED
@@ -1,15 +1,11 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
|
-
import json
|
4
3
|
import functools
|
5
4
|
import time
|
6
5
|
import asyncio
|
7
6
|
from typing import Optional, Dict, Any, Union, Callable
|
8
7
|
from datetime import datetime
|
9
8
|
from azure.monitor.opentelemetry import configure_azure_monitor
|
10
|
-
from opentelemetry.metrics import get_meter_provider
|
11
|
-
from opentelemetry.trace import get_tracer_provider
|
12
|
-
from opentelemetry._logs import get_logger_provider
|
13
9
|
from opentelemetry import trace
|
14
10
|
from opentelemetry.trace import Status, StatusCode, Span
|
15
11
|
from opentelemetry import baggage
|
@@ -17,14 +13,18 @@ from opentelemetry.context import Context
|
|
17
13
|
|
18
14
|
|
19
15
|
class AzureLogger:
|
20
|
-
"""
|
21
|
-
A comprehensive logging class for Azure applications.
|
16
|
+
"""Azure-integrated logger with OpenTelemetry distributed tracing.
|
22
17
|
|
23
|
-
|
24
|
-
|
18
|
+
Provides comprehensive logging with Azure Monitor integration, correlation
|
19
|
+
tracking, baggage propagation, and automated function tracing for Azure
|
20
|
+
applications with seamless local development support.
|
25
21
|
|
26
|
-
|
27
|
-
|
22
|
+
Attributes:
|
23
|
+
service_name: Service identifier for telemetry
|
24
|
+
service_version: Service version for context
|
25
|
+
connection_string: Application Insights connection string
|
26
|
+
logger: Python logger instance
|
27
|
+
tracer: OpenTelemetry tracer for spans
|
28
28
|
"""
|
29
29
|
|
30
30
|
def __init__(
|
@@ -35,17 +35,18 @@ class AzureLogger:
|
|
35
35
|
log_level: int = logging.INFO,
|
36
36
|
enable_console_logging: bool = True,
|
37
37
|
custom_resource_attributes: Optional[Dict[str, str]] = None,
|
38
|
+
instrumentation_options: Optional[Dict[str, Any]] = None,
|
38
39
|
):
|
39
|
-
"""
|
40
|
-
Initialize the Azure Logger with OpenTelemetry tracing
|
40
|
+
"""Initialize Azure Logger with OpenTelemetry tracing.
|
41
41
|
|
42
42
|
Args:
|
43
|
-
service_name:
|
44
|
-
service_version:
|
43
|
+
service_name: Service identifier for telemetry
|
44
|
+
service_version: Service version for metadata
|
45
45
|
connection_string: Application Insights connection string
|
46
|
-
log_level:
|
46
|
+
log_level: Python logging level (default: INFO)
|
47
47
|
enable_console_logging: Enable console output for local development
|
48
|
-
custom_resource_attributes: Additional resource attributes
|
48
|
+
custom_resource_attributes: Additional OpenTelemetry resource attributes
|
49
|
+
instrumentation_options: Azure Monitor instrumentation options
|
49
50
|
"""
|
50
51
|
self.service_name = service_name
|
51
52
|
self.service_version = service_version
|
@@ -53,7 +54,7 @@ class AzureLogger:
|
|
53
54
|
"APPLICATIONINSIGHTS_CONNECTION_STRING"
|
54
55
|
)
|
55
56
|
|
56
|
-
#
|
57
|
+
# Configure resource attributes
|
57
58
|
resource_attributes = {
|
58
59
|
"service.name": service_name,
|
59
60
|
"service.version": service_version,
|
@@ -63,13 +64,14 @@ class AzureLogger:
|
|
63
64
|
if custom_resource_attributes:
|
64
65
|
resource_attributes.update(custom_resource_attributes)
|
65
66
|
|
66
|
-
# Configure Azure Monitor if connection string
|
67
|
+
# Configure Azure Monitor if connection string available
|
67
68
|
if self.connection_string:
|
68
69
|
try:
|
69
70
|
configure_azure_monitor(
|
70
71
|
connection_string=self.connection_string,
|
71
72
|
resource_attributes=resource_attributes,
|
72
73
|
enable_live_metrics=True,
|
74
|
+
instrumentation_options=instrumentation_options,
|
73
75
|
)
|
74
76
|
self._telemetry_enabled = True
|
75
77
|
except Exception as e:
|
@@ -81,21 +83,16 @@ class AzureLogger:
|
|
81
83
|
"Warning: No Application Insights connection string found. Telemetry disabled."
|
82
84
|
)
|
83
85
|
|
84
|
-
#
|
86
|
+
# Configure Python logger
|
85
87
|
self.logger = logging.getLogger(service_name)
|
86
88
|
self.logger.setLevel(log_level)
|
87
|
-
|
88
|
-
# Clear existing handlers to avoid duplicates
|
89
89
|
self.logger.handlers.clear()
|
90
90
|
|
91
|
-
# Add console handler for local development
|
92
91
|
if enable_console_logging:
|
93
92
|
self._setup_console_handler()
|
94
93
|
|
95
|
-
#
|
94
|
+
# Initialize OpenTelemetry tracer and correlation context
|
96
95
|
self.tracer = trace.get_tracer(__name__)
|
97
|
-
|
98
|
-
# Initialize correlation context
|
99
96
|
self._correlation_id = None
|
100
97
|
|
101
98
|
self.info(
|
@@ -103,12 +100,7 @@ class AzureLogger:
|
|
103
100
|
)
|
104
101
|
|
105
102
|
def _setup_console_handler(self):
|
106
|
-
"""
|
107
|
-
|
108
|
-
This is useful for local development and debugging purposes. The handler
|
109
|
-
is configured with a structured formatter to ensure log messages are
|
110
|
-
consistent and easy to read.
|
111
|
-
"""
|
103
|
+
"""Configure console handler for local development."""
|
112
104
|
console_handler = logging.StreamHandler()
|
113
105
|
formatter = logging.Formatter(
|
114
106
|
"%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(pathname)s:%(lineno)d"
|
@@ -117,70 +109,61 @@ class AzureLogger:
|
|
117
109
|
self.logger.addHandler(console_handler)
|
118
110
|
|
119
111
|
def set_correlation_id(self, correlation_id: str):
|
120
|
-
"""
|
121
|
-
|
122
|
-
This ID is automatically included in all subsequent logs and traces,
|
123
|
-
allowing for easy filtering and correlation of telemetry data.
|
112
|
+
"""Set correlation ID for request/transaction tracking.
|
124
113
|
|
125
114
|
Args:
|
126
|
-
correlation_id:
|
115
|
+
correlation_id: Unique identifier for transaction correlation
|
127
116
|
"""
|
128
117
|
self._correlation_id = correlation_id
|
129
118
|
|
130
119
|
def get_correlation_id(self) -> Optional[str]:
|
131
|
-
"""
|
120
|
+
"""Get current correlation ID.
|
132
121
|
|
133
122
|
Returns:
|
134
|
-
|
123
|
+
Current correlation ID if set, otherwise None
|
135
124
|
"""
|
136
125
|
return self._correlation_id
|
137
126
|
|
138
127
|
def set_baggage(self, key: str, value: str) -> Context:
|
139
|
-
"""
|
140
|
-
Set a baggage item in the current context
|
128
|
+
"""Set baggage item in OpenTelemetry context.
|
141
129
|
|
142
130
|
Args:
|
143
131
|
key: Baggage key
|
144
132
|
value: Baggage value
|
145
133
|
|
146
134
|
Returns:
|
147
|
-
Updated context with
|
135
|
+
Updated context with baggage item
|
148
136
|
"""
|
149
137
|
return baggage.set_baggage(key, value)
|
150
138
|
|
151
139
|
def get_baggage(self, key: str) -> Optional[str]:
|
152
|
-
"""
|
153
|
-
Retrieves a baggage item from the current OpenTelemetry context.
|
140
|
+
"""Get baggage item from current context.
|
154
141
|
|
155
142
|
Args:
|
156
|
-
key:
|
143
|
+
key: Baggage key
|
157
144
|
|
158
145
|
Returns:
|
159
|
-
|
146
|
+
Baggage value if exists, otherwise None
|
160
147
|
"""
|
161
148
|
return baggage.get_baggage(key)
|
162
149
|
|
163
150
|
def get_all_baggage(self) -> Dict[str, str]:
|
164
|
-
"""
|
165
|
-
Retrieves all baggage items from the current OpenTelemetry context.
|
151
|
+
"""Get all baggage items from current context.
|
166
152
|
|
167
153
|
Returns:
|
168
|
-
|
154
|
+
Dictionary of all baggage items
|
169
155
|
"""
|
170
156
|
return dict(baggage.get_all())
|
171
157
|
|
172
158
|
def _enhance_extra(self, extra: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
173
|
-
"""
|
174
|
-
Enriches log records with contextual information.
|
175
|
-
|
176
|
-
This method adds service details, correlation IDs, trace context,
|
177
|
-
and baggage items to the log's `extra` dictionary.
|
159
|
+
"""Enrich log records with contextual information.
|
178
160
|
|
179
161
|
Args:
|
180
|
-
extra:
|
162
|
+
extra: Optional custom data dictionary
|
181
163
|
|
182
164
|
Returns:
|
183
|
-
|
165
|
+
Enhanced dictionary with service context, correlation ID, trace
|
166
|
+
context, and baggage items
|
184
167
|
"""
|
185
168
|
enhanced_extra = {
|
186
169
|
"service_name": self.service_name,
|
@@ -209,15 +192,15 @@ class AzureLogger:
|
|
209
192
|
return enhanced_extra
|
210
193
|
|
211
194
|
def debug(self, message: str, extra: Optional[Dict[str, Any]] = None):
|
212
|
-
"""
|
195
|
+
"""Log debug message with enhanced context."""
|
213
196
|
self.logger.debug(message, extra=self._enhance_extra(extra))
|
214
197
|
|
215
198
|
def info(self, message: str, extra: Optional[Dict[str, Any]] = None):
|
216
|
-
"""
|
199
|
+
"""Log info message with enhanced context."""
|
217
200
|
self.logger.info(message, extra=self._enhance_extra(extra))
|
218
201
|
|
219
202
|
def warning(self, message: str, extra: Optional[Dict[str, Any]] = None):
|
220
|
-
"""
|
203
|
+
"""Log warning message with enhanced context."""
|
221
204
|
self.logger.warning(message, extra=self._enhance_extra(extra))
|
222
205
|
|
223
206
|
def error(
|
@@ -226,11 +209,11 @@ class AzureLogger:
|
|
226
209
|
extra: Optional[Dict[str, Any]] = None,
|
227
210
|
exc_info: bool = True,
|
228
211
|
):
|
229
|
-
"""
|
212
|
+
"""Log error message with enhanced context and exception info."""
|
230
213
|
self.logger.error(message, extra=self._enhance_extra(extra), exc_info=exc_info)
|
231
214
|
|
232
215
|
def critical(self, message: str, extra: Optional[Dict[str, Any]] = None):
|
233
|
-
"""
|
216
|
+
"""Log critical message with enhanced context."""
|
234
217
|
self.logger.critical(message, extra=self._enhance_extra(extra))
|
235
218
|
|
236
219
|
def log_function_execution(
|
@@ -240,17 +223,13 @@ class AzureLogger:
|
|
240
223
|
success: bool = True,
|
241
224
|
extra: Optional[Dict[str, Any]] = None,
|
242
225
|
):
|
243
|
-
"""
|
244
|
-
Logs a custom event for a function's execution metrics.
|
245
|
-
|
246
|
-
This captures performance data such as duration and success status,
|
247
|
-
which can be queried in Azure Monitor.
|
226
|
+
"""Log function execution metrics for performance monitoring.
|
248
227
|
|
249
228
|
Args:
|
250
|
-
function_name:
|
251
|
-
duration_ms:
|
252
|
-
success:
|
253
|
-
extra: Additional custom properties
|
229
|
+
function_name: Name of executed function
|
230
|
+
duration_ms: Execution duration in milliseconds
|
231
|
+
success: Whether function executed successfully
|
232
|
+
extra: Additional custom properties
|
254
233
|
"""
|
255
234
|
log_data = {
|
256
235
|
"function_name": function_name,
|
@@ -262,7 +241,6 @@ class AzureLogger:
|
|
262
241
|
if extra:
|
263
242
|
log_data.update(extra)
|
264
243
|
|
265
|
-
log_level = logging.INFO if success else logging.ERROR
|
266
244
|
message = f"Function '{function_name}' executed in {duration_ms:.2f}ms - {'SUCCESS' if success else 'FAILED'}"
|
267
245
|
|
268
246
|
if success:
|
@@ -278,13 +256,12 @@ class AzureLogger:
|
|
278
256
|
duration_ms: float,
|
279
257
|
extra: Optional[Dict[str, Any]] = None,
|
280
258
|
):
|
281
|
-
"""
|
282
|
-
Log HTTP request with comprehensive details
|
259
|
+
"""Log HTTP request with comprehensive details.
|
283
260
|
|
284
261
|
Args:
|
285
|
-
method: HTTP method
|
262
|
+
method: HTTP method (GET, POST, etc.)
|
286
263
|
url: Request URL
|
287
|
-
status_code: HTTP status code
|
264
|
+
status_code: HTTP response status code
|
288
265
|
duration_ms: Request duration in milliseconds
|
289
266
|
extra: Additional custom properties
|
290
267
|
"""
|
@@ -299,7 +276,7 @@ class AzureLogger:
|
|
299
276
|
if extra:
|
300
277
|
log_data.update(extra)
|
301
278
|
|
302
|
-
# Determine log level based on status code
|
279
|
+
# Determine log level and status based on status code
|
303
280
|
if status_code < 400:
|
304
281
|
log_level = logging.INFO
|
305
282
|
status_text = "SUCCESS"
|
@@ -320,19 +297,18 @@ class AzureLogger:
|
|
320
297
|
span_name: str,
|
321
298
|
attributes: Optional[Dict[str, Union[str, int, float, bool]]] = None,
|
322
299
|
) -> Span:
|
323
|
-
"""
|
324
|
-
Create a new span for distributed tracing
|
300
|
+
"""Create OpenTelemetry span for distributed tracing.
|
325
301
|
|
326
302
|
Args:
|
327
|
-
span_name: Name
|
328
|
-
attributes: Initial attributes
|
303
|
+
span_name: Name for the span
|
304
|
+
attributes: Initial span attributes
|
329
305
|
|
330
306
|
Returns:
|
331
307
|
OpenTelemetry span context manager
|
332
308
|
"""
|
333
309
|
span = self.tracer.start_span(span_name)
|
334
310
|
|
335
|
-
# Add default attributes
|
311
|
+
# Add default service attributes
|
336
312
|
span.set_attribute("service.name", self.service_name)
|
337
313
|
span.set_attribute("service.version", self.service_version)
|
338
314
|
|
@@ -354,21 +330,65 @@ class AzureLogger:
|
|
354
330
|
log_args: bool,
|
355
331
|
args: tuple,
|
356
332
|
kwargs: dict,
|
333
|
+
log_result: bool,
|
334
|
+
log_execution: bool,
|
357
335
|
):
|
358
|
-
"""
|
336
|
+
"""Configure span attributes for function tracing."""
|
359
337
|
span.set_attribute("function.name", func.__name__)
|
360
338
|
span.set_attribute("function.module", func.__module__)
|
361
339
|
span.set_attribute("service.name", self.service_name)
|
362
340
|
span.set_attribute("function.is_async", is_async)
|
363
341
|
|
342
|
+
# Add decorator parameters as span attributes
|
343
|
+
span.set_attribute("function.decorator.log_args", log_args)
|
344
|
+
span.set_attribute("function.decorator.log_result", log_result)
|
345
|
+
span.set_attribute("function.decorator.log_execution", log_execution)
|
346
|
+
|
364
347
|
if self._correlation_id:
|
365
348
|
span.set_attribute("correlation.id", self._correlation_id)
|
366
349
|
|
367
350
|
if log_args:
|
368
351
|
if args:
|
369
352
|
span.set_attribute("function.args_count", len(args))
|
353
|
+
# Add positional arguments as span attributes
|
354
|
+
import inspect
|
355
|
+
try:
|
356
|
+
sig = inspect.signature(func)
|
357
|
+
param_names = list(sig.parameters.keys())
|
358
|
+
for i, arg_value in enumerate(args):
|
359
|
+
param_name = param_names[i] if i < len(param_names) else f"arg_{i}"
|
360
|
+
try:
|
361
|
+
# Convert to string for safe serialization
|
362
|
+
attr_value = str(arg_value)
|
363
|
+
# Truncate if too long to avoid excessive data
|
364
|
+
if len(attr_value) > 1000:
|
365
|
+
attr_value = attr_value[:1000] + "..."
|
366
|
+
span.set_attribute(f"function.arg.{param_name}", attr_value)
|
367
|
+
except Exception:
|
368
|
+
span.set_attribute(f"function.arg.{param_name}", "<non-serializable>")
|
369
|
+
except Exception:
|
370
|
+
# Fallback if signature inspection fails
|
371
|
+
for i, arg_value in enumerate(args):
|
372
|
+
try:
|
373
|
+
attr_value = str(arg_value)
|
374
|
+
if len(attr_value) > 1000:
|
375
|
+
attr_value = attr_value[:1000] + "..."
|
376
|
+
span.set_attribute(f"function.arg.{i}", attr_value)
|
377
|
+
except Exception:
|
378
|
+
span.set_attribute(f"function.arg.{i}", "<non-serializable>")
|
379
|
+
|
370
380
|
if kwargs:
|
371
381
|
span.set_attribute("function.kwargs_count", len(kwargs))
|
382
|
+
# Add keyword arguments as span attributes
|
383
|
+
for key, value in kwargs.items():
|
384
|
+
try:
|
385
|
+
attr_value = str(value)
|
386
|
+
# Truncate if too long to avoid excessive data
|
387
|
+
if len(attr_value) > 1000:
|
388
|
+
attr_value = attr_value[:1000] + "..."
|
389
|
+
span.set_attribute(f"function.kwarg.{key}", attr_value)
|
390
|
+
except Exception:
|
391
|
+
span.set_attribute(f"function.kwarg.{key}", "<non-serializable>")
|
372
392
|
|
373
393
|
def _handle_function_success(
|
374
394
|
self,
|
@@ -382,7 +402,7 @@ class AzureLogger:
|
|
382
402
|
args: tuple,
|
383
403
|
kwargs: dict,
|
384
404
|
):
|
385
|
-
"""
|
405
|
+
"""Handle successful function execution in tracing."""
|
386
406
|
span.set_attribute("function.duration_ms", duration_ms)
|
387
407
|
span.set_attribute("function.success", True)
|
388
408
|
span.set_status(Status(StatusCode.OK))
|
@@ -415,7 +435,7 @@ class AzureLogger:
|
|
415
435
|
log_execution: bool,
|
416
436
|
is_async: bool,
|
417
437
|
):
|
418
|
-
"""
|
438
|
+
"""Handle failed function execution in tracing."""
|
419
439
|
span.set_status(Status(StatusCode.ERROR, str(e)))
|
420
440
|
span.record_exception(e)
|
421
441
|
span.set_attribute("function.duration_ms", duration_ms)
|
@@ -441,19 +461,20 @@ class AzureLogger:
|
|
441
461
|
def trace_function(
|
442
462
|
self,
|
443
463
|
function_name: Optional[str] = None,
|
444
|
-
log_args: bool = False,
|
445
|
-
log_result: bool = False,
|
446
464
|
log_execution: bool = True,
|
465
|
+
log_args: bool = True,
|
466
|
+
log_result: bool = False
|
447
467
|
) -> Callable:
|
448
|
-
"""
|
449
|
-
|
450
|
-
Supports both synchronous and asynchronous functions
|
468
|
+
"""Decorator for automatic function execution tracing.
|
469
|
+
|
470
|
+
Supports both synchronous and asynchronous functions with comprehensive
|
471
|
+
logging and OpenTelemetry span creation.
|
451
472
|
|
452
473
|
Args:
|
453
|
-
function_name: Custom name
|
474
|
+
function_name: Custom span name (defaults to function name)
|
475
|
+
log_execution: Whether to log execution metrics
|
454
476
|
log_args: Whether to log function arguments
|
455
477
|
log_result: Whether to log function result
|
456
|
-
log_execution: Whether to log execution metrics
|
457
478
|
"""
|
458
479
|
|
459
480
|
def decorator(func):
|
@@ -462,7 +483,7 @@ class AzureLogger:
|
|
462
483
|
span_name = function_name or f"{func.__module__}.{func.__name__}"
|
463
484
|
with self.tracer.start_as_current_span(span_name) as span:
|
464
485
|
self._setup_span_for_function_trace(
|
465
|
-
span, func, True, log_args, args, kwargs
|
486
|
+
span, func, True, log_args, args, kwargs, log_result, log_execution
|
466
487
|
)
|
467
488
|
start_time = time.time()
|
468
489
|
try:
|
@@ -495,7 +516,7 @@ class AzureLogger:
|
|
495
516
|
span_name = function_name or f"{func.__module__}.{func.__name__}"
|
496
517
|
with self.tracer.start_as_current_span(span_name) as span:
|
497
518
|
self._setup_span_for_function_trace(
|
498
|
-
span, func, False, log_args, args, kwargs
|
519
|
+
span, func, False, log_args, args, kwargs, log_result, log_execution
|
499
520
|
)
|
500
521
|
start_time = time.time()
|
501
522
|
try:
|
@@ -521,7 +542,7 @@ class AzureLogger:
|
|
521
542
|
)
|
522
543
|
raise
|
523
544
|
|
524
|
-
# Return
|
545
|
+
# Return appropriate wrapper based on function type
|
525
546
|
if asyncio.iscoroutinefunction(func):
|
526
547
|
return async_wrapper
|
527
548
|
return sync_wrapper
|
@@ -529,14 +550,14 @@ class AzureLogger:
|
|
529
550
|
return decorator
|
530
551
|
|
531
552
|
def add_span_attributes(self, attributes: Dict[str, Union[str, int, float, bool]]):
|
532
|
-
"""Add attributes to
|
553
|
+
"""Add attributes to current active span."""
|
533
554
|
current_span = trace.get_current_span()
|
534
555
|
if current_span and current_span.is_recording():
|
535
556
|
for key, value in attributes.items():
|
536
557
|
current_span.set_attribute(key, value)
|
537
558
|
|
538
559
|
def add_span_event(self, name: str, attributes: Optional[Dict[str, Any]] = None):
|
539
|
-
"""Add
|
560
|
+
"""Add event to current active span."""
|
540
561
|
current_span = trace.get_current_span()
|
541
562
|
if current_span and current_span.is_recording():
|
542
563
|
event_attributes = attributes or {}
|
@@ -547,7 +568,7 @@ class AzureLogger:
|
|
547
568
|
def set_span_status(
|
548
569
|
self, status_code: StatusCode, description: Optional[str] = None
|
549
570
|
):
|
550
|
-
"""Set
|
571
|
+
"""Set status of current active span."""
|
551
572
|
current_span = trace.get_current_span()
|
552
573
|
if current_span and current_span.is_recording():
|
553
574
|
current_span.set_status(Status(status_code, description))
|
@@ -560,15 +581,14 @@ class AzureLogger:
|
|
560
581
|
extra: Optional[Dict[str, Any]] = None,
|
561
582
|
span_attributes: Optional[Dict[str, Union[str, int, float, bool]]] = None,
|
562
583
|
):
|
563
|
-
"""
|
564
|
-
Log a message within a span context
|
584
|
+
"""Log message within a span context.
|
565
585
|
|
566
586
|
Args:
|
567
|
-
span_name: Name
|
587
|
+
span_name: Name for the span
|
568
588
|
message: Log message
|
569
|
-
level:
|
589
|
+
level: Python logging level
|
570
590
|
extra: Additional log properties
|
571
|
-
span_attributes: Attributes to add to
|
591
|
+
span_attributes: Attributes to add to span
|
572
592
|
"""
|
573
593
|
with self.tracer.start_as_current_span(span_name) as span:
|
574
594
|
if span_attributes:
|
@@ -586,14 +606,13 @@ class AzureLogger:
|
|
586
606
|
duration_ms: float,
|
587
607
|
extra: Optional[Dict[str, Any]] = None,
|
588
608
|
):
|
589
|
-
"""
|
590
|
-
Log external dependency calls (Database, HTTP, etc.)
|
609
|
+
"""Log external dependency calls for monitoring.
|
591
610
|
|
592
611
|
Args:
|
593
612
|
dependency_type: Type of dependency (SQL, HTTP, etc.)
|
594
|
-
name:
|
613
|
+
name: Dependency identifier
|
595
614
|
command: Command/query executed
|
596
|
-
success: Whether
|
615
|
+
success: Whether call was successful
|
597
616
|
duration_ms: Call duration in milliseconds
|
598
617
|
extra: Additional properties
|
599
618
|
"""
|
@@ -616,10 +635,9 @@ class AzureLogger:
|
|
616
635
|
self.logger.log(log_level, message, extra=self._enhance_extra(log_data))
|
617
636
|
|
618
637
|
def flush(self):
|
619
|
-
"""Flush
|
638
|
+
"""Flush pending telemetry data."""
|
620
639
|
if self._telemetry_enabled:
|
621
640
|
try:
|
622
|
-
# Force flush any pending telemetry
|
623
641
|
from opentelemetry.sdk.trace import TracerProvider
|
624
642
|
|
625
643
|
tracer_provider = trace.get_tracer_provider()
|
@@ -629,7 +647,7 @@ class AzureLogger:
|
|
629
647
|
self.warning(f"Failed to flush telemetry: {e}")
|
630
648
|
|
631
649
|
|
632
|
-
# Factory functions
|
650
|
+
# Factory functions with logger caching
|
633
651
|
_loggers: Dict[Any, "AzureLogger"] = {}
|
634
652
|
|
635
653
|
|
@@ -640,18 +658,20 @@ def create_app_logger(
|
|
640
658
|
log_level: int = logging.INFO,
|
641
659
|
enable_console_logging: bool = True,
|
642
660
|
custom_resource_attributes: Optional[Dict[str, str]] = None,
|
661
|
+
instrumentation_options: Optional[Dict[str, Any]] = None,
|
643
662
|
) -> AzureLogger:
|
644
|
-
"""
|
645
|
-
|
646
|
-
|
663
|
+
"""Create cached AzureLogger instance for applications.
|
664
|
+
|
665
|
+
Returns existing logger if one with same configuration exists.
|
647
666
|
|
648
667
|
Args:
|
649
|
-
service_name:
|
650
|
-
service_version:
|
668
|
+
service_name: Service identifier for telemetry
|
669
|
+
service_version: Service version for metadata
|
651
670
|
connection_string: Application Insights connection string
|
652
|
-
log_level:
|
671
|
+
log_level: Python logging level
|
653
672
|
enable_console_logging: Enable console output
|
654
|
-
custom_resource_attributes: Additional resource attributes
|
673
|
+
custom_resource_attributes: Additional OpenTelemetry resource attributes
|
674
|
+
instrumentation_options: Azure Monitor instrumentation options
|
655
675
|
|
656
676
|
Returns:
|
657
677
|
Configured AzureLogger instance
|
@@ -695,18 +715,19 @@ def create_function_logger(
|
|
695
715
|
function_name: str,
|
696
716
|
service_version: str = "1.0.0",
|
697
717
|
connection_string: Optional[str] = None,
|
718
|
+
instrumentation_options: Optional[Dict[str, Any]] = None,
|
698
719
|
) -> AzureLogger:
|
699
|
-
"""
|
700
|
-
Factory function specifically for Azure Functions
|
720
|
+
"""Create AzureLogger optimized for Azure Functions.
|
701
721
|
|
702
722
|
Args:
|
703
|
-
function_app_name:
|
704
|
-
function_name:
|
705
|
-
service_version:
|
723
|
+
function_app_name: Azure Function App name
|
724
|
+
function_name: Specific function name
|
725
|
+
service_version: Service version for metadata
|
706
726
|
connection_string: Application Insights connection string
|
727
|
+
instrumentation_options: Azure Monitor instrumentation options
|
707
728
|
|
708
729
|
Returns:
|
709
|
-
Configured AzureLogger
|
730
|
+
Configured AzureLogger with Azure Functions context
|
710
731
|
"""
|
711
732
|
custom_attributes = {
|
712
733
|
"azure.function.app": function_app_name,
|
@@ -719,4 +740,5 @@ def create_function_logger(
|
|
719
740
|
service_version=service_version,
|
720
741
|
connection_string=connection_string,
|
721
742
|
custom_resource_attributes=custom_attributes,
|
743
|
+
instrumentation_options=instrumentation_options,
|
722
744
|
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: azpaddypy
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.8
|
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
|
@@ -8,11 +8,10 @@ Requires-Python: >=3.11
|
|
8
8
|
Description-Content-Type: text/markdown
|
9
9
|
License-File: LICENSE
|
10
10
|
Requires-Dist: azure-monitor-opentelemetry==1.6.10
|
11
|
+
Requires-Dist: azure-functions==1.23.0
|
11
12
|
Provides-Extra: dev
|
12
|
-
Requires-Dist:
|
13
|
-
Requires-Dist:
|
14
|
-
Requires-Dist: pytest
|
15
|
-
Requires-Dist:
|
16
|
-
Requires-Dist: anyio>=3.7.1; extra == "dev"
|
17
|
-
Requires-Dist: pip; extra == "dev"
|
13
|
+
Requires-Dist: azure-functions==1.23.0; extra == "dev"
|
14
|
+
Requires-Dist: pytest==8.4.1; extra == "dev"
|
15
|
+
Requires-Dist: pytest-asyncio==1.0.0; extra == "dev"
|
16
|
+
Requires-Dist: anyio==4.9.0; extra == "dev"
|
18
17
|
Dynamic: license-file
|
@@ -0,0 +1,10 @@
|
|
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=War3h9Td7KZyeOsvOz3t2_ESNQzJ3jeZcrFL5octoGo,28081
|
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.2.8.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
|
7
|
+
azpaddypy-0.2.8.dist-info/METADATA,sha256=7zvYg76eo4n_0g_GFCqTwmC0pK4cgyVBrPuK4R52e6c,705
|
8
|
+
azpaddypy-0.2.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
9
|
+
azpaddypy-0.2.8.dist-info/top_level.txt,sha256=hsDuboDhT61320ML8X479ezSTwT3rrlDWz1_Z45B2cs,10
|
10
|
+
azpaddypy-0.2.8.dist-info/RECORD,,
|
azpaddypy-0.2.6.dist-info/RECORD
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
azpaddypy/mgmt/__init__.py,sha256=5-0eZuJMZlCONZNJ5hEvWXfvLIM36mg7FsEVs32bY_A,183
|
2
|
-
azpaddypy/mgmt/logging.py,sha256=7Ug99lg8gsvCGq0HukR0qWVl-_vacEq-poq2rUj24d4,26060
|
3
|
-
azpaddypy/test_function/__init__.py,sha256=0NjUl36wvUWV79GpRwBFkgkBaC6uDZsTdaSVOIHMFEU,3481
|
4
|
-
azpaddypy/test_function/function_app.py,sha256=6nX54-iq0L1l_hZpD6E744-j79oLxdaldFyWDCpwH7c,3867
|
5
|
-
azpaddypy-0.2.6.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
|
6
|
-
azpaddypy-0.2.6.dist-info/METADATA,sha256=jycbdYSHrsBp0riAHepPVTGZ-oQNMGWzngkhwC0A7vk,747
|
7
|
-
azpaddypy-0.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
8
|
-
azpaddypy-0.2.6.dist-info/top_level.txt,sha256=hsDuboDhT61320ML8X479ezSTwT3rrlDWz1_Z45B2cs,10
|
9
|
-
azpaddypy-0.2.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|