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