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,164 +2,93 @@
2
2
  Transport configuration with Foundation config integration.
3
3
  """
4
4
 
5
- import os
6
-
7
5
  from attrs import define
8
6
 
9
- from provide.foundation.config import BaseConfig, field
7
+ from provide.foundation.config.base import field
8
+ from provide.foundation.config.converters import (
9
+ parse_bool_extended,
10
+ parse_float_with_validation,
11
+ validate_non_negative,
12
+ validate_positive,
13
+ )
14
+ from provide.foundation.config.env import RuntimeConfig
10
15
  from provide.foundation.config.loader import RuntimeConfigLoader
11
16
  from provide.foundation.config.manager import register_config
12
- from provide.foundation.config.types import ConfigSource
13
17
  from provide.foundation.logger import get_logger
14
18
 
15
19
  log = get_logger(__name__)
16
20
 
17
21
 
18
22
  @define(slots=True, repr=False)
19
- class TransportConfig(BaseConfig):
23
+ class TransportConfig(RuntimeConfig):
20
24
  """Base configuration for all transports."""
21
-
25
+
22
26
  timeout: float = field(
23
27
  default=30.0,
24
28
  env_var="PROVIDE_TRANSPORT_TIMEOUT",
29
+ converter=lambda x: parse_float_with_validation(x, min_val=0.0) if x else 30.0,
30
+ validator=validate_positive,
25
31
  description="Request timeout in seconds",
26
32
  )
27
33
  max_retries: int = field(
28
34
  default=3,
29
35
  env_var="PROVIDE_TRANSPORT_MAX_RETRIES",
36
+ converter=int,
37
+ validator=validate_non_negative,
30
38
  description="Maximum number of retry attempts",
31
39
  )
32
40
  retry_backoff_factor: float = field(
33
41
  default=0.5,
34
- env_var="PROVIDE_TRANSPORT_RETRY_BACKOFF_FACTOR",
42
+ env_var="PROVIDE_TRANSPORT_RETRY_BACKOFF_FACTOR",
43
+ converter=lambda x: parse_float_with_validation(x, min_val=0.0) if x else 0.5,
44
+ validator=validate_non_negative,
35
45
  description="Backoff multiplier for retries",
36
46
  )
37
47
  verify_ssl: bool = field(
38
48
  default=True,
39
49
  env_var="PROVIDE_TRANSPORT_VERIFY_SSL",
50
+ converter=parse_bool_extended,
40
51
  description="Whether to verify SSL certificates",
41
52
  )
42
-
43
- @classmethod
44
- def from_env(cls, strict: bool = True) -> "TransportConfig":
45
- """Load configuration from environment variables."""
46
- config_dict = {}
47
-
48
- if timeout := os.getenv("PROVIDE_TRANSPORT_TIMEOUT"):
49
- try:
50
- config_dict["timeout"] = float(timeout)
51
- except ValueError:
52
- if strict:
53
- log.warning(
54
- "Invalid transport timeout value, using field default",
55
- invalid_value=timeout,
56
- )
57
-
58
- if max_retries := os.getenv("PROVIDE_TRANSPORT_MAX_RETRIES"):
59
- try:
60
- config_dict["max_retries"] = int(max_retries)
61
- except ValueError:
62
- if strict:
63
- log.warning(
64
- "Invalid max retries value, using field default",
65
- invalid_value=max_retries,
66
- )
67
-
68
- if backoff := os.getenv("PROVIDE_TRANSPORT_RETRY_BACKOFF_FACTOR"):
69
- try:
70
- config_dict["retry_backoff_factor"] = float(backoff)
71
- except ValueError:
72
- if strict:
73
- log.warning(
74
- "Invalid backoff factor value, using field default",
75
- invalid_value=backoff,
76
- )
77
-
78
- if verify_ssl := os.getenv("PROVIDE_TRANSPORT_VERIFY_SSL"):
79
- config_dict["verify_ssl"] = verify_ssl.lower() == "true"
80
-
81
- config = cls.from_dict(config_dict, source=ConfigSource.ENV)
82
- log.trace("Loaded transport configuration from environment", config_dict=config_dict)
83
- return config
84
53
 
85
54
 
86
55
  @define(slots=True, repr=False)
87
56
  class HTTPConfig(TransportConfig):
88
57
  """HTTP-specific configuration."""
89
-
58
+
90
59
  pool_connections: int = field(
91
60
  default=10,
92
61
  env_var="PROVIDE_HTTP_POOL_CONNECTIONS",
62
+ converter=int,
63
+ validator=validate_positive,
93
64
  description="Number of connection pools to cache",
94
65
  )
95
66
  pool_maxsize: int = field(
96
67
  default=100,
97
- env_var="PROVIDE_HTTP_POOL_MAXSIZE",
68
+ env_var="PROVIDE_HTTP_POOL_MAXSIZE",
69
+ converter=int,
70
+ validator=validate_positive,
98
71
  description="Maximum number of connections per pool",
99
72
  )
100
73
  follow_redirects: bool = field(
101
74
  default=True,
102
75
  env_var="PROVIDE_HTTP_FOLLOW_REDIRECTS",
76
+ converter=parse_bool_extended,
103
77
  description="Whether to automatically follow redirects",
104
78
  )
105
79
  http2: bool = field(
106
80
  default=False,
107
81
  env_var="PROVIDE_HTTP_USE_HTTP2",
82
+ converter=parse_bool_extended,
108
83
  description="Enable HTTP/2 support",
109
84
  )
110
85
  max_redirects: int = field(
111
86
  default=5,
112
87
  env_var="PROVIDE_HTTP_MAX_REDIRECTS",
88
+ converter=int,
89
+ validator=validate_non_negative,
113
90
  description="Maximum number of redirects to follow",
114
91
  )
115
-
116
- @classmethod
117
- def from_env(cls, strict: bool = True) -> "HTTPConfig":
118
- """Load HTTP configuration from environment variables."""
119
- # Start with base transport config
120
- base_config = TransportConfig.from_env(strict=strict)
121
- config_dict = base_config.to_dict(include_sensitive=True)
122
-
123
- # Add HTTP-specific settings
124
- if pool_connections := os.getenv("PROVIDE_HTTP_POOL_CONNECTIONS"):
125
- try:
126
- config_dict["pool_connections"] = int(pool_connections)
127
- except ValueError:
128
- if strict:
129
- log.warning(
130
- "Invalid pool connections value, using field default",
131
- invalid_value=pool_connections,
132
- )
133
-
134
- if pool_maxsize := os.getenv("PROVIDE_HTTP_POOL_MAXSIZE"):
135
- try:
136
- config_dict["pool_maxsize"] = int(pool_maxsize)
137
- except ValueError:
138
- if strict:
139
- log.warning(
140
- "Invalid pool maxsize value, using field default",
141
- invalid_value=pool_maxsize,
142
- )
143
-
144
- if follow_redirects := os.getenv("PROVIDE_HTTP_FOLLOW_REDIRECTS"):
145
- config_dict["follow_redirects"] = follow_redirects.lower() == "true"
146
-
147
- if http2 := os.getenv("PROVIDE_HTTP_USE_HTTP2"):
148
- config_dict["http2"] = http2.lower() == "true"
149
-
150
- if max_redirects := os.getenv("PROVIDE_HTTP_MAX_REDIRECTS"):
151
- try:
152
- config_dict["max_redirects"] = int(max_redirects)
153
- except ValueError:
154
- if strict:
155
- log.warning(
156
- "Invalid max redirects value, using field default",
157
- invalid_value=max_redirects,
158
- )
159
-
160
- config = cls.from_dict(config_dict, source=ConfigSource.ENV)
161
- log.trace("Loaded HTTP configuration from environment", config_dict=config_dict)
162
- return config
163
92
 
164
93
 
165
94
  async def register_transport_configs() -> None:
@@ -175,10 +104,10 @@ async def register_transport_configs() -> None:
175
104
  "max_retries": 3,
176
105
  "retry_backoff_factor": 0.5,
177
106
  "verify_ssl": True,
178
- }
107
+ },
179
108
  )
180
-
181
- # Register HTTPConfig
109
+
110
+ # Register HTTPConfig
182
111
  await register_config(
183
112
  name="transport.http",
184
113
  config=None, # Will be loaded on demand
@@ -193,17 +122,17 @@ async def register_transport_configs() -> None:
193
122
  "follow_redirects": True,
194
123
  "http2": False,
195
124
  "max_redirects": 5,
196
- }
125
+ },
197
126
  )
198
-
127
+
199
128
  log.trace("Successfully registered transport configurations with ConfigManager")
200
-
129
+
201
130
  except Exception as e:
202
131
  log.warning("Failed to register transport configurations", error=str(e))
203
132
 
204
133
 
205
134
  __all__ = [
206
- "TransportConfig",
207
135
  "HTTPConfig",
136
+ "TransportConfig",
208
137
  "register_transport_configs",
209
- ]
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
+ ]