provide-foundation 0.0.0.dev0__py3-none-any.whl → 0.0.0.dev2__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 (161) hide show
  1. provide/foundation/__init__.py +41 -23
  2. provide/foundation/archive/__init__.py +23 -0
  3. provide/foundation/archive/base.py +70 -0
  4. provide/foundation/archive/bzip2.py +157 -0
  5. provide/foundation/archive/gzip.py +159 -0
  6. provide/foundation/archive/operations.py +334 -0
  7. provide/foundation/archive/tar.py +164 -0
  8. provide/foundation/archive/zip.py +203 -0
  9. provide/foundation/cli/__init__.py +2 -2
  10. provide/foundation/cli/commands/deps.py +13 -7
  11. provide/foundation/cli/commands/logs/__init__.py +1 -1
  12. provide/foundation/cli/commands/logs/query.py +1 -1
  13. provide/foundation/cli/commands/logs/send.py +1 -1
  14. provide/foundation/cli/commands/logs/tail.py +1 -1
  15. provide/foundation/cli/decorators.py +11 -10
  16. provide/foundation/cli/main.py +1 -1
  17. provide/foundation/cli/testing.py +2 -35
  18. provide/foundation/cli/utils.py +21 -17
  19. provide/foundation/config/__init__.py +35 -2
  20. provide/foundation/config/base.py +2 -2
  21. provide/foundation/config/converters.py +479 -0
  22. provide/foundation/config/defaults.py +67 -0
  23. provide/foundation/config/env.py +4 -19
  24. provide/foundation/config/loader.py +9 -3
  25. provide/foundation/config/sync.py +19 -4
  26. provide/foundation/console/input.py +5 -5
  27. provide/foundation/console/output.py +35 -13
  28. provide/foundation/context/__init__.py +8 -4
  29. provide/foundation/context/core.py +85 -109
  30. provide/foundation/core.py +1 -2
  31. provide/foundation/crypto/__init__.py +2 -0
  32. provide/foundation/crypto/certificates/__init__.py +34 -0
  33. provide/foundation/crypto/certificates/base.py +173 -0
  34. provide/foundation/crypto/certificates/certificate.py +290 -0
  35. provide/foundation/crypto/certificates/factory.py +213 -0
  36. provide/foundation/crypto/certificates/generator.py +138 -0
  37. provide/foundation/crypto/certificates/loader.py +130 -0
  38. provide/foundation/crypto/certificates/operations.py +198 -0
  39. provide/foundation/crypto/certificates/trust.py +107 -0
  40. provide/foundation/errors/__init__.py +2 -3
  41. provide/foundation/errors/decorators.py +0 -231
  42. provide/foundation/errors/types.py +0 -97
  43. provide/foundation/eventsets/__init__.py +0 -0
  44. provide/foundation/eventsets/display.py +84 -0
  45. provide/foundation/eventsets/registry.py +160 -0
  46. provide/foundation/eventsets/resolver.py +192 -0
  47. provide/foundation/eventsets/sets/das.py +128 -0
  48. provide/foundation/eventsets/sets/database.py +125 -0
  49. provide/foundation/eventsets/sets/http.py +153 -0
  50. provide/foundation/eventsets/sets/llm.py +139 -0
  51. provide/foundation/eventsets/sets/task_queue.py +107 -0
  52. provide/foundation/eventsets/types.py +70 -0
  53. provide/foundation/file/directory.py +13 -22
  54. provide/foundation/file/lock.py +3 -1
  55. provide/foundation/hub/components.py +77 -515
  56. provide/foundation/hub/config.py +151 -0
  57. provide/foundation/hub/discovery.py +62 -0
  58. provide/foundation/hub/handlers.py +81 -0
  59. provide/foundation/hub/lifecycle.py +194 -0
  60. provide/foundation/hub/manager.py +4 -4
  61. provide/foundation/hub/processors.py +44 -0
  62. provide/foundation/integrations/__init__.py +11 -0
  63. provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
  64. provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
  65. provide/foundation/{observability → integrations}/openobserve/client.py +12 -12
  66. provide/foundation/{observability → integrations}/openobserve/commands.py +3 -3
  67. provide/foundation/integrations/openobserve/config.py +37 -0
  68. provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
  69. provide/foundation/{observability → integrations}/openobserve/otlp.py +1 -1
  70. provide/foundation/{observability → integrations}/openobserve/search.py +2 -2
  71. provide/foundation/{observability → integrations}/openobserve/streaming.py +4 -4
  72. provide/foundation/logger/__init__.py +3 -10
  73. provide/foundation/logger/config/logging.py +68 -298
  74. provide/foundation/logger/config/telemetry.py +41 -121
  75. provide/foundation/logger/core.py +0 -2
  76. provide/foundation/logger/custom_processors.py +1 -0
  77. provide/foundation/logger/factories.py +11 -2
  78. provide/foundation/logger/processors/main.py +20 -84
  79. provide/foundation/logger/setup/__init__.py +5 -1
  80. provide/foundation/logger/setup/coordinator.py +76 -24
  81. provide/foundation/logger/setup/processors.py +2 -9
  82. provide/foundation/logger/trace.py +27 -0
  83. provide/foundation/metrics/otel.py +10 -10
  84. provide/foundation/observability/__init__.py +2 -2
  85. provide/foundation/process/__init__.py +9 -0
  86. provide/foundation/process/exit.py +47 -0
  87. provide/foundation/process/lifecycle.py +115 -59
  88. provide/foundation/resilience/__init__.py +35 -0
  89. provide/foundation/resilience/circuit.py +164 -0
  90. provide/foundation/resilience/decorators.py +220 -0
  91. provide/foundation/resilience/fallback.py +193 -0
  92. provide/foundation/resilience/retry.py +325 -0
  93. provide/foundation/streams/config.py +79 -0
  94. provide/foundation/streams/console.py +7 -8
  95. provide/foundation/streams/core.py +6 -3
  96. provide/foundation/streams/file.py +12 -2
  97. provide/foundation/testing/__init__.py +84 -2
  98. provide/foundation/testing/archive/__init__.py +24 -0
  99. provide/foundation/testing/archive/fixtures.py +217 -0
  100. provide/foundation/testing/cli.py +30 -17
  101. provide/foundation/testing/common/__init__.py +32 -0
  102. provide/foundation/testing/common/fixtures.py +236 -0
  103. provide/foundation/testing/file/__init__.py +40 -0
  104. provide/foundation/testing/file/content_fixtures.py +316 -0
  105. provide/foundation/testing/file/directory_fixtures.py +107 -0
  106. provide/foundation/testing/file/fixtures.py +52 -0
  107. provide/foundation/testing/file/special_fixtures.py +153 -0
  108. provide/foundation/testing/logger.py +117 -11
  109. provide/foundation/testing/mocking/__init__.py +46 -0
  110. provide/foundation/testing/mocking/fixtures.py +331 -0
  111. provide/foundation/testing/process/__init__.py +48 -0
  112. provide/foundation/testing/process/async_fixtures.py +405 -0
  113. provide/foundation/testing/process/fixtures.py +56 -0
  114. provide/foundation/testing/process/subprocess_fixtures.py +209 -0
  115. provide/foundation/testing/threading/__init__.py +38 -0
  116. provide/foundation/testing/threading/basic_fixtures.py +101 -0
  117. provide/foundation/testing/threading/data_fixtures.py +99 -0
  118. provide/foundation/testing/threading/execution_fixtures.py +263 -0
  119. provide/foundation/testing/threading/fixtures.py +54 -0
  120. provide/foundation/testing/threading/sync_fixtures.py +97 -0
  121. provide/foundation/testing/time/__init__.py +32 -0
  122. provide/foundation/testing/time/fixtures.py +409 -0
  123. provide/foundation/testing/transport/__init__.py +30 -0
  124. provide/foundation/testing/transport/fixtures.py +280 -0
  125. provide/foundation/tools/__init__.py +58 -0
  126. provide/foundation/tools/base.py +348 -0
  127. provide/foundation/tools/cache.py +268 -0
  128. provide/foundation/tools/downloader.py +224 -0
  129. provide/foundation/tools/installer.py +254 -0
  130. provide/foundation/tools/registry.py +223 -0
  131. provide/foundation/tools/resolver.py +321 -0
  132. provide/foundation/tools/verifier.py +186 -0
  133. provide/foundation/tracer/otel.py +7 -11
  134. provide/foundation/tracer/spans.py +2 -2
  135. provide/foundation/transport/__init__.py +155 -0
  136. provide/foundation/transport/base.py +171 -0
  137. provide/foundation/transport/client.py +266 -0
  138. provide/foundation/transport/config.py +140 -0
  139. provide/foundation/transport/errors.py +79 -0
  140. provide/foundation/transport/http.py +232 -0
  141. provide/foundation/transport/middleware.py +360 -0
  142. provide/foundation/transport/registry.py +167 -0
  143. provide/foundation/transport/types.py +45 -0
  144. provide/foundation/utils/deps.py +14 -12
  145. provide/foundation/utils/parsing.py +49 -4
  146. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/METADATA +5 -28
  147. provide_foundation-0.0.0.dev2.dist-info/RECORD +225 -0
  148. provide/foundation/cli/commands/logs/generate_old.py +0 -569
  149. provide/foundation/crypto/certificates.py +0 -896
  150. provide/foundation/logger/emoji/__init__.py +0 -44
  151. provide/foundation/logger/emoji/matrix.py +0 -209
  152. provide/foundation/logger/emoji/sets.py +0 -458
  153. provide/foundation/logger/emoji/types.py +0 -56
  154. provide/foundation/logger/setup/emoji_resolver.py +0 -64
  155. provide_foundation-0.0.0.dev0.dist-info/RECORD +0 -149
  156. /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
  157. /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
  158. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/WHEEL +0 -0
  159. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/entry_points.txt +0 -0
  160. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
  161. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,155 @@
