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
@@ -14,28 +14,28 @@ Key Features:
14
14
 
15
15
  Example Usage:
16
16
  >>> from provide.foundation.transport import get, post
17
- >>>
17
+ >>>
18
18
  >>> # Simple requests
19
19
  >>> response = await get("https://api.example.com/users")
20
20
  >>> data = response.json()
21
- >>>
21
+ >>>
22
22
  >>> # POST with JSON body
23
23
  >>> response = await post(
24
24
  ... "https://api.example.com/users",
25
25
  ... body={"name": "John", "email": "john@example.com"}
26
26
  ... )
27
- >>>
27
+ >>>
28
28
  >>> # Using client for multiple requests
29
29
  >>> from provide.foundation.transport import UniversalClient
30
- >>>
30
+ >>>
31
31
  >>> async with UniversalClient() as client:
32
32
  ... users = await client.get("https://api.example.com/users")
33
33
  ... posts = await client.get("https://api.example.com/posts")
34
- >>>
34
+ >>>
35
35
  >>> # Custom transport registration
36
36
  >>> from provide.foundation.transport import register_transport
37
37
  >>> from provide.foundation.transport.types import TransportType
38
- >>>
38
+ >>>
39
39
  >>> register_transport(TransportType("custom"), MyCustomTransport)
40
40
 
41
41
  Environment Configuration:
@@ -43,7 +43,7 @@ Environment Configuration:
43
43
  TRANSPORT_MAX_RETRIES=3
44
44
  TRANSPORT_RETRY_BACKOFF_FACTOR=0.5
45
45
  TRANSPORT_VERIFY_SSL=true
46
-
46
+
47
47
  HTTP_POOL_CONNECTIONS=10
48
48
  HTTP_POOL_MAXSIZE=100
49
49
  HTTP_FOLLOW_REDIRECTS=true
@@ -54,9 +54,23 @@ Environment Configuration:
54
54
  # Core transport abstractions
55
55
  from provide.foundation.transport.base import Request, Response
56
56
 
57
+ # High-level client API
58
+ from provide.foundation.transport.client import (
59
+ UniversalClient,
60
+ delete,
61
+ get,
62
+ get_default_client,
63
+ head,
64
+ options,
65
+ patch,
66
+ post,
67
+ put,
68
+ request,
69
+ stream,
70
+ )
71
+
57
72
  # Transport types and configuration
58
73
  from provide.foundation.transport.config import HTTPConfig, TransportConfig
59
- from provide.foundation.transport.types import HTTPMethod, TransportType
60
74
 
61
75
  # Error types
