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.
Files changed (163) hide show
  1. provide/foundation/__init__.py +36 -10
  2. provide/foundation/archive/__init__.py +1 -1
  3. provide/foundation/archive/base.py +15 -14
  4. provide/foundation/archive/bzip2.py +40 -40
  5. provide/foundation/archive/gzip.py +42 -42
  6. provide/foundation/archive/operations.py +93 -96
  7. provide/foundation/archive/tar.py +33 -31
  8. provide/foundation/archive/zip.py +52 -50
  9. provide/foundation/asynctools/__init__.py +20 -0
  10. provide/foundation/asynctools/core.py +126 -0
  11. provide/foundation/cli/__init__.py +2 -2
  12. provide/foundation/cli/commands/deps.py +15 -9
  13. provide/foundation/cli/commands/logs/__init__.py +3 -3
  14. provide/foundation/cli/commands/logs/generate.py +2 -2
  15. provide/foundation/cli/commands/logs/query.py +4 -4
  16. provide/foundation/cli/commands/logs/send.py +3 -3
  17. provide/foundation/cli/commands/logs/tail.py +3 -3
  18. provide/foundation/cli/decorators.py +11 -11
  19. provide/foundation/cli/main.py +1 -1
  20. provide/foundation/cli/testing.py +2 -40
  21. provide/foundation/cli/utils.py +21 -18
  22. provide/foundation/config/__init__.py +35 -2
  23. provide/foundation/config/base.py +2 -2
  24. provide/foundation/config/converters.py +477 -0
  25. provide/foundation/config/defaults.py +67 -0
  26. provide/foundation/config/env.py +6 -20
  27. provide/foundation/config/loader.py +10 -4
  28. provide/foundation/config/sync.py +8 -6
  29. provide/foundation/config/types.py +5 -5
  30. provide/foundation/config/validators.py +4 -4
  31. provide/foundation/console/input.py +5 -5
  32. provide/foundation/console/output.py +36 -14
  33. provide/foundation/context/__init__.py +8 -4
  34. provide/foundation/context/core.py +88 -110
  35. provide/foundation/crypto/certificates/__init__.py +9 -5
  36. provide/foundation/crypto/certificates/base.py +2 -2
  37. provide/foundation/crypto/certificates/certificate.py +48 -19
  38. provide/foundation/crypto/certificates/factory.py +26 -18
  39. provide/foundation/crypto/certificates/generator.py +24 -23
  40. provide/foundation/crypto/certificates/loader.py +24 -16
  41. provide/foundation/crypto/certificates/operations.py +17 -10
  42. provide/foundation/crypto/certificates/trust.py +21 -21
  43. provide/foundation/env/__init__.py +28 -0
  44. provide/foundation/env/core.py +218 -0
  45. provide/foundation/errors/__init__.py +3 -3
  46. provide/foundation/errors/decorators.py +0 -234
  47. provide/foundation/errors/types.py +0 -98
  48. provide/foundation/eventsets/display.py +13 -14
  49. provide/foundation/eventsets/registry.py +61 -31
  50. provide/foundation/eventsets/resolver.py +50 -46
  51. provide/foundation/eventsets/sets/das.py +8 -8
  52. provide/foundation/eventsets/sets/database.py +14 -14
  53. provide/foundation/eventsets/sets/http.py +21 -21
  54. provide/foundation/eventsets/sets/llm.py +16 -16
  55. provide/foundation/eventsets/sets/task_queue.py +13 -13
  56. provide/foundation/eventsets/types.py +7 -7
  57. provide/foundation/file/directory.py +14 -23
  58. provide/foundation/file/lock.py +4 -3
  59. provide/foundation/hub/components.py +75 -389
  60. provide/foundation/hub/config.py +157 -0
  61. provide/foundation/hub/discovery.py +63 -0
  62. provide/foundation/hub/handlers.py +89 -0
  63. provide/foundation/hub/lifecycle.py +195 -0
  64. provide/foundation/hub/manager.py +7 -4
  65. provide/foundation/hub/processors.py +49 -0
  66. provide/foundation/integrations/__init__.py +11 -0
  67. provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
  68. provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
  69. provide/foundation/{observability → integrations}/openobserve/client.py +14 -14
  70. provide/foundation/{observability → integrations}/openobserve/commands.py +12 -12
  71. provide/foundation/integrations/openobserve/config.py +37 -0
  72. provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
  73. provide/foundation/{observability → integrations}/openobserve/otlp.py +2 -2
  74. provide/foundation/{observability → integrations}/openobserve/search.py +2 -3
  75. provide/foundation/{observability → integrations}/openobserve/streaming.py +5 -5
  76. provide/foundation/logger/__init__.py +0 -1
  77. provide/foundation/logger/config/base.py +1 -1
  78. provide/foundation/logger/config/logging.py +69 -299
  79. provide/foundation/logger/config/telemetry.py +39 -121
  80. provide/foundation/logger/factories.py +2 -2
  81. provide/foundation/logger/processors/main.py +12 -10
  82. provide/foundation/logger/ratelimit/limiters.py +4 -4
  83. provide/foundation/logger/ratelimit/processor.py +1 -1
  84. provide/foundation/logger/setup/coordinator.py +39 -25
  85. provide/foundation/logger/setup/processors.py +3 -3
  86. provide/foundation/logger/setup/testing.py +14 -0
  87. provide/foundation/logger/trace.py +5 -5
  88. provide/foundation/metrics/__init__.py +1 -1
  89. provide/foundation/metrics/otel.py +3 -1
  90. provide/foundation/observability/__init__.py +3 -3
  91. provide/foundation/process/__init__.py +9 -0
  92. provide/foundation/process/exit.py +48 -0
  93. provide/foundation/process/lifecycle.py +69 -46
  94. provide/foundation/resilience/__init__.py +36 -0
  95. provide/foundation/resilience/circuit.py +166 -0
  96. provide/foundation/resilience/decorators.py +236 -0
  97. provide/foundation/resilience/fallback.py +208 -0
  98. provide/foundation/resilience/retry.py +327 -0
  99. provide/foundation/serialization/__init__.py +16 -0
  100. provide/foundation/serialization/core.py +70 -0
  101. provide/foundation/streams/config.py +78 -0
  102. provide/foundation/streams/console.py +4 -5
  103. provide/foundation/streams/core.py +5 -2
  104. provide/foundation/streams/file.py +12 -2
  105. provide/foundation/testing/__init__.py +29 -9
  106. provide/foundation/testing/archive/__init__.py +7 -7
  107. provide/foundation/testing/archive/fixtures.py +58 -54
  108. provide/foundation/testing/cli.py +30 -20
  109. provide/foundation/testing/common/__init__.py +13 -15
  110. provide/foundation/testing/common/fixtures.py +27 -57
  111. provide/foundation/testing/file/__init__.py +15 -15
  112. provide/foundation/testing/file/content_fixtures.py +289 -0
  113. provide/foundation/testing/file/directory_fixtures.py +107 -0
  114. provide/foundation/testing/file/fixtures.py +42 -516
  115. provide/foundation/testing/file/special_fixtures.py +145 -0
  116. provide/foundation/testing/logger.py +89 -8
  117. provide/foundation/testing/mocking/__init__.py +21 -21
  118. provide/foundation/testing/mocking/fixtures.py +80 -67
  119. provide/foundation/testing/process/__init__.py +23 -23
  120. provide/foundation/testing/process/async_fixtures.py +414 -0
  121. provide/foundation/testing/process/fixtures.py +48 -571
  122. provide/foundation/testing/process/subprocess_fixtures.py +210 -0
  123. provide/foundation/testing/threading/__init__.py +17 -17
  124. provide/foundation/testing/threading/basic_fixtures.py +105 -0
  125. provide/foundation/testing/threading/data_fixtures.py +101 -0
  126. provide/foundation/testing/threading/execution_fixtures.py +278 -0
  127. provide/foundation/testing/threading/fixtures.py +32 -502
  128. provide/foundation/testing/threading/sync_fixtures.py +100 -0
  129. provide/foundation/testing/time/__init__.py +11 -11
  130. provide/foundation/testing/time/fixtures.py +95 -83
  131. provide/foundation/testing/transport/__init__.py +9 -9
  132. provide/foundation/testing/transport/fixtures.py +54 -54
  133. provide/foundation/time/__init__.py +18 -0
  134. provide/foundation/time/core.py +63 -0
  135. provide/foundation/tools/__init__.py +2 -2
  136. provide/foundation/tools/base.py +68 -67
  137. provide/foundation/tools/cache.py +69 -74
  138. provide/foundation/tools/downloader.py +68 -62
  139. provide/foundation/tools/installer.py +51 -57
  140. provide/foundation/tools/registry.py +38 -45
  141. provide/foundation/tools/resolver.py +70 -68
  142. provide/foundation/tools/verifier.py +39 -50
  143. provide/foundation/tracer/spans.py +2 -14
  144. provide/foundation/transport/__init__.py +26 -33
  145. provide/foundation/transport/base.py +32 -30
  146. provide/foundation/transport/client.py +44 -49
  147. provide/foundation/transport/config.py +36 -107
  148. provide/foundation/transport/errors.py +13 -27
  149. provide/foundation/transport/http.py +69 -55
  150. provide/foundation/transport/middleware.py +113 -114
  151. provide/foundation/transport/registry.py +29 -27
  152. provide/foundation/transport/types.py +6 -6
  153. provide/foundation/utils/deps.py +17 -14
  154. provide/foundation/utils/parsing.py +49 -4
  155. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
  156. provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
  157. provide_foundation-0.0.0.dev1.dist-info/RECORD +0 -200
  158. /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
  159. /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
  160. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
  161. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
  162. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
  163. {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
- from typing import Any
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, 'headers') else {},
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("Request body", body=request.body, method=request.method, uri=str(request.uri))
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, 'headers') else {},
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 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
+ """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
- 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")
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(1,
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(duration,
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
- method=method,
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(category: str = "transport.middleware") -> list[type[Middleware]]:
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
- "register_middleware",
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(f"Found transport {entry.value.__name__} for scheme '{scheme}'")
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
- "list_registered_transports",
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
+ ]