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
@@ -9,7 +9,10 @@ from attrs import define, field
9
9
 
10
10
  from provide.foundation.logger import get_logger
11
11
  from provide.foundation.transport.base import Request, Response
12
- from provide.foundation.transport.middleware import MiddlewarePipeline, create_default_pipeline
12
+ from provide.foundation.transport.middleware import (
13
+ MiddlewarePipeline,
14
+ create_default_pipeline,
15
+ )
13
16
  from provide.foundation.transport.registry import get_transport
14
17
  from provide.foundation.transport.types import Data, Headers, HTTPMethod, Params
15
18
 
@@ -19,12 +22,12 @@ log = get_logger(__name__)
19
22
  @define
20
23
  class UniversalClient:
21
24
  """Universal client that works with any transport via Hub registry."""
22
-
25
+
23
26
  middleware: MiddlewarePipeline = field(factory=create_default_pipeline)
24
27
  default_headers: Headers = field(factory=dict)
25
28
  default_timeout: float | None = field(default=None)
26
29
  _transports: dict[str, Any] = field(factory=dict, init=False)
27
-
30
+
28
31
  async def request(
29
32
  self,
30
33
  uri: str,
@@ -34,11 +37,11 @@ class UniversalClient:
34
37
  params: Params | None = None,
35
38
  body: Data = None,
36
39
  timeout: float | None = None,
37
- **kwargs
40
+ **kwargs,
38
41
  ) -> Response:
39
42
  """
40
43
  Make a request using appropriate transport.
41
-
44
+
42
45
  Args:
43
46
  uri: Full URI to make request to
44
47
  method: HTTP method or protocol-specific method
@@ -47,19 +50,19 @@ class UniversalClient:
47
50
  body: Request body (dict for JSON, str/bytes for raw)
48
51
  timeout: Request timeout override
49
52
  **kwargs: Additional request metadata
50
-
53
+
51
54
  Returns:
52
55
  Response from the transport
53
56
  """
54
57
  # Normalize method
55
58
  if isinstance(method, HTTPMethod):
56
59
  method = method.value
57
-
60
+
58
61
  # Merge headers
59
62
  request_headers = dict(self.default_headers)
60
63
  if headers:
61
64
  request_headers.update(headers)
62
-
65
+
63
66
  # Create request object
64
67
  request = Request(
65
68
  uri=uri,
@@ -70,92 +73,86 @@ class UniversalClient:
70
73
  timeout=timeout or self.default_timeout,
71
74
  metadata=kwargs,
72
75
  )
73
-
76
+
74
77
  # Process through middleware
75
78
  request = await self.middleware.process_request(request)
76
-
79
+
77
80
  try:
78
81
  # Get transport for this URI
79
82
  transport = await self._get_transport(request.transport_type.value)
80
-
83
+
81
84
  # Execute request
82
85
  response = await transport.execute(request)
83
-
86
+
84
87
  # Process response through middleware
85
88
  response = await self.middleware.process_response(response)
86
-
89
+
87
90
  return response
88
-
91
+
89
92
  except Exception as e:
90
93
  # Process error through middleware
91
94
  e = await self.middleware.process_error(e, request)
92
95
  raise e
93
-
96
+
94
97
  async def stream(
95
- self,
96
- uri: str,
97
- method: str | HTTPMethod = HTTPMethod.GET,
98
- **kwargs
98
+ self, uri: str, method: str | HTTPMethod = HTTPMethod.GET, **kwargs
99
99
  ) -> AsyncIterator[bytes]:
100
100
  """
101
101
  Stream data from URI.
102
-
102
+
103
103
  Args:
104
104
  uri: URI to stream from
105
105
  method: HTTP method or protocol-specific method
106
106
  **kwargs: Additional request parameters
107
-
107
+
108
108
  Yields:
109
109
  Chunks of response data
110
110
  """
111
111
  # Normalize method
112
112
  if isinstance(method, HTTPMethod):
113
113
  method = method.value
114
-
114
+
115
115
  # Create request
116
116
  request = Request(
117
- uri=uri,
118
- method=method,
119
- headers=dict(self.default_headers),
120
- **kwargs
117
+ uri=uri, method=method, headers=dict(self.default_headers), **kwargs
121
118
  )
122
-
119
+
123
120
  # Get transport
124
121
  transport = await self._get_transport(request.transport_type.value)
125
-
122
+
126
123
  # Stream response
127
124
  log.info(f"🌊 Streaming {method} {uri}")
128
125
  async for chunk in transport.stream(request):
129
126
  yield chunk
