provide-foundation 0.0.0.dev1__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 +36 -10
- 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 +93 -96
- 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 +15 -9
- provide/foundation/cli/commands/logs/__init__.py +3 -3
- provide/foundation/cli/commands/logs/generate.py +2 -2
- provide/foundation/cli/commands/logs/query.py +4 -4
- provide/foundation/cli/commands/logs/send.py +3 -3
- provide/foundation/cli/commands/logs/tail.py +3 -3
- provide/foundation/cli/decorators.py +11 -11
- provide/foundation/cli/main.py +1 -1
- provide/foundation/cli/testing.py +2 -40
- provide/foundation/cli/utils.py +21 -18
- provide/foundation/config/__init__.py +35 -2
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +477 -0
- provide/foundation/config/defaults.py +67 -0
- provide/foundation/config/env.py +6 -20
- provide/foundation/config/loader.py +10 -4
- provide/foundation/config/sync.py +8 -6
- provide/foundation/config/types.py +5 -5
- provide/foundation/config/validators.py +4 -4
- provide/foundation/console/input.py +5 -5
- provide/foundation/console/output.py +36 -14
- provide/foundation/context/__init__.py +8 -4
- provide/foundation/context/core.py +88 -110
- 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 -3
- provide/foundation/errors/decorators.py +0 -234
- provide/foundation/errors/types.py +0 -98
- 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 +14 -23
- provide/foundation/file/lock.py +4 -3
- provide/foundation/hub/components.py +75 -389
- provide/foundation/hub/config.py +157 -0
- provide/foundation/hub/discovery.py +63 -0
- provide/foundation/hub/handlers.py +89 -0
- provide/foundation/hub/lifecycle.py +195 -0
- provide/foundation/hub/manager.py +7 -4
- provide/foundation/hub/processors.py +49 -0
- provide/foundation/integrations/__init__.py +11 -0
- provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
- provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/client.py +14 -14
- provide/foundation/{observability → integrations}/openobserve/commands.py +12 -12
- provide/foundation/integrations/openobserve/config.py +37 -0
- provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/otlp.py +2 -2
- provide/foundation/{observability → integrations}/openobserve/search.py +2 -3
- provide/foundation/{observability → integrations}/openobserve/streaming.py +5 -5
- provide/foundation/logger/__init__.py +0 -1
- provide/foundation/logger/config/base.py +1 -1
- provide/foundation/logger/config/logging.py +69 -299
- provide/foundation/logger/config/telemetry.py +39 -121
- 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 +39 -25
- 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 +3 -3
- provide/foundation/process/__init__.py +9 -0
- provide/foundation/process/exit.py +48 -0
- provide/foundation/process/lifecycle.py +69 -46
- provide/foundation/resilience/__init__.py +36 -0
- provide/foundation/resilience/circuit.py +166 -0
- provide/foundation/resilience/decorators.py +236 -0
- provide/foundation/resilience/fallback.py +208 -0
- provide/foundation/resilience/retry.py +327 -0
- provide/foundation/serialization/__init__.py +16 -0
- provide/foundation/serialization/core.py +70 -0
- provide/foundation/streams/config.py +78 -0
- provide/foundation/streams/console.py +4 -5
- provide/foundation/streams/core.py +5 -2
- provide/foundation/streams/file.py +12 -2
- provide/foundation/testing/__init__.py +29 -9
- provide/foundation/testing/archive/__init__.py +7 -7
- provide/foundation/testing/archive/fixtures.py +58 -54
- provide/foundation/testing/cli.py +30 -20
- provide/foundation/testing/common/__init__.py +13 -15
- provide/foundation/testing/common/fixtures.py +27 -57
- provide/foundation/testing/file/__init__.py +15 -15
- provide/foundation/testing/file/content_fixtures.py +289 -0
- provide/foundation/testing/file/directory_fixtures.py +107 -0
- provide/foundation/testing/file/fixtures.py +42 -516
- provide/foundation/testing/file/special_fixtures.py +145 -0
- provide/foundation/testing/logger.py +89 -8
- 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 +414 -0
- provide/foundation/testing/process/fixtures.py +48 -571
- provide/foundation/testing/process/subprocess_fixtures.py +210 -0
- provide/foundation/testing/threading/__init__.py +17 -17
- provide/foundation/testing/threading/basic_fixtures.py +105 -0
- provide/foundation/testing/threading/data_fixtures.py +101 -0
- provide/foundation/testing/threading/execution_fixtures.py +278 -0
- provide/foundation/testing/threading/fixtures.py +32 -502
- provide/foundation/testing/threading/sync_fixtures.py +100 -0
- provide/foundation/testing/time/__init__.py +11 -11
- provide/foundation/testing/time/fixtures.py +95 -83
- 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 +69 -74
- provide/foundation/tools/downloader.py +68 -62
- 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 +2 -14
- 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 +36 -107
- provide/foundation/transport/errors.py +13 -27
- provide/foundation/transport/http.py +69 -55
- provide/foundation/transport/middleware.py +113 -114
- provide/foundation/transport/registry.py +29 -27
- provide/foundation/transport/types.py +6 -6
- provide/foundation/utils/deps.py +17 -14
- provide/foundation/utils/parsing.py +49 -4
- {provide_foundation-0.0.0.dev1.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.dev1.dist-info/RECORD +0 -200
- /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
- /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/top_level.txt +0 -0
@@ -2,17 +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
|
13
|
+
from provide.foundation.resilience.retry import (
|
14
|
+
BackoffStrategy,
|
15
|
+
RetryExecutor,
|
16
|
+
RetryPolicy,
|
17
|
+
)
|
16
18
|
from provide.foundation.transport.base import Request, Response
|
17
19
|
from provide.foundation.transport.errors import TransportError
|
18
20
|
|
@@ -21,17 +23,17 @@ log = get_logger(__name__)
|
|
21
23
|
|
22
24
|
class Middleware(ABC):
|
23
25
|
"""Abstract base class for transport middleware."""
|
24
|
-
|
26
|
+
|
25
27
|
@abstractmethod
|
26
28
|
async def process_request(self, request: Request) -> Request:
|
27
29
|
"""Process request before sending."""
|
28
30
|
pass
|
29
|
-
|
30
|
-
@abstractmethod
|
31
|
+
|
32
|
+
@abstractmethod
|
31
33
|
async def process_response(self, response: Response) -> Response:
|
32
34
|
"""Process response after receiving."""
|
33
35
|
pass
|
34
|
-
|
36
|
+
|
35
37
|
@abstractmethod
|
36
38
|
async def process_error(self, error: Exception, request: Request) -> Exception:
|
37
39
|
"""Process errors during request."""
|
@@ -41,11 +43,11 @@ class Middleware(ABC):
|
|
41
43
|
@define
|
42
44
|
class LoggingMiddleware(Middleware):
|
43
45
|
"""Built-in telemetry middleware using foundation.logger."""
|
44
|
-
|
46
|
+
|
45
47
|
log_requests: bool = field(default=True)
|
46
48
|
log_responses: bool = field(default=True)
|
47
49
|
log_bodies: bool = field(default=False)
|
48
|
-
|
50
|
+
|
49
51
|
async def process_request(self, request: Request) -> Request:
|
50
52
|
"""Log outgoing request."""
|
51
53
|
if self.log_requests:
|
@@ -53,14 +55,19 @@ class LoggingMiddleware(Middleware):
|
|
53
55
|
f"🚀 {request.method} {request.uri}",
|
54
56
|
method=request.method,
|
55
57
|
uri=str(request.uri),
|
56
|
-
headers=dict(request.headers) if hasattr(request,
|
58
|
+
headers=dict(request.headers) if hasattr(request, "headers") else {},
|
57
59
|
)
|
58
|
-
|
60
|
+
|
59
61
|
if self.log_bodies and request.body:
|
60
|
-
log.trace(
|
61
|
-
|
62
|
+
log.trace(
|
63
|
+
"Request body",
|
64
|
+
body=request.body,
|
65
|
+
method=request.method,
|
66
|
+
uri=str(request.uri),
|
67
|
+
)
|
68
|
+
|
62
69
|
return request
|
63
|
-
|
70
|
+
|
64
71
|
async def process_response(self, response: Response) -> Response:
|
65
72
|
"""Log incoming response."""
|
66
73
|
if self.log_responses:
|
@@ -71,20 +78,20 @@ class LoggingMiddleware(Middleware):
|
|
71
78
|
elapsed_ms=response.elapsed_ms,
|
72
79
|
method=response.request.method if response.request else None,
|
73
80
|
uri=str(response.request.uri) if response.request else None,
|
74
|
-
headers=dict(response.headers) if hasattr(response,
|
81
|
+
headers=dict(response.headers) if hasattr(response, "headers") else {},
|
75
82
|
)
|
76
|
-
|
83
|
+
|
77
84
|
if self.log_bodies and response.body:
|
78
85
|
log.trace(
|
79
|
-
"Response body",
|
86
|
+
"Response body",
|
80
87
|
body=response.text[:500], # Truncate large bodies
|
81
88
|
status_code=response.status,
|
82
89
|
method=response.request.method if response.request else None,
|
83
90
|
uri=str(response.request.uri) if response.request else None,
|
84
91
|
)
|
85
|
-
|
92
|
+
|
86
93
|
return response
|
87
|
-
|
94
|
+
|
88
95
|
async def process_error(self, error: Exception, request: Request) -> Exception:
|
89
96
|
"""Log errors."""
|
90
97
|
log.error(
|
@@ -95,13 +102,13 @@ class LoggingMiddleware(Middleware):
|
|
95
102
|
error_message=str(error),
|
96
103
|
)
|
97
104
|
return error
|
98
|
-
|
105
|
+
|
99
106
|
def _get_status_emoji(self, status_code: int) -> str:
|
100
107
|
"""Get emoji for status code."""
|
101
108
|
if 200 <= status_code < 300:
|
102
109
|
return "✅"
|
103
110
|
elif 300 <= status_code < 400:
|
104
|
-
return "↩️"
|
111
|
+
return "↩️"
|
105
112
|
elif 400 <= status_code < 500:
|
106
113
|
return "⚠️"
|
107
114
|
elif 500 <= status_code < 600:
|
@@ -112,135 +119,125 @@ class LoggingMiddleware(Middleware):
|
|
112
119
|
|
113
120
|
@define
|
114
121
|
class RetryMiddleware(Middleware):
|
115
|
-
"""Automatic retry middleware
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
+
"""Automatic retry middleware using unified retry logic."""
|
123
|
+
|
124
|
+
policy: RetryPolicy = field(
|
125
|
+
factory=lambda: RetryPolicy(
|
126
|
+
max_attempts=3,
|
127
|
+
base_delay=0.5,
|
128
|
+
backoff=BackoffStrategy.EXPONENTIAL,
|
129
|
+
retryable_errors=(TransportError,),
|
130
|
+
retryable_status_codes={500, 502, 503, 504},
|
131
|
+
)
|
122
132
|
)
|
123
|
-
|
133
|
+
|
124
134
|
async def process_request(self, request: Request) -> Request:
|
125
135
|
"""No request processing needed."""
|
126
136
|
return request
|
127
|
-
|
137
|
+
|
128
138
|
async def process_response(self, response: Response) -> Response:
|
129
139
|
"""No response processing needed (retries handled in execute)."""
|
130
140
|
return response
|
131
|
-
|
141
|
+
|
132
142
|
async def process_error(self, error: Exception, request: Request) -> Exception:
|
133
143
|
"""Handle error, potentially with retries (this is called by client)."""
|
134
144
|
return error
|
135
|
-
|
145
|
+
|
136
146
|
async def execute_with_retry(self, execute_func, request: Request) -> Response:
|
137
|
-
"""Execute request with retry logic."""
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
await asyncio.sleep(wait_time)
|
160
|
-
else:
|
161
|
-
break
|
162
|
-
|
163
|
-
# All retries exhausted
|
164
|
-
if last_exception:
|
165
|
-
raise last_exception
|
166
|
-
else:
|
167
|
-
# This shouldn't happen, but just in case
|
168
|
-
raise TransportError("Max retries exceeded")
|
147
|
+
"""Execute request with retry logic using unified RetryExecutor."""
|
148
|
+
executor = RetryExecutor(self.policy)
|
149
|
+
|
150
|
+
async def wrapped():
|
151
|
+
response = await execute_func(request)
|
152
|
+
|
153
|
+
# Check if status code is retryable
|
154
|
+
if self.policy.should_retry_response(response, attempt=1):
|
155
|
+
# Convert to exception for executor to handle
|
156
|
+
raise TransportError(f"Retryable HTTP status: {response.status}")
|
157
|
+
|
158
|
+
return response
|
159
|
+
|
160
|
+
try:
|
161
|
+
return await executor.execute_async(wrapped)
|
162
|
+
except TransportError as e:
|
163
|
+
# If it's our synthetic error, extract the response
|
164
|
+
if "Retryable HTTP status" in str(e):
|
165
|
+
# The last response will be returned
|
166
|
+
# For now, re-raise as this needs more sophisticated handling
|
167
|
+
raise
|
168
|
+
raise
|
169
169
|
|
170
170
|
|
171
171
|
@define
|
172
172
|
class MetricsMiddleware(Middleware):
|
173
173
|
"""Middleware for collecting transport metrics using foundation.metrics."""
|
174
|
-
|
174
|
+
|
175
175
|
# Create metrics instances
|
176
176
|
_request_counter = counter(
|
177
177
|
"transport_requests_total",
|
178
178
|
description="Total number of transport requests",
|
179
|
-
unit="requests"
|
179
|
+
unit="requests",
|
180
180
|
)
|
181
181
|
_request_duration = histogram(
|
182
|
-
"transport_request_duration_seconds",
|
182
|
+
"transport_request_duration_seconds",
|
183
183
|
description="Duration of transport requests",
|
184
|
-
unit="seconds"
|
184
|
+
unit="seconds",
|
185
185
|
)
|
186
186
|
_error_counter = counter(
|
187
187
|
"transport_errors_total",
|
188
|
-
description="Total number of transport errors",
|
189
|
-
unit="errors"
|
188
|
+
description="Total number of transport errors",
|
189
|
+
unit="errors",
|
190
190
|
)
|
191
|
-
|
191
|
+
|
192
192
|
async def process_request(self, request: Request) -> Request:
|
193
193
|
"""Record request start time."""
|
194
194
|
request.metadata["start_time"] = time.perf_counter()
|
195
195
|
return request
|
196
|
-
|
196
|
+
|
197
197
|
async def process_response(self, response: Response) -> Response:
|
198
198
|
"""Record response metrics."""
|
199
199
|
if response.request and "start_time" in response.request.metadata:
|
200
200
|
start_time = response.request.metadata["start_time"]
|
201
201
|
duration = time.perf_counter() - start_time
|
202
|
-
|
202
|
+
|
203
203
|
method = response.request.method
|
204
204
|
status_class = f"{response.status // 100}xx"
|
205
|
-
|
205
|
+
|
206
206
|
# Record metrics with labels
|
207
|
-
self._request_counter.inc(
|
207
|
+
self._request_counter.inc(
|
208
|
+
1,
|
208
209
|
method=method,
|
209
210
|
status_code=str(response.status),
|
210
|
-
status_class=status_class
|
211
|
+
status_class=status_class,
|
211
212
|
)
|
212
|
-
|
213
|
-
self._request_duration.observe(
|
214
|
-
method=method,
|
215
|
-
status_class=status_class
|
213
|
+
|
214
|
+
self._request_duration.observe(
|
215
|
+
duration, method=method, status_class=status_class
|
216
216
|
)
|
217
|
-
|
217
|
+
|
218
218
|
return response
|
219
|
-
|
219
|
+
|
220
220
|
async def process_error(self, error: Exception, request: Request) -> Exception:
|
221
221
|
"""Record error metrics."""
|
222
222
|
method = request.method
|
223
223
|
error_type = error.__class__.__name__
|
224
|
-
|
225
|
-
self._error_counter.inc(1,
|
226
|
-
|
227
|
-
error_type=error_type
|
228
|
-
)
|
229
|
-
|
224
|
+
|
225
|
+
self._error_counter.inc(1, method=method, error_type=error_type)
|
226
|
+
|
230
227
|
return error
|
231
228
|
|
232
229
|
|
233
230
|
@define
|
234
231
|
class MiddlewarePipeline:
|
235
232
|
"""Pipeline for executing middleware in order."""
|
236
|
-
|
233
|
+
|
237
234
|
middleware: list[Middleware] = field(factory=list)
|
238
|
-
|
235
|
+
|
239
236
|
def add(self, middleware: Middleware) -> None:
|
240
237
|
"""Add middleware to the pipeline."""
|
241
238
|
self.middleware.append(middleware)
|
242
239
|
log.trace(f"Added middleware: {middleware.__class__.__name__}")
|
243
|
-
|
240
|
+
|
244
241
|
def remove(self, middleware_class: type[Middleware]) -> bool:
|
245
242
|
"""Remove middleware by class type."""
|
246
243
|
for i, mw in enumerate(self.middleware):
|
@@ -249,19 +246,19 @@ class MiddlewarePipeline:
|
|
249
246
|
log.trace(f"Removed middleware: {middleware_class.__name__}")
|
250
247
|
return True
|
251
248
|
return False
|
252
|
-
|
249
|
+
|
253
250
|
async def process_request(self, request: Request) -> Request:
|
254
251
|
"""Process request through all middleware."""
|
255
252
|
for mw in self.middleware:
|
256
253
|
request = await mw.process_request(request)
|
257
254
|
return request
|
258
|
-
|
255
|
+
|
259
256
|
async def process_response(self, response: Response) -> Response:
|
260
257
|
"""Process response through all middleware (in reverse order)."""
|
261
258
|
for mw in reversed(self.middleware):
|
262
259
|
response = await mw.process_response(response)
|
263
260
|
return response
|
264
|
-
|
261
|
+
|
265
262
|
async def process_error(self, error: Exception, request: Request) -> Exception:
|
266
263
|
"""Process error through all middleware."""
|
267
264
|
for mw in self.middleware:
|
@@ -273,11 +270,11 @@ def register_middleware(
|
|
273
270
|
name: str,
|
274
271
|
middleware_class: type[Middleware],
|
275
272
|
category: str = "transport.middleware",
|
276
|
-
**metadata
|
273
|
+
**metadata,
|
277
274
|
) -> None:
|
278
275
|
"""Register middleware in the Hub."""
|
279
276
|
registry = get_component_registry()
|
280
|
-
|
277
|
+
|
281
278
|
registry.register(
|
282
279
|
name=name,
|
283
280
|
value=middleware_class,
|
@@ -286,24 +283,26 @@ def register_middleware(
|
|
286
283
|
"category": category,
|
287
284
|
"priority": metadata.get("priority", 100),
|
288
285
|
"class_name": middleware_class.__name__,
|
289
|
-
**metadata
|
286
|
+
**metadata,
|
290
287
|
},
|
291
288
|
replace=True,
|
292
289
|
)
|
293
|
-
|
290
|
+
|
294
291
|
log.debug(f"Registered middleware {middleware_class.__name__} as '{name}'")
|
295
292
|
|
296
293
|
|
297
|
-
def get_middleware_by_category(
|
294
|
+
def get_middleware_by_category(
|
295
|
+
category: str = "transport.middleware",
|
296
|
+
) -> list[type[Middleware]]:
|
298
297
|
"""Get all middleware for a category, sorted by priority."""
|
299
298
|
registry = get_component_registry()
|
300
299
|
middleware = []
|
301
|
-
|
300
|
+
|
302
301
|
for entry in registry:
|
303
302
|
if entry.dimension == category:
|
304
303
|
priority = entry.metadata.get("priority", 100)
|
305
304
|
middleware.append((entry.value, priority))
|
306
|
-
|
305
|
+
|
307
306
|
# Sort by priority (lower numbers = higher priority)
|
308
307
|
middleware.sort(key=lambda x: x[1])
|
309
308
|
return [mw[0] for mw in middleware]
|
@@ -312,11 +311,11 @@ def get_middleware_by_category(category: str = "transport.middleware") -> list[t
|
|
312
311
|
def create_default_pipeline() -> MiddlewarePipeline:
|
313
312
|
"""Create pipeline with default middleware."""
|
314
313
|
pipeline = MiddlewarePipeline()
|
315
|
-
|
314
|
+
|
316
315
|
# Add built-in middleware
|
317
316
|
pipeline.add(LoggingMiddleware())
|
318
317
|
pipeline.add(MetricsMiddleware())
|
319
|
-
|
318
|
+
|
320
319
|
return pipeline
|
321
320
|
|
322
321
|
|
@@ -330,21 +329,21 @@ def _register_builtin_middleware():
|
|
330
329
|
description="Built-in request/response logging",
|
331
330
|
priority=10,
|
332
331
|
)
|
333
|
-
|
332
|
+
|
334
333
|
register_middleware(
|
335
|
-
"retry",
|
334
|
+
"retry",
|
336
335
|
RetryMiddleware,
|
337
336
|
description="Automatic retry with exponential backoff",
|
338
337
|
priority=20,
|
339
338
|
)
|
340
|
-
|
339
|
+
|
341
340
|
register_middleware(
|
342
341
|
"metrics",
|
343
|
-
MetricsMiddleware,
|
342
|
+
MetricsMiddleware,
|
344
343
|
description="Request/response metrics collection",
|
345
344
|
priority=30,
|
346
345
|
)
|
347
|
-
|
346
|
+
|
348
347
|
except ImportError:
|
349
348
|
# Registry not available yet
|
350
349
|
pass
|
@@ -355,12 +354,12 @@ _register_builtin_middleware()
|
|
355
354
|
|
356
355
|
|
357
356
|
__all__ = [
|
358
|
-
"Middleware",
|
359
357
|
"LoggingMiddleware",
|
360
|
-
"RetryMiddleware",
|
361
358
|
"MetricsMiddleware",
|
359
|
+
"Middleware",
|
362
360
|
"MiddlewarePipeline",
|
363
|
-
"
|
364
|
-
"get_middleware_by_category",
|
361
|
+
"RetryMiddleware",
|
365
362
|
"create_default_pipeline",
|
366
|
-
|
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
|
+
]
|