provide-foundation 0.0.0.dev0__py3-none-any.whl → 0.0.0.dev1__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 +12 -20
- provide/foundation/archive/__init__.py +23 -0
- provide/foundation/archive/base.py +70 -0
- provide/foundation/archive/bzip2.py +157 -0
- provide/foundation/archive/gzip.py +159 -0
- provide/foundation/archive/operations.py +336 -0
- provide/foundation/archive/tar.py +164 -0
- provide/foundation/archive/zip.py +203 -0
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/sync.py +19 -4
- provide/foundation/core.py +1 -2
- provide/foundation/crypto/__init__.py +2 -0
- provide/foundation/crypto/certificates/__init__.py +34 -0
- provide/foundation/crypto/certificates/base.py +173 -0
- provide/foundation/crypto/certificates/certificate.py +290 -0
- provide/foundation/crypto/certificates/factory.py +213 -0
- provide/foundation/crypto/certificates/generator.py +138 -0
- provide/foundation/crypto/certificates/loader.py +130 -0
- provide/foundation/crypto/certificates/operations.py +198 -0
- provide/foundation/crypto/certificates/trust.py +107 -0
- provide/foundation/eventsets/__init__.py +0 -0
- provide/foundation/eventsets/display.py +84 -0
- provide/foundation/eventsets/registry.py +160 -0
- provide/foundation/eventsets/resolver.py +192 -0
- provide/foundation/eventsets/sets/das.py +128 -0
- provide/foundation/eventsets/sets/database.py +125 -0
- provide/foundation/eventsets/sets/http.py +153 -0
- provide/foundation/eventsets/sets/llm.py +139 -0
- provide/foundation/eventsets/sets/task_queue.py +107 -0
- provide/foundation/eventsets/types.py +70 -0
- provide/foundation/hub/components.py +7 -133
- provide/foundation/logger/__init__.py +3 -10
- provide/foundation/logger/config/logging.py +6 -6
- provide/foundation/logger/core.py +0 -2
- provide/foundation/logger/custom_processors.py +1 -0
- provide/foundation/logger/factories.py +11 -2
- provide/foundation/logger/processors/main.py +20 -84
- provide/foundation/logger/setup/__init__.py +5 -1
- provide/foundation/logger/setup/coordinator.py +75 -23
- provide/foundation/logger/setup/processors.py +2 -9
- provide/foundation/logger/trace.py +27 -0
- provide/foundation/metrics/otel.py +10 -10
- provide/foundation/process/lifecycle.py +82 -26
- provide/foundation/testing/__init__.py +77 -0
- provide/foundation/testing/archive/__init__.py +24 -0
- provide/foundation/testing/archive/fixtures.py +217 -0
- provide/foundation/testing/common/__init__.py +34 -0
- provide/foundation/testing/common/fixtures.py +263 -0
- provide/foundation/testing/file/__init__.py +40 -0
- provide/foundation/testing/file/fixtures.py +523 -0
- provide/foundation/testing/logger.py +41 -11
- provide/foundation/testing/mocking/__init__.py +46 -0
- provide/foundation/testing/mocking/fixtures.py +331 -0
- provide/foundation/testing/process/__init__.py +48 -0
- provide/foundation/testing/process/fixtures.py +577 -0
- provide/foundation/testing/threading/__init__.py +38 -0
- provide/foundation/testing/threading/fixtures.py +520 -0
- provide/foundation/testing/time/__init__.py +32 -0
- provide/foundation/testing/time/fixtures.py +409 -0
- provide/foundation/testing/transport/__init__.py +30 -0
- provide/foundation/testing/transport/fixtures.py +280 -0
- provide/foundation/tools/__init__.py +58 -0
- provide/foundation/tools/base.py +348 -0
- provide/foundation/tools/cache.py +266 -0
- provide/foundation/tools/downloader.py +213 -0
- provide/foundation/tools/installer.py +254 -0
- provide/foundation/tools/registry.py +223 -0
- provide/foundation/tools/resolver.py +321 -0
- provide/foundation/tools/verifier.py +186 -0
- provide/foundation/tracer/otel.py +7 -11
- provide/foundation/transport/__init__.py +155 -0
- provide/foundation/transport/base.py +171 -0
- provide/foundation/transport/client.py +266 -0
- provide/foundation/transport/config.py +209 -0
- provide/foundation/transport/errors.py +79 -0
- provide/foundation/transport/http.py +232 -0
- provide/foundation/transport/middleware.py +366 -0
- provide/foundation/transport/registry.py +167 -0
- provide/foundation/transport/types.py +45 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/METADATA +5 -28
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/RECORD +85 -34
- provide/foundation/cli/commands/logs/generate_old.py +0 -569
- provide/foundation/crypto/certificates.py +0 -896
- provide/foundation/logger/emoji/__init__.py +0 -44
- provide/foundation/logger/emoji/matrix.py +0 -209
- provide/foundation/logger/emoji/sets.py +0 -458
- provide/foundation/logger/emoji/types.py +0 -56
- provide/foundation/logger/setup/emoji_resolver.py +0 -64
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,366 @@
|
|
1
|
+
"""
|
2
|
+
Transport middleware system with Hub registration.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import asyncio
|
6
|
+
import time
|
7
|
+
from abc import ABC, abstractmethod
|
8
|
+
from typing import Any
|
9
|
+
|
10
|
+
from attrs import define, field
|
11
|
+
|
12
|
+
from provide.foundation.hub import get_component_registry
|
13
|
+
from provide.foundation.hub.components import ComponentCategory
|
14
|
+
from provide.foundation.logger import get_logger
|
15
|
+
from provide.foundation.metrics import counter, histogram
|
16
|
+
from provide.foundation.transport.base import Request, Response
|
17
|
+
from provide.foundation.transport.errors import TransportError
|
18
|
+
|
19
|
+
log = get_logger(__name__)
|
20
|
+
|
21
|
+
|
22
|
+
class Middleware(ABC):
|
23
|
+
"""Abstract base class for transport middleware."""
|
24
|
+
|
25
|
+
@abstractmethod
|
26
|
+
async def process_request(self, request: Request) -> Request:
|
27
|
+
"""Process request before sending."""
|
28
|
+
pass
|
29
|
+
|
30
|
+
@abstractmethod
|
31
|
+
async def process_response(self, response: Response) -> Response:
|
32
|
+
"""Process response after receiving."""
|
33
|
+
pass
|
34
|
+
|
35
|
+
@abstractmethod
|
36
|
+
async def process_error(self, error: Exception, request: Request) -> Exception:
|
37
|
+
"""Process errors during request."""
|
38
|
+
pass
|
39
|
+
|
40
|
+
|
41
|
+
@define
|
42
|
+
class LoggingMiddleware(Middleware):
|
43
|
+
"""Built-in telemetry middleware using foundation.logger."""
|
44
|
+
|
45
|
+
log_requests: bool = field(default=True)
|
46
|
+
log_responses: bool = field(default=True)
|
47
|
+
log_bodies: bool = field(default=False)
|
48
|
+
|
49
|
+
async def process_request(self, request: Request) -> Request:
|
50
|
+
"""Log outgoing request."""
|
51
|
+
if self.log_requests:
|
52
|
+
log.info(
|
53
|
+
f"🚀 {request.method} {request.uri}",
|
54
|
+
method=request.method,
|
55
|
+
uri=str(request.uri),
|
56
|
+
headers=dict(request.headers) if hasattr(request, 'headers') else {},
|
57
|
+
)
|
58
|
+
|
59
|
+
if self.log_bodies and request.body:
|
60
|
+
log.trace("Request body", body=request.body, method=request.method, uri=str(request.uri))
|
61
|
+
|
62
|
+
return request
|
63
|
+
|
64
|
+
async def process_response(self, response: Response) -> Response:
|
65
|
+
"""Log incoming response."""
|
66
|
+
if self.log_responses:
|
67
|
+
status_emoji = self._get_status_emoji(response.status)
|
68
|
+
log.info(
|
69
|
+
f"{status_emoji} {response.status} ({response.elapsed_ms:.0f}ms)",
|
70
|
+
status_code=response.status,
|
71
|
+
elapsed_ms=response.elapsed_ms,
|
72
|
+
method=response.request.method if response.request else None,
|
73
|
+
uri=str(response.request.uri) if response.request else None,
|
74
|
+
headers=dict(response.headers) if hasattr(response, 'headers') else {},
|
75
|
+
)
|
76
|
+
|
77
|
+
if self.log_bodies and response.body:
|
78
|
+
log.trace(
|
79
|
+
"Response body",
|
80
|
+
body=response.text[:500], # Truncate large bodies
|
81
|
+
status_code=response.status,
|
82
|
+
method=response.request.method if response.request else None,
|
83
|
+
uri=str(response.request.uri) if response.request else None,
|
84
|
+
)
|
85
|
+
|
86
|
+
return response
|
87
|
+
|
88
|
+
async def process_error(self, error: Exception, request: Request) -> Exception:
|
89
|
+
"""Log errors."""
|
90
|
+
log.error(
|
91
|
+
f"❌ {request.method} {request.uri} failed: {error}",
|
92
|
+
method=request.method,
|
93
|
+
uri=str(request.uri),
|
94
|
+
error_type=error.__class__.__name__,
|
95
|
+
error_message=str(error),
|
96
|
+
)
|
97
|
+
return error
|
98
|
+
|
99
|
+
def _get_status_emoji(self, status_code: int) -> str:
|
100
|
+
"""Get emoji for status code."""
|
101
|
+
if 200 <= status_code < 300:
|
102
|
+
return "✅"
|
103
|
+
elif 300 <= status_code < 400:
|
104
|
+
return "↩️"
|
105
|
+
elif 400 <= status_code < 500:
|
106
|
+
return "⚠️"
|
107
|
+
elif 500 <= status_code < 600:
|
108
|
+
return "❌"
|
109
|
+
else:
|
110
|
+
return "❓"
|
111
|
+
|
112
|
+
|
113
|
+
@define
|
114
|
+
class RetryMiddleware(Middleware):
|
115
|
+
"""Automatic retry middleware with exponential backoff."""
|
116
|
+
|
117
|
+
max_retries: int = field(default=3)
|
118
|
+
backoff_factor: float = field(default=0.5)
|
119
|
+
retryable_status_codes: set[int] = field(factory=lambda: {500, 502, 503, 504})
|
120
|
+
retryable_exceptions: tuple[type[Exception], ...] = field(
|
121
|
+
factory=lambda: (TransportError,)
|
122
|
+
)
|
123
|
+
|
124
|
+
async def process_request(self, request: Request) -> Request:
|
125
|
+
"""No request processing needed."""
|
126
|
+
return request
|
127
|
+
|
128
|
+
async def process_response(self, response: Response) -> Response:
|
129
|
+
"""No response processing needed (retries handled in execute)."""
|
130
|
+
return response
|
131
|
+
|
132
|
+
async def process_error(self, error: Exception, request: Request) -> Exception:
|
133
|
+
"""Handle error, potentially with retries (this is called by client)."""
|
134
|
+
return error
|
135
|
+
|
136
|
+
async def execute_with_retry(self, execute_func, request: Request) -> Response:
|
137
|
+
"""Execute request with retry logic."""
|
138
|
+
last_exception = None
|
139
|
+
|
140
|
+
for attempt in range(self.max_retries + 1):
|
141
|
+
try:
|
142
|
+
response = await execute_func(request)
|
143
|
+
|
144
|
+
# Check if status code is retryable
|
145
|
+
if response.status in self.retryable_status_codes and attempt < self.max_retries:
|
146
|
+
wait_time = self.backoff_factor * (2 ** attempt)
|
147
|
+
log.info(f"🔄 Retry {attempt + 1}/{self.max_retries} after {wait_time:.1f}s (status {response.status})")
|
148
|
+
await asyncio.sleep(wait_time)
|
149
|
+
continue
|
150
|
+
|
151
|
+
return response
|
152
|
+
|
153
|
+
except self.retryable_exceptions as e:
|
154
|
+
last_exception = e
|
155
|
+
|
156
|
+
if attempt < self.max_retries:
|
157
|
+
wait_time = self.backoff_factor * (2 ** attempt)
|
158
|
+
log.info(f"🔄 Retry {attempt + 1}/{self.max_retries} after {wait_time:.1f}s (error: {e})")
|
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")
|
169
|
+
|
170
|
+
|
171
|
+
@define
|
172
|
+
class MetricsMiddleware(Middleware):
|
173
|
+
"""Middleware for collecting transport metrics using foundation.metrics."""
|
174
|
+
|
175
|
+
# Create metrics instances
|
176
|
+
_request_counter = counter(
|
177
|
+
"transport_requests_total",
|
178
|
+
description="Total number of transport requests",
|
179
|
+
unit="requests"
|
180
|
+
)
|
181
|
+
_request_duration = histogram(
|
182
|
+
"transport_request_duration_seconds",
|
183
|
+
description="Duration of transport requests",
|
184
|
+
unit="seconds"
|
185
|
+
)
|
186
|
+
_error_counter = counter(
|
187
|
+
"transport_errors_total",
|
188
|
+
description="Total number of transport errors",
|
189
|
+
unit="errors"
|
190
|
+
)
|
191
|
+
|
192
|
+
async def process_request(self, request: Request) -> Request:
|
193
|
+
"""Record request start time."""
|
194
|
+
request.metadata["start_time"] = time.perf_counter()
|
195
|
+
return request
|
196
|
+
|
197
|
+
async def process_response(self, response: Response) -> Response:
|
198
|
+
"""Record response metrics."""
|
199
|
+
if response.request and "start_time" in response.request.metadata:
|
200
|
+
start_time = response.request.metadata["start_time"]
|
201
|
+
duration = time.perf_counter() - start_time
|
202
|
+
|
203
|
+
method = response.request.method
|
204
|
+
status_class = f"{response.status // 100}xx"
|
205
|
+
|
206
|
+
# Record metrics with labels
|
207
|
+
self._request_counter.inc(1,
|
208
|
+
method=method,
|
209
|
+
status_code=str(response.status),
|
210
|
+
status_class=status_class
|
211
|
+
)
|
212
|
+
|
213
|
+
self._request_duration.observe(duration,
|
214
|
+
method=method,
|
215
|
+
status_class=status_class
|
216
|
+
)
|
217
|
+
|
218
|
+
return response
|
219
|
+
|
220
|
+
async def process_error(self, error: Exception, request: Request) -> Exception:
|
221
|
+
"""Record error metrics."""
|
222
|
+
method = request.method
|
223
|
+
error_type = error.__class__.__name__
|
224
|
+
|
225
|
+
self._error_counter.inc(1,
|
226
|
+
method=method,
|
227
|
+
error_type=error_type
|
228
|
+
)
|
229
|
+
|
230
|
+
return error
|
231
|
+
|
232
|
+
|
233
|
+
@define
|
234
|
+
class MiddlewarePipeline:
|
235
|
+
"""Pipeline for executing middleware in order."""
|
236
|
+
|
237
|
+
middleware: list[Middleware] = field(factory=list)
|
238
|
+
|
239
|
+
def add(self, middleware: Middleware) -> None:
|
240
|
+
"""Add middleware to the pipeline."""
|
241
|
+
self.middleware.append(middleware)
|
242
|
+
log.trace(f"Added middleware: {middleware.__class__.__name__}")
|
243
|
+
|
244
|
+
def remove(self, middleware_class: type[Middleware]) -> bool:
|
245
|
+
"""Remove middleware by class type."""
|
246
|
+
for i, mw in enumerate(self.middleware):
|
247
|
+
if isinstance(mw, middleware_class):
|
248
|
+
del self.middleware[i]
|
249
|
+
log.trace(f"Removed middleware: {middleware_class.__name__}")
|
250
|
+
return True
|
251
|
+
return False
|
252
|
+
|
253
|
+
async def process_request(self, request: Request) -> Request:
|
254
|
+
"""Process request through all middleware."""
|
255
|
+
for mw in self.middleware:
|
256
|
+
request = await mw.process_request(request)
|
257
|
+
return request
|
258
|
+
|
259
|
+
async def process_response(self, response: Response) -> Response:
|
260
|
+
"""Process response through all middleware (in reverse order)."""
|
261
|
+
for mw in reversed(self.middleware):
|
262
|
+
response = await mw.process_response(response)
|
263
|
+
return response
|
264
|
+
|
265
|
+
async def process_error(self, error: Exception, request: Request) -> Exception:
|
266
|
+
"""Process error through all middleware."""
|
267
|
+
for mw in self.middleware:
|
268
|
+
error = await mw.process_error(error, request)
|
269
|
+
return error
|
270
|
+
|
271
|
+
|
272
|
+
def register_middleware(
|
273
|
+
name: str,
|
274
|
+
middleware_class: type[Middleware],
|
275
|
+
category: str = "transport.middleware",
|
276
|
+
**metadata
|
277
|
+
) -> None:
|
278
|
+
"""Register middleware in the Hub."""
|
279
|
+
registry = get_component_registry()
|
280
|
+
|
281
|
+
registry.register(
|
282
|
+
name=name,
|
283
|
+
value=middleware_class,
|
284
|
+
dimension=category,
|
285
|
+
metadata={
|
286
|
+
"category": category,
|
287
|
+
"priority": metadata.get("priority", 100),
|
288
|
+
"class_name": middleware_class.__name__,
|
289
|
+
**metadata
|
290
|
+
},
|
291
|
+
replace=True,
|
292
|
+
)
|
293
|
+
|
294
|
+
log.debug(f"Registered middleware {middleware_class.__name__} as '{name}'")
|
295
|
+
|
296
|
+
|
297
|
+
def get_middleware_by_category(category: str = "transport.middleware") -> list[type[Middleware]]:
|
298
|
+
"""Get all middleware for a category, sorted by priority."""
|
299
|
+
registry = get_component_registry()
|
300
|
+
middleware = []
|
301
|
+
|
302
|
+
for entry in registry:
|
303
|
+
if entry.dimension == category:
|
304
|
+
priority = entry.metadata.get("priority", 100)
|
305
|
+
middleware.append((entry.value, priority))
|
306
|
+
|
307
|
+
# Sort by priority (lower numbers = higher priority)
|
308
|
+
middleware.sort(key=lambda x: x[1])
|
309
|
+
return [mw[0] for mw in middleware]
|
310
|
+
|
311
|
+
|
312
|
+
def create_default_pipeline() -> MiddlewarePipeline:
|
313
|
+
"""Create pipeline with default middleware."""
|
314
|
+
pipeline = MiddlewarePipeline()
|
315
|
+
|
316
|
+
# Add built-in middleware
|
317
|
+
pipeline.add(LoggingMiddleware())
|
318
|
+
pipeline.add(MetricsMiddleware())
|
319
|
+
|
320
|
+
return pipeline
|
321
|
+
|
322
|
+
|
323
|
+
# Auto-register built-in middleware
|
324
|
+
def _register_builtin_middleware():
|
325
|
+
"""Register built-in middleware with the Hub."""
|
326
|
+
try:
|
327
|
+
register_middleware(
|
328
|
+
"logging",
|
329
|
+
LoggingMiddleware,
|
330
|
+
description="Built-in request/response logging",
|
331
|
+
priority=10,
|
332
|
+
)
|
333
|
+
|
334
|
+
register_middleware(
|
335
|
+
"retry",
|
336
|
+
RetryMiddleware,
|
337
|
+
description="Automatic retry with exponential backoff",
|
338
|
+
priority=20,
|
339
|
+
)
|
340
|
+
|
341
|
+
register_middleware(
|
342
|
+
"metrics",
|
343
|
+
MetricsMiddleware,
|
344
|
+
description="Request/response metrics collection",
|
345
|
+
priority=30,
|
346
|
+
)
|
347
|
+
|
348
|
+
except ImportError:
|
349
|
+
# Registry not available yet
|
350
|
+
pass
|
351
|
+
|
352
|
+
|
353
|
+
# Register when module is imported
|
354
|
+
_register_builtin_middleware()
|
355
|
+
|
356
|
+
|
357
|
+
__all__ = [
|
358
|
+
"Middleware",
|
359
|
+
"LoggingMiddleware",
|
360
|
+
"RetryMiddleware",
|
361
|
+
"MetricsMiddleware",
|
362
|
+
"MiddlewarePipeline",
|
363
|
+
"register_middleware",
|
364
|
+
"get_middleware_by_category",
|
365
|
+
"create_default_pipeline",
|
366
|
+
]
|
@@ -0,0 +1,167 @@
|
|
1
|
+
"""
|
2
|
+
Transport registration and discovery using Foundation Hub.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Any
|
6
|
+
|
7
|
+
from provide.foundation.hub import get_component_registry
|
8
|
+
from provide.foundation.hub.components import ComponentCategory
|
9
|
+
from provide.foundation.logger import get_logger
|
10
|
+
from provide.foundation.transport.base import Transport
|
11
|
+
from provide.foundation.transport.errors import TransportNotFoundError
|
12
|
+
from provide.foundation.transport.types import TransportType
|
13
|
+
|
14
|
+
log = get_logger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
def register_transport(
|
18
|
+
transport_type: TransportType,
|
19
|
+
transport_class: type[Transport],
|
20
|
+
schemes: list[str] | None = None,
|
21
|
+
**metadata
|
22
|
+
) -> None:
|
23
|
+
"""
|
24
|
+
Register a transport implementation in the Hub.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
transport_type: The primary transport type
|
28
|
+
transport_class: Transport implementation class
|
29
|
+
schemes: List of URI schemes this transport handles
|
30
|
+
**metadata: Additional metadata for the transport
|
31
|
+
"""
|
32
|
+
registry = get_component_registry()
|
33
|
+
|
34
|
+
# Default schemes to just the transport type
|
35
|
+
if schemes is None:
|
36
|
+
schemes = [transport_type.value]
|
37
|
+
|
38
|
+
registry.register(
|
39
|
+
name=transport_type.value,
|
40
|
+
value=transport_class,
|
41
|
+
dimension=ComponentCategory.TRANSPORT.value,
|
42
|
+
metadata={
|
43
|
+
"transport_type": transport_type,
|
44
|
+
"schemes": schemes,
|
45
|
+
"class_name": transport_class.__name__,
|
46
|
+
**metadata
|
47
|
+
},
|
48
|
+
replace=True, # Allow re-registration
|
49
|
+
)
|
50
|
+
|
51
|
+
log.debug(f"Registered transport {transport_class.__name__} for schemes: {schemes}")
|
52
|
+
|
53
|
+
|
54
|
+
def get_transport_for_scheme(scheme: str) -> type[Transport]:
|
55
|
+
"""
|
56
|
+
Get transport class for a URI scheme.
|
57
|
+
|
58
|
+
Args:
|
59
|
+
scheme: URI scheme (e.g., 'http', 'https', 'ws')
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
Transport class that handles the scheme
|
63
|
+
|
64
|
+
Raises:
|
65
|
+
TransportNotFoundError: If no transport is registered for the scheme
|
66
|
+
"""
|
67
|
+
registry = get_component_registry()
|
68
|
+
|
69
|
+
# Search through registered transports
|
70
|
+
for entry in registry:
|
71
|
+
if entry.dimension == ComponentCategory.TRANSPORT.value:
|
72
|
+
schemes = entry.metadata.get("schemes", [])
|
73
|
+
if scheme.lower() in schemes:
|
74
|
+
log.trace(f"Found transport {entry.value.__name__} for scheme '{scheme}'")
|
75
|
+
return entry.value
|
76
|
+
|
77
|
+
raise TransportNotFoundError(
|
78
|
+
f"No transport registered for scheme: {scheme}",
|
79
|
+
scheme=scheme,
|
80
|
+
)
|
81
|
+
|
82
|
+
|
83
|
+
def get_transport(uri: str) -> Transport:
|
84
|
+
"""
|
85
|
+
Get transport instance for a URI.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
uri: Full URI to get transport for
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
Transport instance ready to use
|
92
|
+
|
93
|
+
Raises:
|
94
|
+
TransportNotFoundError: If no transport supports the URI scheme
|
95
|
+
"""
|
96
|
+
scheme = uri.split("://")[0].lower()
|
97
|
+
transport_class = get_transport_for_scheme(scheme)
|
98
|
+
return transport_class()
|
99
|
+
|
100
|
+
|
101
|
+
def list_registered_transports() -> dict[str, dict[str, Any]]:
|
102
|
+
"""
|
103
|
+
List all registered transports.
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
Dictionary mapping transport names to their info
|
107
|
+
"""
|
108
|
+
registry = get_component_registry()
|
109
|
+
transports = {}
|
110
|
+
|
111
|
+
for entry in registry:
|
112
|
+
if entry.dimension == ComponentCategory.TRANSPORT.value:
|
113
|
+
transports[entry.name] = {
|
114
|
+
"class": entry.value,
|
115
|
+
"schemes": entry.metadata.get("schemes", []),
|
116
|
+
"transport_type": entry.metadata.get("transport_type"),
|
117
|
+
"metadata": entry.metadata,
|
118
|
+
}
|
119
|
+
|
120
|
+
return transports
|
121
|
+
|
122
|
+
|
123
|
+
def get_transport_info(scheme_or_name: str) -> dict[str, Any] | None:
|
124
|
+
"""
|
125
|
+
Get detailed information about a transport.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
scheme_or_name: URI scheme or transport name
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
Transport information or None if not found
|
132
|
+
"""
|
133
|
+
registry = get_component_registry()
|
134
|
+
|
135
|
+
for entry in registry:
|
136
|
+
if entry.dimension == ComponentCategory.TRANSPORT.value:
|
137
|
+
# Check if it matches by name
|
138
|
+
if entry.name == scheme_or_name:
|
139
|
+
return {
|
140
|
+
"name": entry.name,
|
141
|
+
"class": entry.value,
|
142
|
+
"schemes": entry.metadata.get("schemes", []),
|
143
|
+
"transport_type": entry.metadata.get("transport_type"),
|
144
|
+
"metadata": entry.metadata,
|
145
|
+
}
|
146
|
+
|
147
|
+
# Check if it matches by scheme
|
148
|
+
schemes = entry.metadata.get("schemes", [])
|
149
|
+
if scheme_or_name.lower() in schemes:
|
150
|
+
return {
|
151
|
+
"name": entry.name,
|
152
|
+
"class": entry.value,
|
153
|
+
"schemes": schemes,
|
154
|
+
"transport_type": entry.metadata.get("transport_type"),
|
155
|
+
"metadata": entry.metadata,
|
156
|
+
}
|
157
|
+
|
158
|
+
return None
|
159
|
+
|
160
|
+
|
161
|
+
__all__ = [
|
162
|
+
"register_transport",
|
163
|
+
"get_transport_for_scheme",
|
164
|
+
"get_transport",
|
165
|
+
"list_registered_transports",
|
166
|
+
"get_transport_info",
|
167
|
+
]
|
@@ -0,0 +1,45 @@
|
|
1
|
+
"""
|
2
|
+
Transport type definitions and enums.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from enum import Enum
|
6
|
+
from typing import Any, TypeAlias
|
7
|
+
|
8
|
+
# Type aliases
|
9
|
+
Headers: TypeAlias = dict[str, str]
|
10
|
+
Params: TypeAlias = dict[str, Any]
|
11
|
+
Data: TypeAlias = dict[str, Any] | bytes | str | None
|
12
|
+
|
13
|
+
|
14
|
+
class TransportType(str, Enum):
|
15
|
+
"""Supported transport types."""
|
16
|
+
|
17
|
+
HTTP = "http"
|
18
|
+
HTTPS = "https"
|
19
|
+
WS = "ws"
|
20
|
+
WSS = "wss"
|
21
|
+
GRPC = "grpc"
|
22
|
+
GRAPHQL = "graphql"
|
23
|
+
AMQP = "amqp"
|
24
|
+
MQTT = "mqtt"
|
25
|
+
|
26
|
+
|
27
|
+
class HTTPMethod(str, Enum):
|
28
|
+
"""HTTP methods."""
|
29
|
+
|
30
|
+
GET = "GET"
|
31
|
+
POST = "POST"
|
32
|
+
PUT = "PUT"
|
33
|
+
PATCH = "PATCH"
|
34
|
+
DELETE = "DELETE"
|
35
|
+
HEAD = "HEAD"
|
36
|
+
OPTIONS = "OPTIONS"
|
37
|
+
|
38
|
+
|
39
|
+
__all__ = [
|
40
|
+
"Headers",
|
41
|
+
"Params",
|
42
|
+
"Data",
|
43
|
+
"TransportType",
|
44
|
+
"HTTPMethod",
|
45
|
+
]
|
{provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.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.dev1
|
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>
|
@@ -29,13 +29,15 @@ Provides-Extra: cli
|
|
29
29
|
Requires-Dist: click>=8.1.7; extra == "cli"
|
30
30
|
Provides-Extra: crypto
|
31
31
|
Requires-Dist: cryptography>=45.0.7; extra == "crypto"
|
32
|
+
Provides-Extra: transport
|
33
|
+
Requires-Dist: httpx>=0.27.0; extra == "transport"
|
32
34
|
Provides-Extra: opentelemetry
|
33
35
|
Requires-Dist: opentelemetry-api>=1.22.0; extra == "opentelemetry"
|
34
36
|
Requires-Dist: opentelemetry-sdk>=1.22.0; extra == "opentelemetry"
|
35
37
|
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.22.0; extra == "opentelemetry"
|
36
38
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.22.0; extra == "opentelemetry"
|
37
39
|
Provides-Extra: all
|
38
|
-
Requires-Dist: provide-foundation[cli,crypto,opentelemetry]; extra == "all"
|
40
|
+
Requires-Dist: provide-foundation[cli,crypto,opentelemetry,transport]; extra == "all"
|
39
41
|
Dynamic: license-file
|
40
42
|
|
41
43
|
# provide.foundation
|
@@ -439,31 +441,6 @@ Complete working examples are available in the [examples/](examples/) directory:
|
|
439
441
|
|
440
442
|
---
|
441
443
|
|
442
|
-
## Performance
|
443
|
-
|
444
|
-
- **Logging**: 14,000+ messages/second with emoji processing and structured logging
|
445
|
-
- **Configuration**: Lazy loading with multi-source caching for optimal performance
|
446
|
-
- **File Operations**: Atomic writes with format detection prevent corruption
|
447
|
-
- **Process Management**: Efficient streaming with async support and backpressure handling
|
448
|
-
- **Cryptography**: Hardware-accelerated operations with secure algorithm defaults
|
449
|
-
- **Platform Detection**: Cached system information for minimal overhead
|
450
|
-
|
451
|
-
---
|
452
|
-
|
453
|
-
## Contributing
|
454
|
-
|
455
|
-
We welcome contributions! Please see:
|
456
|
-
- [DEVELOPMENT.md](DEVELOPMENT.md) - Development setup and guidelines
|
457
|
-
- [GitHub Issues](https://github.com/provide-io/provide-foundation/issues) - Bug reports and feature requests
|
458
|
-
|
459
|
-
---
|
460
|
-
|
461
|
-
## License
|
462
|
-
|
463
|
-
MIT License - see [LICENSE](LICENSE) file for details.
|
464
|
-
|
465
|
-
---
|
466
|
-
|
467
444
|
<p align="center">
|
468
|
-
Built by <a href="https://provide.io">
|
445
|
+
Built by <a href="https://provide.io">provide.io</a>
|
469
446
|
</p>
|