130
-
127
+
131
128
  async def get(self, uri: str, **kwargs) -> Response:
132
129
  """GET request."""
133
130
  return await self.request(uri, HTTPMethod.GET, **kwargs)
134
-
131
+
135
132
  async def post(self, uri: str, **kwargs) -> Response:
136
133
  """POST request."""
137
134
  return await self.request(uri, HTTPMethod.POST, **kwargs)
138
-
135
+
139
136
  async def put(self, uri: str, **kwargs) -> Response:
140
137
  """PUT request."""
141
138
  return await self.request(uri, HTTPMethod.PUT, **kwargs)
142
-
139
+
143
140
  async def patch(self, uri: str, **kwargs) -> Response:
144
141
  """PATCH request."""
145
142
  return await self.request(uri, HTTPMethod.PATCH, **kwargs)
146
-
143
+
147
144
  async def delete(self, uri: str, **kwargs) -> Response:
148
145
  """DELETE request."""
149
146
  return await self.request(uri, HTTPMethod.DELETE, **kwargs)
150
-
147
+
151
148
  async def head(self, uri: str, **kwargs) -> Response:
152
149
  """HEAD request."""
153
150
  return await self.request(uri, HTTPMethod.HEAD, **kwargs)
154
-
151
+
155
152
  async def options(self, uri: str, **kwargs) -> Response:
156
153
  """OPTIONS request."""
157
154
  return await self.request(uri, HTTPMethod.OPTIONS, **kwargs)
158
-
155
+
159
156
  async def _get_transport(self, scheme: str) -> Any:
160
157
  """Get or create transport for scheme."""
161
158
  if scheme not in self._transports:
@@ -163,13 +160,13 @@ class UniversalClient:
163
160
  transport = get_transport(f"{scheme}://example.com")
164
161
  await transport.connect()
165
162
  self._transports[scheme] = transport
166
-
163
+
167
164
  return self._transports[scheme]
168
-
165
+
169
166
  async def __aenter__(self) -> "UniversalClient":
170
167
  """Context manager entry."""
171
168
  return self
172
-
169
+
173
170
  async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
174
171
  """Context manager exit - cleanup all transports."""
175
172
  for transport in self._transports.values():
@@ -193,9 +190,7 @@ def get_default_client() -> UniversalClient:
193
190
 
194
191
 
195
192
  async def request(
196
- uri: str,
197
- method: str | HTTPMethod = HTTPMethod.GET,
198
- **kwargs
193
+ uri: str, method: str | HTTPMethod = HTTPMethod.GET, **kwargs
199
194
  ) -> Response:
200
195
  """Make a request using the default client."""
201
196
  client = get_default_client()
@@ -253,14 +248,14 @@ async def stream(uri: str, **kwargs) -> AsyncIterator[bytes]:
253
248
 
254
249
  __all__ = [
255
250
  "UniversalClient",
256
- "get_default_client",
257
- "request",
258
- "get",
259
- "post",
260
- "put",
261
- "patch",
262
251
  "delete",
252
+ "get",
253
+ "get_default_client",
263
254
  "head",
264
255
  "options",
256
+ "patch",
257
+ "post",
258
+ "put",
259
+ "request",
265
260
  "stream",
266
- ]
261
+ ]
@@ -4,7 +4,6 @@ Transport configuration with Foundation config integration.
4
4
 
5
5
  from attrs import define
6
6
 
7
- from provide.foundation.config.env import RuntimeConfig
8
7
  from provide.foundation.config.base import field
9
8
  from provide.foundation.config.converters import (
10
9
  parse_bool_extended,
@@ -12,6 +11,7 @@ from provide.foundation.config.converters import (
12
11
  validate_non_negative,
13
12
  validate_positive,
14
13
  )
14
+ from provide.foundation.config.env import RuntimeConfig
15
15
  from provide.foundation.config.loader import RuntimeConfigLoader
16
16
  from provide.foundation.config.manager import register_config
17
17
  from provide.foundation.logger import get_logger
@@ -22,7 +22,7 @@ log = get_logger(__name__)
22
22
  @define(slots=True, repr=False)
23
23
  class TransportConfig(RuntimeConfig):
24
24
  """Base configuration for all transports."""
25
-
25
+
26
26
  timeout: float = field(
27
27
  default=30.0,
28
28
  env_var="PROVIDE_TRANSPORT_TIMEOUT",
@@ -50,13 +50,12 @@ class TransportConfig(RuntimeConfig):
50
50
  converter=parse_bool_extended,
51
51
  description="Whether to verify SSL certificates",
52
52
  )
53
-
54
53
 
55
54
 
56
55
  @define(slots=True, repr=False)
57
56
  class HTTPConfig(TransportConfig):
58
57
  """HTTP-specific configuration."""
59
-
58
+
60
59
  pool_connections: int = field(
61
60
  default=10,
62
61
  env_var="PROVIDE_HTTP_POOL_CONNECTIONS",
@@ -90,7 +89,6 @@ class HTTPConfig(TransportConfig):
90
89
  validator=validate_non_negative,
91
90
  description="Maximum number of redirects to follow",
92
91
  )
93
-
94
92
 
95
93
 
96
94
  async def register_transport_configs() -> None:
@@ -106,10 +104,10 @@ async def register_transport_configs() -> None:
106
104
  "max_retries": 3,
107
105
  "retry_backoff_factor": 0.5,
108
106
  "verify_ssl": True,
109
- }
107
+ },
110
108
  )