1
+ """
2
+ Provide Foundation Transport System
3
+ ==================================
4
+
5
+ Protocol-agnostic transport layer with HTTP/HTTPS support using Foundation Hub registry.
6
+
7
+ Key Features:
8
+ - Hub-based transport registration and discovery
9
+ - Async-first with httpx backend for HTTP/HTTPS
10
+ - Built-in telemetry with foundation.logger
11
+ - Middleware pipeline for extensibility
12
+ - No hardcoded defaults - all configuration from environment
13
+ - Modern Python 3.11+ typing
14
+
15
+ Example Usage:
16
+ >>> from provide.foundation.transport import get, post
17
+ >>>
18
+ >>> # Simple requests
19
+ >>> response = await get("https://api.example.com/users")
20
+ >>> data = response.json()
21
+ >>>
22
+ >>> # POST with JSON body
23
+ >>> response = await post(
24
+ ... "https://api.example.com/users",
25
+ ... body={"name": "John", "email": "john@example.com"}
26
+ ... )
27
+ >>>
28
+ >>> # Using client for multiple requests
29
+ >>> from provide.foundation.transport import UniversalClient
30
+ >>>
31
+ >>> async with UniversalClient() as client:
32
+ ... users = await client.get("https://api.example.com/users")
33
+ ... posts = await client.get("https://api.example.com/posts")
34
+ >>>
35
+ >>> # Custom transport registration
36
+ >>> from provide.foundation.transport import register_transport
37
+ >>> from provide.foundation.transport.types import TransportType
38
+ >>>
39
+ >>> register_transport(TransportType("custom"), MyCustomTransport)
40
+
41
+ Environment Configuration:
42
+ TRANSPORT_TIMEOUT=30.0
43
+ TRANSPORT_MAX_RETRIES=3
44
+ TRANSPORT_RETRY_BACKOFF_FACTOR=0.5
45
+ TRANSPORT_VERIFY_SSL=true
46
+
47
+ HTTP_POOL_CONNECTIONS=10
48
+ HTTP_POOL_MAXSIZE=100
49
+ HTTP_FOLLOW_REDIRECTS=true
50
+ HTTP_USE_HTTP2=true
51
+ HTTP_MAX_REDIRECTS=5
52
+ """
53
+
54
+ # Core transport abstractions
55
+ from provide.foundation.transport.base import Request, Response
56
+
57
+ # Transport types and configuration
58
+ from provide.foundation.transport.config import HTTPConfig, TransportConfig
59
+ from provide.foundation.transport.types import HTTPMethod, TransportType
60
+
61
+ # Error types
62
+ from provide.foundation.transport.errors import (
63
+ HTTPResponseError,
64
+ TransportConnectionError,
65
+ TransportError,
66
+ TransportNotFoundError,
67
+ TransportTimeoutError,
68
+ )
69
+
70
+ # Transport implementations
71
+ from provide.foundation.transport.http import HTTPTransport
72
+
73
+ # Middleware system
74
+ from provide.foundation.transport.middleware import (
75
+ LoggingMiddleware,
76
+ MetricsMiddleware,
77
+ Middleware,
78
+ MiddlewarePipeline,
79
+ RetryMiddleware,
80
+ create_default_pipeline,
81
+ )
82
+
83
+ # Registry and discovery
84
+ from provide.foundation.transport.registry import (
85
+ get_transport,
86
+ get_transport_info,
87
+ list_registered_transports,
88
+ register_transport,
89
+ )
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
+ )
105
+
106
+ __all__ = [
107
+ # Core abstractions
108
+ "Request",
109
+ "Response",
110
+
111
+ # Configuration
112
+ "TransportConfig",
113
+ "HTTPConfig",
114
+
115
+ # Types
116
+ "TransportType",
117
+ "HTTPMethod",
118
+
119
+ # Errors
120
+ "TransportError",
121
+ "TransportConnectionError",
122
+ "TransportTimeoutError",
123
+ "HTTPResponseError",
124
+ "TransportNotFoundError",
125
+
126
+ # Transport implementations
127
+ "HTTPTransport",
128
+
129
+ # Middleware
130
+ "Middleware",
131
+ "MiddlewarePipeline",
132
+ "LoggingMiddleware",
133
+ "RetryMiddleware",
134
+ "MetricsMiddleware",
135
+ "create_default_pipeline",
136
+
137
+ # Registry
138
+ "register_transport",
139
+ "get_transport",
140
+ "get_transport_info",
141
+ "list_registered_transports",
142
+
143
+ # Client API
144
+ "UniversalClient",
145
+ "get_default_client",
146
+ "request",
147
+ "get",
148
+ "post",
149
+ "put",
150
+ "patch",
151
+ "delete",
152
+ "head",
153
+ "options",
154
+ "stream",
155
+ ]
@@ -0,0 +1,171 @@
1
+ """
2
+ Core transport abstractions.
3
+ """
4
+
5
+ import time
6
+ from abc import ABC, abstractmethod
7
+ from collections.abc import AsyncIterator
8
+ from typing import Any, Protocol, runtime_checkable
9
+
10
+ from attrs import define, field
11
+
12
+ from provide.foundation.logger import get_logger
13
+ from provide.foundation.transport.types import Data, Headers, Params, TransportType
14
+
15
+ log = get_logger(__name__)
16
+
17
+
18
+ @define
19
+ class Request:
20
+ """Protocol-agnostic request."""
21
+
22
+ uri: str
23
+ method: str = "GET"
24
+ headers: Headers = field(factory=dict)
25
+ params: Params = field(factory=dict)
26
+ body: Data = None
27
+ timeout: float | None = None
28
+ metadata: dict[str, Any] = field(factory=dict)
29
+
30
+ @property
31
+ def transport_type(self) -> TransportType:
32
+ """Infer transport type from URI scheme."""
33
+ scheme = self.uri.split("://")[0].lower()
34
+ try:
35
+ return TransportType(scheme)
36
+ except ValueError:
37
+ log.trace(f"Unknown scheme '{scheme}', defaulting to HTTP")
38
+ return TransportType.HTTP
39
+
40
+ @property
41
+ def base_url(self) -> str:
42
+ """Extract base URL from URI."""
43
+ parts = self.uri.split("/")
44
+ if len(parts) >= 3:
45
+ return f"{parts[0]}//{parts[2]}"
46
+ return self.uri
47
+
48
+
49
+ @define
50
+ class Response:
51
+ """Protocol-agnostic response."""
52
+
53
+ status: int
54
+ headers: Headers = field(factory=dict)
55
+ body: bytes | str | None = None
56
+ metadata: dict[str, Any] = field(factory=dict)
57
+ elapsed_ms: float = 0
58
+ request: Request | None = None
59
+
60
+ def is_success(self) -> bool:
61
+ """Check if response indicates success."""
62
+ return 200 <= self.status < 300
63
+
64
+ def json(self) -> Any:
65
+ """Parse response body as JSON."""
66
+ import json
67
+
68
+ if isinstance(self.body, bytes):
69
+ return json.loads(self.body.decode('utf-8'))
70
+ elif isinstance(self.body, str):
71
+ return json.loads(self.body)
72
+ else:
73
+ raise ValueError("Response body is not JSON-parseable")
74
+
75
+ @property
76
+ def text(self) -> str:
77
+ """Get response body as text."""
78
+ if isinstance(self.body, bytes):
79
+ return self.body.decode('utf-8')
80
+ elif isinstance(self.body, str):
81
+ return self.body
82
+ else:
83
+ return str(self.body or "")
84
+
85
+ def raise_for_status(self) -> None:
86
+ """Raise error if response status indicates failure."""
87
+ if not self.is_success():
88
+ from provide.foundation.transport.errors import HTTPResponseError
89
+ raise HTTPResponseError(
90
+ f"Request failed with status {self.status}",
91
+ status_code=self.status,
92
+ response=self,
93
+ )
94
+
95
+
96
+ @runtime_checkable
97
+ class Transport(Protocol):
98
+ """Abstract transport protocol."""
99
+
100
+ async def execute(self, request: Request) -> Response:
101
+ """Execute a request and return response."""
102
+ ...
103
+
104
+ async def stream(self, request: Request) -> AsyncIterator[bytes]:
105
+ """Stream response data."""
106
+ ...
107
+
108
+ async def connect(self) -> None:
109
+ """Establish connection if needed."""
110
+ ...
111
+
112
+ async def disconnect(self) -> None:
113
+ """Close connection if needed."""
114
+ ...
115
+
116
+ def supports(self, transport_type: TransportType) -> bool:
117
+ """Check if this transport handles the given type."""
118
+ ...
119
+
120
+ async def __aenter__(self) -> "Transport":
121
+ """Context manager entry."""
122
+ await self.connect()
123
+ return self
124
+
125
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
126
+ """Context manager exit."""
127
+ await self.disconnect()
128
+
129
+
130
+ class TransportBase(ABC):
131
+ """Base class for transport implementations."""
132
+
133
+ def __init__(self):
134
+ self._logger = get_logger(self.__class__.__name__)
135
+
136
+ @abstractmethod
137
+ async def execute(self, request: Request) -> Response:
138
+ """Execute a request and return response."""
139
+ pass
140
+
141
+ @abstractmethod
142
+ def supports(self, transport_type: TransportType) -> bool:
143
+ """Check if this transport handles the given type."""
144
+ pass
145
+
146
+ async def connect(self) -> None:
147
+ """Default connect implementation."""
148
+ self._logger.trace("Transport connecting")
149
+
150
+ async def disconnect(self) -> None:
151
+ """Default disconnect implementation."""
152
+ self._logger.trace("Transport disconnecting")
153
+
154
+ async def stream(self, request: Request) -> AsyncIterator[bytes]:
155
+ """Default streaming implementation (not supported)."""
156
+ raise NotImplementedError(f"{self.__class__.__name__} does not support streaming")
157
+
158
+ async def __aenter__(self) -> "TransportBase":
159
+ await self.connect()
160
+ return self
161
+
162
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
163
+ await self.disconnect()
164
+
165
+
166
+ __all__ = [
167
+ "Request",
168
+ "Response",
169
+ "Transport",
170
+ "TransportBase",
171
+ ]
@@ -0,0 +1,266 @@
1
+ """
2
+ Universal transport client with middleware support.
3
+ """
4
+
5
+ from collections.abc import AsyncIterator
6
+ from typing import Any
7
+
8
+ from attrs import define, field
9
+
10
+ from provide.foundation.logger import get_logger
11
+ from provide.foundation.transport.base import Request, Response
12
+ from provide.foundation.transport.middleware import MiddlewarePipeline, create_default_pipeline
13
+ from provide.foundation.transport.registry import get_transport
14
+ from provide.foundation.transport.types import Data, Headers, HTTPMethod, Params
15
+
16
+ log = get_logger(__name__)
17
+
18
+
19
+ @define
20
+ class UniversalClient:
21
+ """Universal client that works with any transport via Hub registry."""
22
+
23
+ middleware: MiddlewarePipeline = field(factory=create_default_pipeline)
24
+ default_headers: Headers = field(factory=dict)
25
+ default_timeout: float | None = field(default=None)
26
+ _transports: dict[str, Any] = field(factory=dict, init=False)
27
+
28
+ async def request(
29
+ self,
30
+ uri: str,
31
+ method: str | HTTPMethod = HTTPMethod.GET,
32
+ *,
33
+ headers: Headers | None = None,
34
+ params: Params | None = None,
35
+ body: Data = None,
36
+ timeout: float | None = None,
37
+ **kwargs
38
+ ) -> Response:
39
+ """
40
+ Make a request using appropriate transport.
41
+
42
+ Args:
43
+ uri: Full URI to make request to
44
+ method: HTTP method or protocol-specific method
45
+ headers: Request headers
46
+ params: Query parameters
47
+ body: Request body (dict for JSON, str/bytes for raw)
48
+ timeout: Request timeout override
49
+ **kwargs: Additional request metadata
50
+
51
+ Returns:
52
+ Response from the transport
53
+ """
54
+ # Normalize method
55
+ if isinstance(method, HTTPMethod):
56
+ method = method.value
57
+
58
+ # Merge headers
59
+ request_headers = dict(self.default_headers)
60
+ if headers:
61
+ request_headers.update(headers)
62
+
63
+ # Create request object
64
+ request = Request(
65
+ uri=uri,
66
+ method=method,
67
+ headers=request_headers,
68
+ params=params or {},
69
+ body=body,
70
+ timeout=timeout or self.default_timeout,
71
+ metadata=kwargs,
72
+ )
73
+
74
+ # Process through middleware
75
+ request = await self.middleware.process_request(request)
76
+
77
+ try:
78
+ # Get transport for this URI
79
+ transport = await self._get_transport(request.transport_type.value)
80
+
81
+ # Execute request
82
+ response = await transport.execute(request)
83
+
84
+ # Process response through middleware
85
+ response = await self.middleware.process_response(response)
86
+
87
+ return response
88
+
89
+ except Exception as e:
90
+ # Process error through middleware
91
+ e = await self.middleware.process_error(e, request)
92
+ raise e
93
+
94
+ async def stream(
95
+ self,
96
+ uri: str,
97
+ method: str | HTTPMethod = HTTPMethod.GET,
98
+ **kwargs
99
+ ) -> AsyncIterator[bytes]:
100
+ """
101
+ Stream data from URI.
102
+
103
+ Args:
104
+ uri: URI to stream from
105
+ method: HTTP method or protocol-specific method
106
+ **kwargs: Additional request parameters
107
+
108
+ Yields:
109
+ Chunks of response data
110
+ """
111
+ # Normalize method
112
+ if isinstance(method, HTTPMethod):
113
+ method = method.value
114
+
115
+ # Create request
116
+ request = Request(
117
+ uri=uri,
118
+ method=method,
119
+ headers=dict(self.default_headers),
120
+ **kwargs
121
+ )
122
+
123
+ # Get transport
124
+ transport = await self._get_transport(request.transport_type.value)
125
+
126
+ # Stream response
127
+ log.info(f"🌊 Streaming {method} {uri}")
128
+ async for chunk in transport.stream(request):
129
+ yield chunk
130
+
131
+ async def get(self, uri: str, **kwargs) -> Response:
132
+ """GET request."""
133
+ return await self.request(uri, HTTPMethod.GET, **kwargs)
134
+
135
+ async def post(self, uri: str, **kwargs) -> Response:
136
+ """POST request."""
137
+ return await self.request(uri, HTTPMethod.POST, **kwargs)
138
+
139
+ async def put(self, uri: str, **kwargs) -> Response:
140
+ """PUT request."""
141
+ return await self.request(uri, HTTPMethod.PUT, **kwargs)
142
+
143
+ async def patch(self, uri: str, **kwargs) -> Response:
144
+ """PATCH request."""
145
+ return await self.request(uri, HTTPMethod.PATCH, **kwargs)
146
+
147
+ async def delete(self, uri: str, **kwargs) -> Response:
148
+ """DELETE request."""
149
+ return await self.request(uri, HTTPMethod.DELETE, **kwargs)
150
+
151
+ async def head(self, uri: str, **kwargs) -> Response:
152
+ """HEAD request."""
153
+ return await self.request(uri, HTTPMethod.HEAD, **kwargs)
154
+
155
+ async def options(self, uri: str, **kwargs) -> Response:
156
+ """OPTIONS request."""
157
+ return await self.request(uri, HTTPMethod.OPTIONS, **kwargs)
158
+
159
+ async def _get_transport(self, scheme: str) -> Any:
160
+ """Get or create transport for scheme."""
161
+ if scheme not in self._transports:
162
+ # Get transport class from registry
163
+ transport = get_transport(f"{scheme}://example.com")
164
+ await transport.connect()
165
+ self._transports[scheme] = transport
166
+
167
+ return self._transports[scheme]
168
+
169
+ async def __aenter__(self) -> "UniversalClient":
170
+ """Context manager entry."""
171
+ return self
172
+
173
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
174
+ """Context manager exit - cleanup all transports."""
175
+ for transport in self._transports.values():
176
+ try:
177
+ await transport.disconnect()
178
+ except Exception as e:
179
+ log.error(f"Error disconnecting transport: {e}")
180
+ self._transports.clear()
181
+
182
+
183
+ # Global client instance for convenience functions
184
+ _default_client: UniversalClient | None = None
185
+
186
+
187
+ def get_default_client() -> UniversalClient:
188
+ """Get or create the default client instance."""
189
+ global _default_client
190
+ if _default_client is None:
191
+ _default_client = UniversalClient()
192
+ return _default_client
193
+
194
+
195
+ async def request(
196
+ uri: str,
197
+ method: str | HTTPMethod = HTTPMethod.GET,
198
+ **kwargs
199
+ ) -> Response:
200
+ """Make a request using the default client."""
201
+ client = get_default_client()
202
+ return await client.request(uri, method, **kwargs)
203
+
204
+
205
+ async def get(uri: str, **kwargs) -> Response:
206
+ """GET request using default client."""
207
+ client = get_default_client()
208
+ return await client.get(uri, **kwargs)
209
+
210
+
211
+ async def post(uri: str, **kwargs) -> Response:
212
+ """POST request using default client."""
213
+ client = get_default_client()
214
+ return await client.post(uri, **kwargs)
215
+
216
+
217
+ async def put(uri: str, **kwargs) -> Response:
218
+ """PUT request using default client."""
219
+ client = get_default_client()
220
+ return await client.put(uri, **kwargs)
221
+
222
+
223
+ async def patch(uri: str, **kwargs) -> Response:
224
+ """PATCH request using default client."""
225
+ client = get_default_client()
226
+ return await client.patch(uri, **kwargs)
227
+
228
+
229
+ async def delete(uri: str, **kwargs) -> Response:
230
+ """DELETE request using default client."""
231
+ client = get_default_client()
232
+ return await client.delete(uri, **kwargs)
233
+
234
+
235
+ async def head(uri: str, **kwargs) -> Response:
236
+ """HEAD request using default client."""
237
+ client = get_default_client()
238
+ return await client.head(uri, **kwargs)
239
+
240
+
241
+ async def options(uri: str, **kwargs) -> Response:
242
+ """OPTIONS request using default client."""
243
+ client = get_default_client()
244
+ return await client.options(uri, **kwargs)
245
+
246
+
247
+ async def stream(uri: str, **kwargs) -> AsyncIterator[bytes]:
248
+ """Stream data using default client."""
249
+ client = get_default_client()
250
+ async for chunk in client.stream(uri, **kwargs):
251
+ yield chunk
252
+
253
+
254
+ __all__ = [
255
+ "UniversalClient",
256
+ "get_default_client",
257
+ "request",
258
+ "get",
259
+ "post",
260
+ "put",
261
+ "patch",
262
+ "delete",
263
+ "head",
264
+ "options",
265
+ "stream",
266
+ ]