62
76
  from provide.foundation.transport.errors import (
@@ -87,45 +101,26 @@ from provide.foundation.transport.registry import (
87
101
  list_registered_transports,
88
102
  register_transport,
89
103
  )
90
-
91
- # High-level client API
92
- from provide.foundation.transport.client import (
93
- UniversalClient,
94
- delete,
95
- get,
96
- get_default_client,
97
- head,
98
- options,
99
- patch,
100
- post,
101
- put,
102
- request,
103
- stream,
104
- )
104
+ from provide.foundation.transport.types import HTTPMethod, TransportType
105
105
 
106
106
  __all__ = [
107
107
  # Core abstractions
108
108
  "Request",
109
109
  "Response",
110
-
111
110
  # Configuration
112
111
  "TransportConfig",
113
112
  "HTTPConfig",
114
-
115
113
  # Types
116
114
  "TransportType",
117
115
  "HTTPMethod",
118
-
119
116
  # Errors
120
117
  "TransportError",
121
118
  "TransportConnectionError",
122
- "TransportTimeoutError",
119
+ "TransportTimeoutError",
123
120
  "HTTPResponseError",
124
121
  "TransportNotFoundError",
125
-
126
122
  # Transport implementations
127
123
  "HTTPTransport",
128
-
129
124
  # Middleware
130
125
  "Middleware",
131
126
  "MiddlewarePipeline",
@@ -133,13 +128,11 @@ __all__ = [
133
128
  "RetryMiddleware",
134
129
  "MetricsMiddleware",
135
130
  "create_default_pipeline",
136
-
137
131
  # Registry
138
132
  "register_transport",
139
133
  "get_transport",
140
134
  "get_transport_info",
141
135
  "list_registered_transports",
142
-
143
136
  # Client API
144
137
  "UniversalClient",
145
138
  "get_default_client",
@@ -147,9 +140,9 @@ __all__ = [
147
140
  "get",
148
141
  "post",
149
142
  "put",
150
- "patch",
143
+ "patch",
151
144
  "delete",
152
145
  "head",
153
146
  "options",
154
147
  "stream",
155
- ]
148
+ ]
@@ -2,7 +2,6 @@
2
2
  Core transport abstractions.
3
3
  """
4
4
 
5
- import time
6
5
  from abc import ABC, abstractmethod
7
6
  from collections.abc import AsyncIterator
8
7
  from typing import Any, Protocol, runtime_checkable
@@ -18,7 +17,7 @@ log = get_logger(__name__)
18
17
  @define
19
18
  class Request:
20
19
  """Protocol-agnostic request."""
21
-
20
+
22
21
  uri: str
23
22
  method: str = "GET"
24
23
  headers: Headers = field(factory=dict)
@@ -26,7 +25,7 @@ class Request:
26
25
  body: Data = None
27
26
  timeout: float | None = None
28
27
  metadata: dict[str, Any] = field(factory=dict)
29
-
28
+
30
29
  @property
31
30
  def transport_type(self) -> TransportType:
32
31
  """Infer transport type from URI scheme."""
@@ -36,7 +35,7 @@ class Request:
36
35
  except ValueError:
37
36
  log.trace(f"Unknown scheme '{scheme}', defaulting to HTTP")
38
37
  return TransportType.HTTP
39
-
38
+
40
39
  @property
41
40
  def base_url(self) -> str:
42
41
  """Extract base URL from URI."""
@@ -49,43 +48,44 @@ class Request:
49
48
  @define
50
49
  class Response:
51
50
  """Protocol-agnostic response."""
52
-
51
+
53
52
  status: int
54
53
  headers: Headers = field(factory=dict)
55
54
  body: bytes | str | None = None
56
55
  metadata: dict[str, Any] = field(factory=dict)
57
56
  elapsed_ms: float = 0
58
57
  request: Request | None = None
59
-
58
+
60
59
  def is_success(self) -> bool:
61
60
  """Check if response indicates success."""
62
61
  return 200 <= self.status < 300
63
-
62
+
64
63
  def json(self) -> Any:
65
64
  """Parse response body as JSON."""
66
65
  import json
67
-
66
+
68
67
  if isinstance(self.body, bytes):
69
- return json.loads(self.body.decode('utf-8'))
68
+ return json.loads(self.body.decode("utf-8"))
70
69
  elif isinstance(self.body, str):
71
70
  return json.loads(self.body)
72
71
  else:
73
72
  raise ValueError("Response body is not JSON-parseable")
74
-
73
+
75
74
  @property
76
75
  def text(self) -> str:
77
76
  """Get response body as text."""
78
77
  if isinstance(self.body, bytes):
79
- return self.body.decode('utf-8')
78
+ return self.body.decode("utf-8")
80
79
  elif isinstance(self.body, str):
81
80
  return self.body
82
81
  else:
83
82
  return str(self.body or "")
84
-
83
+
85
84
  def raise_for_status(self) -> None:
86
85
  """Raise error if response status indicates failure."""
87
86
  if not self.is_success():
88
87
  from provide.foundation.transport.errors import HTTPResponseError
88
+
89
89
  raise HTTPResponseError(
90
90
  f"Request failed with status {self.status}",
91
91
  status_code=self.status,
@@ -96,32 +96,32 @@ class Response:
96
96
  @runtime_checkable
97
97
  class Transport(Protocol):
98
98
  """Abstract transport protocol."""
99
-
99
+
100
100
  async def execute(self, request: Request) -> Response:
101
101
  """Execute a request and return response."""
102
102
  ...
103
-
103
+
104
104
  async def stream(self, request: Request) -> AsyncIterator[bytes]:
105
105
  """Stream response data."""
106
106
  ...
107
-
107
+
108
108
  async def connect(self) -> None:
109
109
  """Establish connection if needed."""
110
110
  ...
111
-
111
+
112
112
  async def disconnect(self) -> None:
113
113
  """Close connection if needed."""
114
114
  ...
115
-
115
+
116
116
  def supports(self, transport_type: TransportType) -> bool:
117
117
  """Check if this transport handles the given type."""
118
118
  ...
119
-
119
+
120
120
  async def __aenter__(self) -> "Transport":
121
121
  """Context manager entry."""
122
122
  await self.connect()
123
123
  return self
124
-
124
+
125
125
  async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
126
126
  """Context manager exit."""
127
127
  await self.disconnect()
@@ -129,43 +129,45 @@ class Transport(Protocol):
129
129
 
130
130
  class TransportBase(ABC):
131
131
  """Base class for transport implementations."""
132
-
132
+
133
133
  def __init__(self):
134
134
  self._logger = get_logger(self.__class__.__name__)
135
-
135
+
136
136
  @abstractmethod
137
137
  async def execute(self, request: Request) -> Response:
138
138
  """Execute a request and return response."""
139
139
  pass
140
-
140
+
141
141
  @abstractmethod
142
142
  def supports(self, transport_type: TransportType) -> bool:
143
143
  """Check if this transport handles the given type."""
144
144
  pass
145
-
145
+
146
146
  async def connect(self) -> None:
147
147
  """Default connect implementation."""
148
148
  self._logger.trace("Transport connecting")
149
-
149
+
150
150
  async def disconnect(self) -> None:
151
151
  """Default disconnect implementation."""
152
152
  self._logger.trace("Transport disconnecting")
153
-
153
+
154
154
  async def stream(self, request: Request) -> AsyncIterator[bytes]:
155
155
  """Default streaming implementation (not supported)."""
156
- raise NotImplementedError(f"{self.__class__.__name__} does not support streaming")
157
-
156
+ raise NotImplementedError(
157
+ f"{self.__class__.__name__} does not support streaming"
158
+ )
159
+
158
160
  async def __aenter__(self) -> "TransportBase":
159
161
  await self.connect()
160
162
  return self
161
-
163
+
162
164
  async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
163
165
  await self.disconnect()
164
166
 
165
167
 
166
168
  __all__ = [
167
169
  "Request",
168
- "Response",
170
+ "Response",
169
171
  "Transport",
170
172
  "TransportBase",
171
- ]
173
+ ]
@@ -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
+ ]