111
-
112
- # Register HTTPConfig
109
+
110
+ # Register HTTPConfig
113
111
  await register_config(
114
112
  name="transport.http",
115
113
  config=None, # Will be loaded on demand
@@ -124,17 +122,17 @@ async def register_transport_configs() -> None:
124
122
  "follow_redirects": True,
125
123
  "http2": False,
126
124
  "max_redirects": 5,
127
- }
125
+ },
128
126
  )
129
-
127
+
130
128
  log.trace("Successfully registered transport configurations with ConfigManager")
131
-
129
+
132
130
  except Exception as e:
133
131
  log.warning("Failed to register transport configurations", error=str(e))
134
132
 
135
133
 
136
134
  __all__ = [
137
- "TransportConfig",
138
135
  "HTTPConfig",
136
+ "TransportConfig",
139
137
  "register_transport_configs",
140
- ]
138
+ ]
@@ -12,38 +12,29 @@ if TYPE_CHECKING:
12
12
 
13
13
  class TransportError(FoundationError):
14
14
  """Base transport error."""
15
-
16
- def __init__(
17
- self,
18
- message: str,
19
- *,
20
- request: "Request | None" = None,
21
- **kwargs
22
- ):
15
+
16
+ def __init__(self, message: str, *, request: "Request | None" = None, **kwargs):
23
17
  super().__init__(message, **kwargs)
24
18
  self.request = request
25
19
 
26
20
 
27
21
  class TransportConnectionError(TransportError):
28
22
  """Transport connection failed."""
23
+
29
24
  pass
30
25
 
31
26
 
32
27
  class TransportTimeoutError(TransportError):
33
28
  """Transport request timed out."""
29
+
34
30
  pass
35
31
 
36
32
 
37
33
  class HTTPResponseError(TransportError):
38
34
  """HTTP response error (4xx/5xx status codes)."""
39
-
35
+
40
36
  def __init__(
41
- self,
42
- message: str,
43
- *,
44
- status_code: int,
45
- response: "Response",
46
- **kwargs
37
+ self, message: str, *, status_code: int, response: "Response", **kwargs
47
38
  ):
48
39
  super().__init__(message, **kwargs)
49
40
  self.status_code = status_code
@@ -52,28 +43,23 @@ class HTTPResponseError(TransportError):
52
43
 
53
44
  class TransportConfigurationError(TransportError):
54
45
  """Transport configuration error."""
46
+
55
47
  pass
56
48
 
57
49
 
58
50
  class TransportNotFoundError(TransportError):
59
51
  """No transport found for the given URI scheme."""
60
-
61
- def __init__(
62
- self,
63
- message: str,
64
- *,
65
- scheme: str,
66
- **kwargs
67
- ):
52
+
53
+ def __init__(self, message: str, *, scheme: str, **kwargs):
68
54
  super().__init__(message, **kwargs)
69
55
  self.scheme = scheme
70
56
 
71
57
 
72
58
  __all__ = [
73
- "TransportError",
74
- "TransportConnectionError",
75
- "TransportTimeoutError",
76
59
  "HTTPResponseError",
77
60
  "TransportConfigurationError",
61
+ "TransportConnectionError",
62
+ "TransportError",
78
63
  "TransportNotFoundError",
79
- ]
64
+ "TransportTimeoutError",
65
+ ]
@@ -2,18 +2,16 @@
2
2
  HTTP/HTTPS transport implementation using httpx.
