provide-foundation 0.0.0.dev0__py3-none-any.whl → 0.0.0.dev1__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.
- provide/foundation/__init__.py +12 -20
- provide/foundation/archive/__init__.py +23 -0
- provide/foundation/archive/base.py +70 -0
- provide/foundation/archive/bzip2.py +157 -0
- provide/foundation/archive/gzip.py +159 -0
- provide/foundation/archive/operations.py +336 -0
- provide/foundation/archive/tar.py +164 -0
- provide/foundation/archive/zip.py +203 -0
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/sync.py +19 -4
- provide/foundation/core.py +1 -2
- provide/foundation/crypto/__init__.py +2 -0
- provide/foundation/crypto/certificates/__init__.py +34 -0
- provide/foundation/crypto/certificates/base.py +173 -0
- provide/foundation/crypto/certificates/certificate.py +290 -0
- provide/foundation/crypto/certificates/factory.py +213 -0
- provide/foundation/crypto/certificates/generator.py +138 -0
- provide/foundation/crypto/certificates/loader.py +130 -0
- provide/foundation/crypto/certificates/operations.py +198 -0
- provide/foundation/crypto/certificates/trust.py +107 -0
- provide/foundation/eventsets/__init__.py +0 -0
- provide/foundation/eventsets/display.py +84 -0
- provide/foundation/eventsets/registry.py +160 -0
- provide/foundation/eventsets/resolver.py +192 -0
- provide/foundation/eventsets/sets/das.py +128 -0
- provide/foundation/eventsets/sets/database.py +125 -0
- provide/foundation/eventsets/sets/http.py +153 -0
- provide/foundation/eventsets/sets/llm.py +139 -0
- provide/foundation/eventsets/sets/task_queue.py +107 -0
- provide/foundation/eventsets/types.py +70 -0
- provide/foundation/hub/components.py +7 -133
- provide/foundation/logger/__init__.py +3 -10
- provide/foundation/logger/config/logging.py +6 -6
- provide/foundation/logger/core.py +0 -2
- provide/foundation/logger/custom_processors.py +1 -0
- provide/foundation/logger/factories.py +11 -2
- provide/foundation/logger/processors/main.py +20 -84
- provide/foundation/logger/setup/__init__.py +5 -1
- provide/foundation/logger/setup/coordinator.py +75 -23
- provide/foundation/logger/setup/processors.py +2 -9
- provide/foundation/logger/trace.py +27 -0
- provide/foundation/metrics/otel.py +10 -10
- provide/foundation/process/lifecycle.py +82 -26
- provide/foundation/testing/__init__.py +77 -0
- provide/foundation/testing/archive/__init__.py +24 -0
- provide/foundation/testing/archive/fixtures.py +217 -0
- provide/foundation/testing/common/__init__.py +34 -0
- provide/foundation/testing/common/fixtures.py +263 -0
- provide/foundation/testing/file/__init__.py +40 -0
- provide/foundation/testing/file/fixtures.py +523 -0
- provide/foundation/testing/logger.py +41 -11
- provide/foundation/testing/mocking/__init__.py +46 -0
- provide/foundation/testing/mocking/fixtures.py +331 -0
- provide/foundation/testing/process/__init__.py +48 -0
- provide/foundation/testing/process/fixtures.py +577 -0
- provide/foundation/testing/threading/__init__.py +38 -0
- provide/foundation/testing/threading/fixtures.py +520 -0
- provide/foundation/testing/time/__init__.py +32 -0
- provide/foundation/testing/time/fixtures.py +409 -0
- provide/foundation/testing/transport/__init__.py +30 -0
- provide/foundation/testing/transport/fixtures.py +280 -0
- provide/foundation/tools/__init__.py +58 -0
- provide/foundation/tools/base.py +348 -0
- provide/foundation/tools/cache.py +266 -0
- provide/foundation/tools/downloader.py +213 -0
- provide/foundation/tools/installer.py +254 -0
- provide/foundation/tools/registry.py +223 -0
- provide/foundation/tools/resolver.py +321 -0
- provide/foundation/tools/verifier.py +186 -0
- provide/foundation/tracer/otel.py +7 -11
- provide/foundation/transport/__init__.py +155 -0
- provide/foundation/transport/base.py +171 -0
- provide/foundation/transport/client.py +266 -0
- provide/foundation/transport/config.py +209 -0
- provide/foundation/transport/errors.py +79 -0
- provide/foundation/transport/http.py +232 -0
- provide/foundation/transport/middleware.py +366 -0
- provide/foundation/transport/registry.py +167 -0
- provide/foundation/transport/types.py +45 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/METADATA +5 -28
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/RECORD +85 -34
- provide/foundation/cli/commands/logs/generate_old.py +0 -569
- provide/foundation/crypto/certificates.py +0 -896
- provide/foundation/logger/emoji/__init__.py +0 -44
- provide/foundation/logger/emoji/matrix.py +0 -209
- provide/foundation/logger/emoji/sets.py +0 -458
- provide/foundation/logger/emoji/types.py +0 -56
- provide/foundation/logger/setup/emoji_resolver.py +0 -64
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.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
|
+
]
|