provide-foundation 0.0.0.dev2__py3-none-any.whl → 0.0.0.dev3__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.
- provide/foundation/__init__.py +20 -20
- provide/foundation/archive/__init__.py +1 -1
- provide/foundation/archive/base.py +15 -14
- provide/foundation/archive/bzip2.py +40 -40
- provide/foundation/archive/gzip.py +42 -42
- provide/foundation/archive/operations.py +90 -91
- provide/foundation/archive/tar.py +33 -31
- provide/foundation/archive/zip.py +52 -50
- provide/foundation/asynctools/__init__.py +20 -0
- provide/foundation/asynctools/core.py +126 -0
- provide/foundation/cli/__init__.py +2 -2
- provide/foundation/cli/commands/deps.py +4 -4
- provide/foundation/cli/commands/logs/__init__.py +2 -2
- provide/foundation/cli/commands/logs/generate.py +2 -2
- provide/foundation/cli/commands/logs/query.py +3 -3
- provide/foundation/cli/commands/logs/send.py +2 -2
- provide/foundation/cli/commands/logs/tail.py +2 -2
- provide/foundation/cli/decorators.py +0 -1
- provide/foundation/cli/testing.py +0 -5
- provide/foundation/cli/utils.py +1 -2
- provide/foundation/config/__init__.py +19 -19
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +81 -83
- provide/foundation/config/defaults.py +1 -1
- provide/foundation/config/env.py +2 -1
- provide/foundation/config/loader.py +1 -1
- provide/foundation/config/sync.py +8 -6
- provide/foundation/config/types.py +5 -5
- provide/foundation/config/validators.py +4 -4
- provide/foundation/console/output.py +7 -7
- provide/foundation/context/core.py +19 -17
- provide/foundation/crypto/certificates/__init__.py +9 -5
- provide/foundation/crypto/certificates/base.py +2 -2
- provide/foundation/crypto/certificates/certificate.py +48 -19
- provide/foundation/crypto/certificates/factory.py +26 -18
- provide/foundation/crypto/certificates/generator.py +24 -23
- provide/foundation/crypto/certificates/loader.py +24 -16
- provide/foundation/crypto/certificates/operations.py +17 -10
- provide/foundation/crypto/certificates/trust.py +21 -21
- provide/foundation/env/__init__.py +28 -0
- provide/foundation/env/core.py +218 -0
- provide/foundation/errors/__init__.py +3 -2
- provide/foundation/errors/decorators.py +0 -3
- provide/foundation/errors/types.py +0 -1
- provide/foundation/eventsets/display.py +13 -14
- provide/foundation/eventsets/registry.py +61 -31
- provide/foundation/eventsets/resolver.py +50 -46
- provide/foundation/eventsets/sets/das.py +8 -8
- provide/foundation/eventsets/sets/database.py +14 -14
- provide/foundation/eventsets/sets/http.py +21 -21
- provide/foundation/eventsets/sets/llm.py +16 -16
- provide/foundation/eventsets/sets/task_queue.py +13 -13
- provide/foundation/eventsets/types.py +7 -7
- provide/foundation/file/directory.py +1 -1
- provide/foundation/file/lock.py +2 -3
- provide/foundation/hub/components.py +19 -21
- provide/foundation/hub/config.py +25 -19
- provide/foundation/hub/discovery.py +5 -4
- provide/foundation/hub/handlers.py +13 -5
- provide/foundation/hub/lifecycle.py +10 -9
- provide/foundation/hub/manager.py +3 -0
- provide/foundation/hub/processors.py +8 -3
- provide/foundation/integrations/__init__.py +1 -1
- provide/foundation/integrations/openobserve/client.py +2 -2
- provide/foundation/integrations/openobserve/commands.py +9 -9
- provide/foundation/integrations/openobserve/config.py +2 -2
- provide/foundation/integrations/openobserve/otlp.py +2 -2
- provide/foundation/integrations/openobserve/search.py +1 -2
- provide/foundation/integrations/openobserve/streaming.py +1 -1
- provide/foundation/logger/__init__.py +0 -1
- provide/foundation/logger/config/base.py +1 -1
- provide/foundation/logger/config/logging.py +19 -19
- provide/foundation/logger/config/telemetry.py +11 -13
- provide/foundation/logger/factories.py +2 -2
- provide/foundation/logger/processors/main.py +12 -10
- provide/foundation/logger/ratelimit/limiters.py +4 -4
- provide/foundation/logger/ratelimit/processor.py +1 -1
- provide/foundation/logger/setup/coordinator.py +38 -24
- provide/foundation/logger/setup/processors.py +3 -3
- provide/foundation/logger/setup/testing.py +14 -0
- provide/foundation/logger/trace.py +5 -5
- provide/foundation/metrics/__init__.py +1 -1
- provide/foundation/metrics/otel.py +3 -1
- provide/foundation/observability/__init__.py +1 -1
- provide/foundation/process/__init__.py +1 -1
- provide/foundation/process/exit.py +6 -5
- provide/foundation/process/lifecycle.py +41 -18
- provide/foundation/resilience/__init__.py +6 -5
- provide/foundation/resilience/circuit.py +32 -30
- provide/foundation/resilience/decorators.py +58 -42
- provide/foundation/resilience/fallback.py +55 -40
- provide/foundation/resilience/retry.py +67 -65
- provide/foundation/serialization/__init__.py +16 -0
- provide/foundation/serialization/core.py +70 -0
- provide/foundation/streams/config.py +8 -9
- provide/foundation/streams/console.py +3 -3
- provide/foundation/streams/core.py +2 -2
- provide/foundation/streams/file.py +1 -1
- provide/foundation/testing/__init__.py +22 -7
- provide/foundation/testing/archive/__init__.py +7 -7
- provide/foundation/testing/archive/fixtures.py +58 -54
- provide/foundation/testing/cli.py +3 -6
- provide/foundation/testing/common/__init__.py +13 -13
- provide/foundation/testing/common/fixtures.py +27 -30
- provide/foundation/testing/file/__init__.py +15 -15
- provide/foundation/testing/file/content_fixtures.py +65 -92
- provide/foundation/testing/file/directory_fixtures.py +19 -19
- provide/foundation/testing/file/fixtures.py +14 -17
- provide/foundation/testing/file/special_fixtures.py +34 -42
- provide/foundation/testing/logger.py +28 -23
- provide/foundation/testing/mocking/__init__.py +21 -21
- provide/foundation/testing/mocking/fixtures.py +80 -67
- provide/foundation/testing/process/__init__.py +23 -23
- provide/foundation/testing/process/async_fixtures.py +89 -80
- provide/foundation/testing/process/fixtures.py +11 -13
- provide/foundation/testing/process/subprocess_fixtures.py +41 -40
- provide/foundation/testing/threading/__init__.py +17 -17
- provide/foundation/testing/threading/basic_fixtures.py +21 -17
- provide/foundation/testing/threading/data_fixtures.py +18 -16
- provide/foundation/testing/threading/execution_fixtures.py +67 -52
- provide/foundation/testing/threading/fixtures.py +10 -14
- provide/foundation/testing/threading/sync_fixtures.py +21 -18
- provide/foundation/testing/time/__init__.py +11 -11
- provide/foundation/testing/time/fixtures.py +91 -79
- provide/foundation/testing/transport/__init__.py +9 -9
- provide/foundation/testing/transport/fixtures.py +54 -54
- provide/foundation/time/__init__.py +18 -0
- provide/foundation/time/core.py +63 -0
- provide/foundation/tools/__init__.py +2 -2
- provide/foundation/tools/base.py +68 -67
- provide/foundation/tools/cache.py +62 -69
- provide/foundation/tools/downloader.py +51 -56
- provide/foundation/tools/installer.py +51 -57
- provide/foundation/tools/registry.py +38 -45
- provide/foundation/tools/resolver.py +70 -68
- provide/foundation/tools/verifier.py +39 -50
- provide/foundation/tracer/spans.py +1 -13
- provide/foundation/transport/__init__.py +26 -33
- provide/foundation/transport/base.py +32 -30
- provide/foundation/transport/client.py +44 -49
- provide/foundation/transport/config.py +11 -13
- provide/foundation/transport/errors.py +13 -27
- provide/foundation/transport/http.py +69 -55
- provide/foundation/transport/middleware.py +86 -81
- provide/foundation/transport/registry.py +29 -27
- provide/foundation/transport/types.py +6 -6
- provide/foundation/utils/deps.py +3 -2
- provide/foundation/utils/parsing.py +7 -7
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
- provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
- provide_foundation-0.0.0.dev2.dist-info/RECORD +0 -225
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/top_level.txt +0 -0
@@ -2,18 +2,19 @@
|
|
2
2
|
Transport middleware system with Hub registration.
|
3
3
|
"""
|
4
4
|
|
5
|
-
import asyncio
|
6
|
-
import time
|
7
5
|
from abc import ABC, abstractmethod
|
8
|
-
|
6
|
+
import time
|
9
7
|
|
10
8
|
from attrs import define, field
|
11
9
|
|
12
10
|
from provide.foundation.hub import get_component_registry
|
13
|
-
from provide.foundation.hub.components import ComponentCategory
|
14
11
|
from provide.foundation.logger import get_logger
|
15
12
|
from provide.foundation.metrics import counter, histogram
|
16
|
-
from provide.foundation.resilience.retry import
|
13
|
+
from provide.foundation.resilience.retry import (
|
14
|
+
BackoffStrategy,
|
15
|
+
RetryExecutor,
|
16
|
+
RetryPolicy,
|
17
|
+
)
|
17
18
|
from provide.foundation.transport.base import Request, Response
|
18
19
|
from provide.foundation.transport.errors import TransportError
|
19
20
|
|
@@ -22,17 +23,17 @@ log = get_logger(__name__)
|
|
22
23
|
|
23
24
|
class Middleware(ABC):
|
24
25
|
"""Abstract base class for transport middleware."""
|
25
|
-
|
26
|
+
|
26
27
|
@abstractmethod
|
27
28
|
async def process_request(self, request: Request) -> Request:
|
28
29
|
"""Process request before sending."""
|
29
30
|
pass
|
30
|
-
|
31
|
-
@abstractmethod
|
31
|
+
|
32
|
+
@abstractmethod
|
32
33
|
async def process_response(self, response: Response) -> Response:
|
33
34
|
"""Process response after receiving."""
|
34
35
|
pass
|
35
|
-
|
36
|
+
|
36
37
|
@abstractmethod
|
37
38
|
async def process_error(self, error: Exception, request: Request) -> Exception:
|
38
39
|
"""Process errors during request."""
|
@@ -42,11 +43,11 @@ class Middleware(ABC):
|
|
42
43
|
@define
|
43
44
|
class LoggingMiddleware(Middleware):
|
44
45
|
"""Built-in telemetry middleware using foundation.logger."""
|
45
|
-
|
46
|
+
|
46
47
|
log_requests: bool = field(default=True)
|
47
48
|
log_responses: bool = field(default=True)
|
48
49
|
log_bodies: bool = field(default=False)
|
49
|
-
|
50
|
+
|
50
51
|
async def process_request(self, request: Request) -> Request:
|
51
52
|
"""Log outgoing request."""
|
52
53
|
if self.log_requests:
|
@@ -54,14 +55,19 @@ class LoggingMiddleware(Middleware):
|
|
54
55
|
f"🚀 {request.method} {request.uri}",
|
55
56
|
method=request.method,
|
56
57
|
uri=str(request.uri),
|
57
|
-
headers=dict(request.headers) if hasattr(request,
|
58
|
+
headers=dict(request.headers) if hasattr(request, "headers") else {},
|
58
59
|
)
|
59
|
-
|
60
|
+
|
60
61
|
if self.log_bodies and request.body:
|
61
|
-
log.trace(
|
62
|
-
|
62
|
+
log.trace(
|
63
|
+
"Request body",
|
64
|
+
body=request.body,
|
65
|
+
method=request.method,
|
66
|
+
uri=str(request.uri),
|
67
|
+
)
|
68
|
+
|
63
69
|
return request
|
64
|
-
|
70
|
+
|
65
71
|
async def process_response(self, response: Response) -> Response:
|
66
72
|
"""Log incoming response."""
|
67
73
|
if self.log_responses:
|
@@ -72,20 +78,20 @@ class LoggingMiddleware(Middleware):
|
|
72
78
|
elapsed_ms=response.elapsed_ms,
|
73
79
|
method=response.request.method if response.request else None,
|
74
80
|
uri=str(response.request.uri) if response.request else None,
|
75
|
-
headers=dict(response.headers) if hasattr(response,
|
81
|
+
headers=dict(response.headers) if hasattr(response, "headers") else {},
|
76
82
|
)
|
77
|
-
|
83
|
+
|
78
84
|
if self.log_bodies and response.body:
|
79
85
|
log.trace(
|
80
|
-
"Response body",
|
86
|
+
"Response body",
|
81
87
|
body=response.text[:500], # Truncate large bodies
|
82
88
|
status_code=response.status,
|
83
89
|
method=response.request.method if response.request else None,
|
84
90
|
uri=str(response.request.uri) if response.request else None,
|
85
91
|
)
|
86
|
-
|
92
|
+
|
87
93
|
return response
|
88
|
-
|
94
|
+
|
89
95
|
async def process_error(self, error: Exception, request: Request) -> Exception:
|
90
96
|
"""Log errors."""
|
91
97
|
log.error(
|
@@ -96,13 +102,13 @@ class LoggingMiddleware(Middleware):
|
|
96
102
|
error_message=str(error),
|
97
103
|
)
|
98
104
|
return error
|
99
|
-
|
105
|
+
|
100
106
|
def _get_status_emoji(self, status_code: int) -> str:
|
101
107
|
"""Get emoji for status code."""
|
102
108
|
if 200 <= status_code < 300:
|
103
109
|
return "✅"
|
104
110
|
elif 300 <= status_code < 400:
|
105
|
-
return "↩️"
|
111
|
+
return "↩️"
|
106
112
|
elif 400 <= status_code < 500:
|
107
113
|
return "⚠️"
|
108
114
|
elif 500 <= status_code < 600:
|
@@ -114,7 +120,7 @@ class LoggingMiddleware(Middleware):
|
|
114
120
|
@define
|
115
121
|
class RetryMiddleware(Middleware):
|
116
122
|
"""Automatic retry middleware using unified retry logic."""
|
117
|
-
|
123
|
+
|
118
124
|
policy: RetryPolicy = field(
|
119
125
|
factory=lambda: RetryPolicy(
|
120
126
|
max_attempts=3,
|
@@ -124,33 +130,33 @@ class RetryMiddleware(Middleware):
|
|
124
130
|
retryable_status_codes={500, 502, 503, 504},
|
125
131
|
)
|
126
132
|
)
|
127
|
-
|
133
|
+
|
128
134
|
async def process_request(self, request: Request) -> Request:
|
129
135
|
"""No request processing needed."""
|
130
136
|
return request
|
131
|
-
|
137
|
+
|
132
138
|
async def process_response(self, response: Response) -> Response:
|
133
139
|
"""No response processing needed (retries handled in execute)."""
|
134
140
|
return response
|
135
|
-
|
141
|
+
|
136
142
|
async def process_error(self, error: Exception, request: Request) -> Exception:
|
137
143
|
"""Handle error, potentially with retries (this is called by client)."""
|
138
144
|
return error
|
139
|
-
|
145
|
+
|
140
146
|
async def execute_with_retry(self, execute_func, request: Request) -> Response:
|
141
147
|
"""Execute request with retry logic using unified RetryExecutor."""
|
142
148
|
executor = RetryExecutor(self.policy)
|
143
|
-
|
149
|
+
|
144
150
|
async def wrapped():
|
145
151
|
response = await execute_func(request)
|
146
|
-
|
152
|
+
|
147
153
|
# Check if status code is retryable
|
148
154
|
if self.policy.should_retry_response(response, attempt=1):
|
149
155
|
# Convert to exception for executor to handle
|
150
156
|
raise TransportError(f"Retryable HTTP status: {response.status}")
|
151
|
-
|
157
|
+
|
152
158
|
return response
|
153
|
-
|
159
|
+
|
154
160
|
try:
|
155
161
|
return await executor.execute_async(wrapped)
|
156
162
|
except TransportError as e:
|
@@ -165,76 +171,73 @@ class RetryMiddleware(Middleware):
|
|
165
171
|
@define
|
166
172
|
class MetricsMiddleware(Middleware):
|
167
173
|
"""Middleware for collecting transport metrics using foundation.metrics."""
|
168
|
-
|
174
|
+
|
169
175
|
# Create metrics instances
|
170
176
|
_request_counter = counter(
|
171
177
|
"transport_requests_total",
|
172
178
|
description="Total number of transport requests",
|
173
|
-
unit="requests"
|
179
|
+
unit="requests",
|
174
180
|
)
|
175
181
|
_request_duration = histogram(
|
176
|
-
"transport_request_duration_seconds",
|
182
|
+
"transport_request_duration_seconds",
|
177
183
|
description="Duration of transport requests",
|
178
|
-
unit="seconds"
|
184
|
+
unit="seconds",
|
179
185
|
)
|
180
186
|
_error_counter = counter(
|
181
187
|
"transport_errors_total",
|
182
|
-
description="Total number of transport errors",
|
183
|
-
unit="errors"
|
188
|
+
description="Total number of transport errors",
|
189
|
+
unit="errors",
|
184
190
|
)
|
185
|
-
|
191
|
+
|
186
192
|
async def process_request(self, request: Request) -> Request:
|
187
193
|
"""Record request start time."""
|
188
194
|
request.metadata["start_time"] = time.perf_counter()
|
189
195
|
return request
|
190
|
-
|
196
|
+
|
191
197
|
async def process_response(self, response: Response) -> Response:
|
192
198
|
"""Record response metrics."""
|
193
199
|
if response.request and "start_time" in response.request.metadata:
|
194
200
|
start_time = response.request.metadata["start_time"]
|
195
201
|
duration = time.perf_counter() - start_time
|
196
|
-
|
202
|
+
|
197
203
|
method = response.request.method
|
198
204
|
status_class = f"{response.status // 100}xx"
|
199
|
-
|
205
|
+
|
200
206
|
# Record metrics with labels
|
201
|
-
self._request_counter.inc(
|
207
|
+
self._request_counter.inc(
|
208
|
+
1,
|
202
209
|
method=method,
|
203
210
|
status_code=str(response.status),
|
204
|
-
status_class=status_class
|
211
|
+
status_class=status_class,
|
205
212
|
)
|
206
|
-
|
207
|
-
self._request_duration.observe(
|
208
|
-
method=method,
|
209
|
-
status_class=status_class
|
213
|
+
|
214
|
+
self._request_duration.observe(
|
215
|
+
duration, method=method, status_class=status_class
|
210
216
|
)
|
211
|
-
|
217
|
+
|
212
218
|
return response
|
213
|
-
|
219
|
+
|
214
220
|
async def process_error(self, error: Exception, request: Request) -> Exception:
|
215
221
|
"""Record error metrics."""
|
216
222
|
method = request.method
|
217
223
|
error_type = error.__class__.__name__
|
218
|
-
|
219
|
-
self._error_counter.inc(1,
|
220
|
-
|
221
|
-
error_type=error_type
|
222
|
-
)
|
223
|
-
|
224
|
+
|
225
|
+
self._error_counter.inc(1, method=method, error_type=error_type)
|
226
|
+
|
224
227
|
return error
|
225
228
|
|
226
229
|
|
227
230
|
@define
|
228
231
|
class MiddlewarePipeline:
|
229
232
|
"""Pipeline for executing middleware in order."""
|
230
|
-
|
233
|
+
|
231
234
|
middleware: list[Middleware] = field(factory=list)
|
232
|
-
|
235
|
+
|
233
236
|
def add(self, middleware: Middleware) -> None:
|
234
237
|
"""Add middleware to the pipeline."""
|
235
238
|
self.middleware.append(middleware)
|
236
239
|
log.trace(f"Added middleware: {middleware.__class__.__name__}")
|
237
|
-
|
240
|
+
|
238
241
|
def remove(self, middleware_class: type[Middleware]) -> bool:
|
239
242
|
"""Remove middleware by class type."""
|
240
243
|
for i, mw in enumerate(self.middleware):
|
@@ -243,19 +246,19 @@ class MiddlewarePipeline:
|
|
243
246
|
log.trace(f"Removed middleware: {middleware_class.__name__}")
|
244
247
|
return True
|
245
248
|
return False
|
246
|
-
|
249
|
+
|
247
250
|
async def process_request(self, request: Request) -> Request:
|
248
251
|
"""Process request through all middleware."""
|
249
252
|
for mw in self.middleware:
|
250
253
|
request = await mw.process_request(request)
|
251
254
|
return request
|
252
|
-
|
255
|
+
|
253
256
|
async def process_response(self, response: Response) -> Response:
|
254
257
|
"""Process response through all middleware (in reverse order)."""
|
255
258
|
for mw in reversed(self.middleware):
|
256
259
|
response = await mw.process_response(response)
|
257
260
|
return response
|
258
|
-
|
261
|
+
|
259
262
|
async def process_error(self, error: Exception, request: Request) -> Exception:
|
260
263
|
"""Process error through all middleware."""
|
261
264
|
for mw in self.middleware:
|
@@ -267,11 +270,11 @@ def register_middleware(
|
|
267
270
|
name: str,
|
268
271
|
middleware_class: type[Middleware],
|
269
272
|
category: str = "transport.middleware",
|
270
|
-
**metadata
|
273
|
+
**metadata,
|
271
274
|
) -> None:
|
272
275
|
"""Register middleware in the Hub."""
|
273
276
|
registry = get_component_registry()
|
274
|
-
|
277
|
+
|
275
278
|
registry.register(
|
276
279
|
name=name,
|
277
280
|
value=middleware_class,
|
@@ -280,24 +283,26 @@ def register_middleware(
|
|
280
283
|
"category": category,
|
281
284
|
"priority": metadata.get("priority", 100),
|
282
285
|
"class_name": middleware_class.__name__,
|
283
|
-
**metadata
|
286
|
+
**metadata,
|
284
287
|
},
|
285
288
|
replace=True,
|
286
289
|
)
|
287
|
-
|
290
|
+
|
288
291
|
log.debug(f"Registered middleware {middleware_class.__name__} as '{name}'")
|
289
292
|
|
290
293
|
|
291
|
-
def get_middleware_by_category(
|
294
|
+
def get_middleware_by_category(
|
295
|
+
category: str = "transport.middleware",
|
296
|
+
) -> list[type[Middleware]]:
|
292
297
|
"""Get all middleware for a category, sorted by priority."""
|
293
298
|
registry = get_component_registry()
|
294
299
|
middleware = []
|
295
|
-
|
300
|
+
|
296
301
|
for entry in registry:
|
297
302
|
if entry.dimension == category:
|
298
303
|
priority = entry.metadata.get("priority", 100)
|
299
304
|
middleware.append((entry.value, priority))
|
300
|
-
|
305
|
+
|
301
306
|
# Sort by priority (lower numbers = higher priority)
|
302
307
|
middleware.sort(key=lambda x: x[1])
|
303
308
|
return [mw[0] for mw in middleware]
|
@@ -306,11 +311,11 @@ def get_middleware_by_category(category: str = "transport.middleware") -> list[t
|
|
306
311
|
def create_default_pipeline() -> MiddlewarePipeline:
|
307
312
|
"""Create pipeline with default middleware."""
|
308
313
|
pipeline = MiddlewarePipeline()
|
309
|
-
|
314
|
+
|
310
315
|
# Add built-in middleware
|
311
316
|
pipeline.add(LoggingMiddleware())
|
312
317
|
pipeline.add(MetricsMiddleware())
|
313
|
-
|
318
|
+
|
314
319
|
return pipeline
|
315
320
|
|
316
321
|
|
@@ -324,21 +329,21 @@ def _register_builtin_middleware():
|
|
324
329
|
description="Built-in request/response logging",
|
325
330
|
priority=10,
|
326
331
|
)
|
327
|
-
|
332
|
+
|
328
333
|
register_middleware(
|
329
|
-
"retry",
|
334
|
+
"retry",
|
330
335
|
RetryMiddleware,
|
331
336
|
description="Automatic retry with exponential backoff",
|
332
337
|
priority=20,
|
333
338
|
)
|
334
|
-
|
339
|
+
|
335
340
|
register_middleware(
|
336
341
|
"metrics",
|
337
|
-
MetricsMiddleware,
|
342
|
+
MetricsMiddleware,
|
338
343
|
description="Request/response metrics collection",
|
339
344
|
priority=30,
|
340
345
|
)
|
341
|
-
|
346
|
+
|
342
347
|
except ImportError:
|
343
348
|
# Registry not available yet
|
344
349
|
pass
|
@@ -349,12 +354,12 @@ _register_builtin_middleware()
|
|
349
354
|
|
350
355
|
|
351
356
|
__all__ = [
|
352
|
-
"Middleware",
|
353
357
|
"LoggingMiddleware",
|
354
|
-
"RetryMiddleware",
|
355
358
|
"MetricsMiddleware",
|
359
|
+
"Middleware",
|
356
360
|
"MiddlewarePipeline",
|
357
|
-
"
|
358
|
-
"get_middleware_by_category",
|
361
|
+
"RetryMiddleware",
|
359
362
|
"create_default_pipeline",
|
360
|
-
|
363
|
+
"get_middleware_by_category",
|
364
|
+
"register_middleware",
|
365
|
+
]
|
@@ -18,11 +18,11 @@ def register_transport(
|
|
18
18
|
transport_type: TransportType,
|
19
19
|
transport_class: type[Transport],
|
20
20
|
schemes: list[str] | None = None,
|
21
|
-
**metadata
|
21
|
+
**metadata,
|
22
22
|
) -> None:
|
23
23
|
"""
|
24
24
|
Register a transport implementation in the Hub.
|
25
|
-
|
25
|
+
|
26
26
|
Args:
|
27
27
|
transport_type: The primary transport type
|
28
28
|
transport_class: Transport implementation class
|
@@ -30,11 +30,11 @@ def register_transport(
|
|
30
30
|
**metadata: Additional metadata for the transport
|
31
31
|
"""
|
32
32
|
registry = get_component_registry()
|
33
|
-
|
33
|
+
|
34
34
|
# Default schemes to just the transport type
|
35
35
|
if schemes is None:
|
36
36
|
schemes = [transport_type.value]
|
37
|
-
|
37
|
+
|
38
38
|
registry.register(
|
39
39
|
name=transport_type.value,
|
40
40
|
value=transport_class,
|
@@ -43,37 +43,39 @@ def register_transport(
|
|
43
43
|
"transport_type": transport_type,
|
44
44
|
"schemes": schemes,
|
45
45
|
"class_name": transport_class.__name__,
|
46
|
-
**metadata
|
46
|
+
**metadata,
|
47
47
|
},
|
48
48
|
replace=True, # Allow re-registration
|
49
49
|
)
|
50
|
-
|
50
|
+
|
51
51
|
log.debug(f"Registered transport {transport_class.__name__} for schemes: {schemes}")
|
52
52
|
|
53
53
|
|
54
54
|
def get_transport_for_scheme(scheme: str) -> type[Transport]:
|
55
55
|
"""
|
56
56
|
Get transport class for a URI scheme.
|
57
|
-
|
57
|
+
|
58
58
|
Args:
|
59
59
|
scheme: URI scheme (e.g., 'http', 'https', 'ws')
|
60
|
-
|
60
|
+
|
61
61
|
Returns:
|
62
62
|
Transport class that handles the scheme
|
63
|
-
|
63
|
+
|
64
64
|
Raises:
|
65
65
|
TransportNotFoundError: If no transport is registered for the scheme
|
66
66
|
"""
|
67
67
|
registry = get_component_registry()
|
68
|
-
|
68
|
+
|
69
69
|
# Search through registered transports
|
70
70
|
for entry in registry:
|
71
71
|
if entry.dimension == ComponentCategory.TRANSPORT.value:
|
72
72
|
schemes = entry.metadata.get("schemes", [])
|
73
73
|
if scheme.lower() in schemes:
|
74
|
-
log.trace(
|
74
|
+
log.trace(
|
75
|
+
f"Found transport {entry.value.__name__} for scheme '{scheme}'"
|
76
|
+
)
|
75
77
|
return entry.value
|
76
|
-
|
78
|
+
|
77
79
|
raise TransportNotFoundError(
|
78
80
|
f"No transport registered for scheme: {scheme}",
|
79
81
|
scheme=scheme,
|
@@ -83,13 +85,13 @@ def get_transport_for_scheme(scheme: str) -> type[Transport]:
|
|
83
85
|
def get_transport(uri: str) -> Transport:
|
84
86
|
"""
|
85
87
|
Get transport instance for a URI.
|
86
|
-
|
88
|
+
|
87
89
|
Args:
|
88
90
|
uri: Full URI to get transport for
|
89
|
-
|
91
|
+
|
90
92
|
Returns:
|
91
93
|
Transport instance ready to use
|
92
|
-
|
94
|
+
|
93
95
|
Raises:
|
94
96
|
TransportNotFoundError: If no transport supports the URI scheme
|
95
97
|
"""
|
@@ -101,13 +103,13 @@ def get_transport(uri: str) -> Transport:
|
|
101
103
|
def list_registered_transports() -> dict[str, dict[str, Any]]:
|
102
104
|
"""
|
103
105
|
List all registered transports.
|
104
|
-
|
106
|
+
|
105
107
|
Returns:
|
106
108
|
Dictionary mapping transport names to their info
|
107
109
|
"""
|
108
110
|
registry = get_component_registry()
|
109
111
|
transports = {}
|
110
|
-
|
112
|
+
|
111
113
|
for entry in registry:
|
112
114
|
if entry.dimension == ComponentCategory.TRANSPORT.value:
|
113
115
|
transports[entry.name] = {
|
@@ -116,22 +118,22 @@ def list_registered_transports() -> dict[str, dict[str, Any]]:
|
|
116
118
|
"transport_type": entry.metadata.get("transport_type"),
|
117
119
|
"metadata": entry.metadata,
|
118
120
|
}
|
119
|
-
|
121
|
+
|
120
122
|
return transports
|
121
123
|
|
122
124
|
|
123
125
|
def get_transport_info(scheme_or_name: str) -> dict[str, Any] | None:
|
124
126
|
"""
|
125
127
|
Get detailed information about a transport.
|
126
|
-
|
128
|
+
|
127
129
|
Args:
|
128
130
|
scheme_or_name: URI scheme or transport name
|
129
|
-
|
131
|
+
|
130
132
|
Returns:
|
131
133
|
Transport information or None if not found
|
132
134
|
"""
|
133
135
|
registry = get_component_registry()
|
134
|
-
|
136
|
+
|
135
137
|
for entry in registry:
|
136
138
|
if entry.dimension == ComponentCategory.TRANSPORT.value:
|
137
139
|
# Check if it matches by name
|
@@ -143,7 +145,7 @@ def get_transport_info(scheme_or_name: str) -> dict[str, Any] | None:
|
|
143
145
|
"transport_type": entry.metadata.get("transport_type"),
|
144
146
|
"metadata": entry.metadata,
|
145
147
|
}
|
146
|
-
|
148
|
+
|
147
149
|
# Check if it matches by scheme
|
148
150
|
schemes = entry.metadata.get("schemes", [])
|
149
151
|
if scheme_or_name.lower() in schemes:
|
@@ -154,14 +156,14 @@ def get_transport_info(scheme_or_name: str) -> dict[str, Any] | None:
|
|
154
156
|
"transport_type": entry.metadata.get("transport_type"),
|
155
157
|
"metadata": entry.metadata,
|
156
158
|
}
|
157
|
-
|
159
|
+
|
158
160
|
return None
|
159
161
|
|
160
162
|
|
161
163
|
__all__ = [
|
162
|
-
"register_transport",
|
163
|
-
"get_transport_for_scheme",
|
164
164
|
"get_transport",
|
165
|
-
"
|
165
|
+
"get_transport_for_scheme",
|
166
166
|
"get_transport_info",
|
167
|
-
|
167
|
+
"list_registered_transports",
|
168
|
+
"register_transport",
|
169
|
+
]
|
@@ -13,7 +13,7 @@ Data: TypeAlias = dict[str, Any] | bytes | str | None
|
|
13
13
|
|
14
14
|
class TransportType(str, Enum):
|
15
15
|
"""Supported transport types."""
|
16
|
-
|
16
|
+
|
17
17
|
HTTP = "http"
|
18
18
|
HTTPS = "https"
|
19
19
|
WS = "ws"
|
@@ -26,7 +26,7 @@ class TransportType(str, Enum):
|
|
26
26
|
|
27
27
|
class HTTPMethod(str, Enum):
|
28
28
|
"""HTTP methods."""
|
29
|
-
|
29
|
+
|
30
30
|
GET = "GET"
|
31
31
|
POST = "POST"
|
32
32
|
PUT = "PUT"
|
@@ -37,9 +37,9 @@ class HTTPMethod(str, Enum):
|
|
37
37
|
|
38
38
|
|
39
39
|
__all__ = [
|
40
|
-
"Headers",
|
41
|
-
"Params",
|
42
40
|
"Data",
|
43
|
-
"TransportType",
|
44
41
|
"HTTPMethod",
|
45
|
-
|
42
|
+
"Headers",
|
43
|
+
"Params",
|
44
|
+
"TransportType",
|
45
|
+
]
|
provide/foundation/utils/deps.py
CHANGED
@@ -6,6 +6,7 @@ from typing import NamedTuple
|
|
6
6
|
def _get_logger():
|
7
7
|
"""Lazy logger import to avoid circular dependencies."""
|
8
8
|
from provide.foundation.logger import get_logger
|
9
|
+
|
9
10
|
return get_logger(__name__)
|
10
11
|
|
11
12
|
|
@@ -21,7 +22,7 @@ class DependencyStatus(NamedTuple):
|
|
21
22
|
def _check_click() -> DependencyStatus:
|
22
23
|
"""Check click availability."""
|
23
24
|
try:
|
24
|
-
import click
|
25
|
+
import click # noqa: F401
|
25
26
|
|
26
27
|
# Use importlib.metadata to avoid deprecation warning
|
27
28
|
try:
|
@@ -68,7 +69,7 @@ def _check_cryptography() -> DependencyStatus:
|
|
68
69
|
def _check_opentelemetry() -> DependencyStatus:
|
69
70
|
"""Check OpenTelemetry availability."""
|
70
71
|
try:
|
71
|
-
import opentelemetry
|
72
|
+
import opentelemetry # noqa: F401
|
72
73
|
|
73
74
|
try:
|
74
75
|
from importlib.metadata import version
|
@@ -222,11 +222,11 @@ def auto_parse(attr: Any, value: str) -> Any:
|
|
222
222
|
'HELLO'
|
223
223
|
"""
|
224
224
|
# Check for attrs field converter first
|
225
|
-
if hasattr(attr,
|
225
|
+
if hasattr(attr, "converter") and attr.converter is not None:
|
226
226
|
try:
|
227
227
|
result = attr.converter(value)
|
228
228
|
# Check if result is a Mock object (test scenario)
|
229
|
-
if hasattr(result,
|
229
|
+
if hasattr(result, "_mock_name") or str(type(result)).find("Mock") >= 0:
|
230
230
|
# It's a Mock, fall back to type-based parsing
|
231
231
|
pass
|
232
232
|
else:
|
@@ -234,15 +234,15 @@ def auto_parse(attr: Any, value: str) -> Any:
|
|
234
234
|
except Exception:
|
235
235
|
# If converter fails, fall back to type-based parsing
|
236
236
|
pass
|
237
|
-
|
237
|
+
|
238
238
|
# Check for converter in metadata as fallback
|
239
|
-
if hasattr(attr,
|
240
|
-
converter = attr.metadata.get(
|
239
|
+
if hasattr(attr, "metadata") and attr.metadata:
|
240
|
+
converter = attr.metadata.get("converter")
|
241
241
|
if converter and callable(converter):
|
242
242
|
try:
|
243
243
|
result = converter(value)
|
244
244
|
# Check if result is a Mock object (test scenario)
|
245
|
-
if hasattr(result,
|
245
|
+
if hasattr(result, "_mock_name") or str(type(result)).find("Mock") >= 0:
|
246
246
|
# It's a Mock, fall back to type-based parsing
|
247
247
|
pass
|
248
248
|
else:
|
@@ -250,7 +250,7 @@ def auto_parse(attr: Any, value: str) -> Any:
|
|
250
250
|
except Exception:
|
251
251
|
# If converter fails, fall back to type-based parsing
|
252
252
|
pass
|
253
|
-
|
253
|
+
|
254
254
|
# Get type hint from attrs field
|
255
255
|
if hasattr(attr, "type") and attr.type is not None:
|
256
256
|
field_type = attr.type
|
{provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: provide-foundation
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev3
|
4
4
|
Summary: Foundation Telemetry: An opinionated, developer-friendly telemetry wrapper for Python.
|
5
5
|
Author-email: Tim Perkins <code@tim.life>
|
6
6
|
Maintainer-email: "provide.io" <code@provide.io>
|
@@ -96,7 +96,7 @@ provide.foundation has optional feature sets that require additional dependencie
|
|
96
96
|
### Core Components
|
97
97
|
|
98
98
|
#### **Structured Logging**
|
99
|
-
Beautiful, performant logging built on `structlog` with
|
99
|
+
Beautiful, performant logging built on `structlog` with event-enriched structured logging and zero configuration required.
|
100
100
|
|
101
101
|
```python
|
102
102
|
# Simple usage - works immediately with base install
|