3
3
  """
4
4
 
5
- import time
6
5
  from collections.abc import AsyncIterator
7
- from typing import Any
6
+ import time
8
7
 
9
- import httpx
10
8
  from attrs import define, field
9
+ import httpx
11
10
 
12
11
  from provide.foundation.logger import get_logger
13
12
  from provide.foundation.transport.base import Request, Response, TransportBase
14
13
  from provide.foundation.transport.config import HTTPConfig
15
14
  from provide.foundation.transport.errors import (
16
- HTTPResponseError,
17
15
  TransportConnectionError,
18
16
  TransportTimeoutError,
19
17
  )
@@ -25,28 +23,28 @@ log = get_logger(__name__)
25
23
  @define
26
24
  class HTTPTransport(TransportBase):
27
25
  """HTTP/HTTPS transport using httpx backend."""
28
-
26
+
29
27
  SCHEMES = ["http", "https"]
30
-
28
+
31
29
  config: HTTPConfig = field(factory=HTTPConfig.from_env)
32
30
  _client: httpx.AsyncClient | None = field(default=None, init=False)
33
-
31
+
34
32
  def supports(self, transport_type: TransportType) -> bool:
35
33
  """Check if this transport supports the given type."""
36
34
  return transport_type.value in self.SCHEMES
37
-
35
+
38
36
  async def connect(self) -> None:
39
37
  """Initialize httpx client with configuration."""
40
38
  if self._client is not None:
41
39
  return
42
-
40
+
43
41
  limits = httpx.Limits(
44
42
  max_connections=self.config.pool_connections,
45
43
  max_keepalive_connections=self.config.pool_maxsize,
46
44
  )
47
-
45
+
48
46
  timeout = httpx.Timeout(self.config.timeout)
49
-
47
+
50
48
  self._client = httpx.AsyncClient(
51
49
  limits=limits,
52
50
  timeout=timeout,
@@ -55,35 +53,37 @@ class HTTPTransport(TransportBase):
55
53
  max_redirects=self.config.max_redirects,
56
54
  http2=self.config.http2,
57
55
  )
58
-
59
- log.trace("HTTP transport connected",
60
- pool_connections=self.config.pool_connections,
61
- http2=self.config.http2)
62
-
56
+
57
+ log.trace(
58
+ "HTTP transport connected",
59
+ pool_connections=self.config.pool_connections,
60
+ http2=self.config.http2,
61
+ )
62
+
63
63
  async def disconnect(self) -> None:
64
64
  """Close httpx client."""
65
65
  if self._client is not None:
66
66
  await self._client.aclose()
67
67
  self._client = None
68
68
  log.trace("HTTP transport disconnected")
69
-
69
+
70
70
  async def execute(self, request: Request) -> Response:
71
71
  """Execute HTTP request."""
72
72
  await self.connect()
73
-
73
+
74
74
  if self._client is None:
75
75
  raise TransportConnectionError("HTTP client not connected")
76
-
76
+
77
77
  # Log request with emoji
78
78
  log.info(f"🚀 {request.method} {request.uri}")
79
-
79
+
80
80
  start_time = time.perf_counter()
81
-
81
+
82
82
  try:
83
83
  # Determine request body format
84
84
  json_data = None
85
85
  data = None
86
-
86
+
87
87
  if request.body is not None:
88
88
  if isinstance(request.body, dict):
89
89
  json_data = request.body
@@ -91,9 +91,8 @@ class HTTPTransport(TransportBase):
91
91
  data = request.body
92
92
  else:
93
93
  # Try to serialize as JSON
94
- import json
95
94
  json_data = request.body
96
-
95
+
97
96
  # Make the request
98
97
  httpx_response = await self._client.request(
99
98
  method=request.method,
@@ -104,13 +103,15 @@ class HTTPTransport(TransportBase):
104
103
  data=data,
105
104
  timeout=request.timeout or self.config.timeout,
106
105
  )
107
-
106
+
108
107
  elapsed_ms = (time.perf_counter() - start_time) * 1000
109
-
108
+
110
109
  # Log response with status emoji
111
110
  status_emoji = self._get_status_emoji(httpx_response.status_code)
112
- log.info(f"{status_emoji} {httpx_response.status_code} ({elapsed_ms:.0f}ms)")
113
-
111
+ log.info(
112
+ f"{status_emoji} {httpx_response.status_code} ({elapsed_ms:.0f}ms)"
113
+ )
114
+
114
115
  # Create response object
115
116
  response = Response(
116
117
  status=httpx_response.status_code,
@@ -126,35 +127,43 @@ class HTTPTransport(TransportBase):
126
127
  elapsed_ms=elapsed_ms,
127
128
  request=request,
128
129
  )
129
-
130
+
130
131
  return response
131
-
132
+
132
133
  except httpx.ConnectError as e:
133
134
  log.error(f"❌ Connection failed: {e}")
134
- raise TransportConnectionError(f"Failed to connect: {e}", request=request) from e
135
-
135
+ raise TransportConnectionError(
136
+ f"Failed to connect: {e}", request=request
137
+ ) from e
138
+
136
139
  except httpx.TimeoutException as e:
137
140
  elapsed_ms = (time.perf_counter() - start_time) * 1000
138
141
  log.error(f"⏱️ Request timed out ({elapsed_ms:.0f}ms)")
139
- raise TransportTimeoutError(f"Request timed out: {e}", request=request) from e
140
-
142
+ raise TransportTimeoutError(
143
+ f"Request timed out: {e}", request=request
144
+ ) from e
145
+
141
146
  except httpx.RequestError as e:
142
147
  log.error(f"❌ Request failed: {e}")
143
- raise TransportConnectionError(f"Request failed: {e}", request=request) from e
144
-
148
+ raise TransportConnectionError(
149
+ f"Request failed: {e}", request=request
150
+ ) from e
151
+
145
152
  except Exception as e:
146
153
  log.error(f"❌ Unexpected error: {e}", exc_info=True)
147
- raise TransportConnectionError(f"Unexpected error: {e}", request=request) from e
148
-
154
+ raise TransportConnectionError(
155
+ f"Unexpected error: {e}", request=request
156
+ ) from e
157
+
149
158
  async def stream(self, request: Request) -> AsyncIterator[bytes]:
150
159
  """Stream HTTP response."""
151
160
  await self.connect()
152
-
161
+
153
162
  if self._client is None:
154
163
  raise TransportConnectionError("HTTP client not connected")
155
-
164
+
156
165
  log.info(f"🌊 Streaming {request.method} {request.uri}")
157
-
166
+
158
167
  try:
159
168
  async with self._client.stream(
160
169
  method=request.method,
@@ -163,24 +172,29 @@ class HTTPTransport(TransportBase):
163
172
  params=request.params,
164
173
  timeout=request.timeout or self.config.timeout,
165
174
  ) as response:
166
-
167
175
  # Log response start
168
176
  status_emoji = self._get_status_emoji(response.status_code)
169
177
  log.info(f"{status_emoji} {response.status_code} (streaming)")
170
-
178
+
171
179
  # Stream the response
172
180
  async for chunk in response.aiter_bytes():
173
181
  yield chunk
174
-
182
+
175
183
  except httpx.ConnectError as e:
176
- raise TransportConnectionError(f"Failed to connect: {e}", request=request) from e
177
-
184
+ raise TransportConnectionError(
185
+ f"Failed to connect: {e}", request=request
186
+ ) from e
187
+
178
188
  except httpx.TimeoutException as e:
179
- raise TransportTimeoutError(f"Stream timed out: {e}", request=request) from e
180
-
189
+ raise TransportTimeoutError(
190
+ f"Stream timed out: {e}", request=request
191
+ ) from e
192
+
181
193
  except httpx.RequestError as e:
182
- raise TransportConnectionError(f"Stream failed: {e}", request=request) from e
183
-
194
+ raise TransportConnectionError(
195
+ f"Stream failed: {e}", request=request
196
+ ) from e
197
+
184
198
  def _get_status_emoji(self, status_code: int) -> str:
185
199
  """Get emoji for HTTP status code."""
186
200
  if 200 <= status_code < 300:
@@ -200,7 +214,7 @@ def _register_http_transport():
200
214
  """Register HTTP transport with the Hub."""
201
215
  try:
202
216
  from provide.foundation.transport.registry import register_transport
203
-
217
+
204
218
  register_transport(
205
219
  TransportType.HTTP,
206
220
  HTTPTransport,
@@ -208,16 +222,16 @@ def _register_http_transport():
208
222
  description="HTTP/HTTPS transport using httpx",
209
223
  version="1.0.0",
210
224
  )
211
-
225
+
212
226
  # Also register HTTPS explicitly
213
227
  register_transport(
214
228
  TransportType.HTTPS,
215
229
  HTTPTransport,
216
230
  schemes=HTTPTransport.SCHEMES,
217
- description="HTTP/HTTPS transport using httpx",
231
+ description="HTTP/HTTPS transport using httpx",
218
232
  version="1.0.0",
219
233
  )
220
-
234
+
221
235
  except ImportError:
222
236
  # Registry not available yet, will be registered later
223
237
  pass
@@ -229,4 +243,4 @@ _register_http_transport()
229
243
 
230
244
  __all__ = [
231
245
  "HTTPTransport",
232
- ]
246
+ ]