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.
- provide/foundation/__init__.py +36 -10
- provide/foundation/archive/__init__.py +1 -1
- provide/foundation/archive/base.py +15 -14
- provide/foundation/archive/bzip2.py +40 -40
- provide/foundation/archive/gzip.py +42 -42
- provide/foundation/archive/operations.py +93 -96
- provide/foundation/archive/tar.py +33 -31
- provide/foundation/archive/zip.py +52 -50
- provide/foundation/asynctools/__init__.py +20 -0
- provide/foundation/asynctools/core.py +126 -0
- provide/foundation/cli/__init__.py +2 -2
- provide/foundation/cli/commands/deps.py +15 -9
- provide/foundation/cli/commands/logs/__init__.py +3 -3
- provide/foundation/cli/commands/logs/generate.py +2 -2
- provide/foundation/cli/commands/logs/query.py +4 -4
- provide/foundation/cli/commands/logs/send.py +3 -3
- provide/foundation/cli/commands/logs/tail.py +3 -3
- provide/foundation/cli/decorators.py +11 -11
- provide/foundation/cli/main.py +1 -1
- provide/foundation/cli/testing.py +2 -40
- provide/foundation/cli/utils.py +21 -18
- provide/foundation/config/__init__.py +35 -2
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +477 -0
- provide/foundation/config/defaults.py +67 -0
- provide/foundation/config/env.py +6 -20
- provide/foundation/config/loader.py +10 -4
- provide/foundation/config/sync.py +8 -6
- provide/foundation/config/types.py +5 -5
- provide/foundation/config/validators.py +4 -4
- provide/foundation/console/input.py +5 -5
- provide/foundation/console/output.py +36 -14
- provide/foundation/context/__init__.py +8 -4
- provide/foundation/context/core.py +88 -110
- provide/foundation/crypto/certificates/__init__.py +9 -5
- provide/foundation/crypto/certificates/base.py +2 -2
- provide/foundation/crypto/certificates/certificate.py +48 -19
- provide/foundation/crypto/certificates/factory.py +26 -18
- provide/foundation/crypto/certificates/generator.py +24 -23
- provide/foundation/crypto/certificates/loader.py +24 -16
- provide/foundation/crypto/certificates/operations.py +17 -10
- provide/foundation/crypto/certificates/trust.py +21 -21
- provide/foundation/env/__init__.py +28 -0
- provide/foundation/env/core.py +218 -0
- provide/foundation/errors/__init__.py +3 -3
- provide/foundation/errors/decorators.py +0 -234
- provide/foundation/errors/types.py +0 -98
- provide/foundation/eventsets/display.py +13 -14
- provide/foundation/eventsets/registry.py +61 -31
- provide/foundation/eventsets/resolver.py +50 -46
- provide/foundation/eventsets/sets/das.py +8 -8
- provide/foundation/eventsets/sets/database.py +14 -14
- provide/foundation/eventsets/sets/http.py +21 -21
- provide/foundation/eventsets/sets/llm.py +16 -16
- provide/foundation/eventsets/sets/task_queue.py +13 -13
- provide/foundation/eventsets/types.py +7 -7
- provide/foundation/file/directory.py +14 -23
- provide/foundation/file/lock.py +4 -3
- provide/foundation/hub/components.py +75 -389
- provide/foundation/hub/config.py +157 -0
- provide/foundation/hub/discovery.py +63 -0
- provide/foundation/hub/handlers.py +89 -0
- provide/foundation/hub/lifecycle.py +195 -0
- provide/foundation/hub/manager.py +7 -4
- provide/foundation/hub/processors.py +49 -0
- provide/foundation/integrations/__init__.py +11 -0
- provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
- provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/client.py +14 -14
- provide/foundation/{observability → integrations}/openobserve/commands.py +12 -12
- provide/foundation/integrations/openobserve/config.py +37 -0
- provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/otlp.py +2 -2
- provide/foundation/{observability → integrations}/openobserve/search.py +2 -3
- provide/foundation/{observability → integrations}/openobserve/streaming.py +5 -5
- provide/foundation/logger/__init__.py +0 -1
- provide/foundation/logger/config/base.py +1 -1
- provide/foundation/logger/config/logging.py +69 -299
- provide/foundation/logger/config/telemetry.py +39 -121
- provide/foundation/logger/factories.py +2 -2
- provide/foundation/logger/processors/main.py +12 -10
- provide/foundation/logger/ratelimit/limiters.py +4 -4
- provide/foundation/logger/ratelimit/processor.py +1 -1
- provide/foundation/logger/setup/coordinator.py +39 -25
- provide/foundation/logger/setup/processors.py +3 -3
- provide/foundation/logger/setup/testing.py +14 -0
- provide/foundation/logger/trace.py +5 -5
- provide/foundation/metrics/__init__.py +1 -1
- provide/foundation/metrics/otel.py +3 -1
- provide/foundation/observability/__init__.py +3 -3
- provide/foundation/process/__init__.py +9 -0
- provide/foundation/process/exit.py +48 -0
- provide/foundation/process/lifecycle.py +69 -46
- provide/foundation/resilience/__init__.py +36 -0
- provide/foundation/resilience/circuit.py +166 -0
- provide/foundation/resilience/decorators.py +236 -0
- provide/foundation/resilience/fallback.py +208 -0
- provide/foundation/resilience/retry.py +327 -0
- provide/foundation/serialization/__init__.py +16 -0
- provide/foundation/serialization/core.py +70 -0
- provide/foundation/streams/config.py +78 -0
- provide/foundation/streams/console.py +4 -5
- provide/foundation/streams/core.py +5 -2
- provide/foundation/streams/file.py +12 -2
- provide/foundation/testing/__init__.py +29 -9
- provide/foundation/testing/archive/__init__.py +7 -7
- provide/foundation/testing/archive/fixtures.py +58 -54
- provide/foundation/testing/cli.py +30 -20
- provide/foundation/testing/common/__init__.py +13 -15
- provide/foundation/testing/common/fixtures.py +27 -57
- provide/foundation/testing/file/__init__.py +15 -15
- provide/foundation/testing/file/content_fixtures.py +289 -0
- provide/foundation/testing/file/directory_fixtures.py +107 -0
- provide/foundation/testing/file/fixtures.py +42 -516
- provide/foundation/testing/file/special_fixtures.py +145 -0
- provide/foundation/testing/logger.py +89 -8
- provide/foundation/testing/mocking/__init__.py +21 -21
- provide/foundation/testing/mocking/fixtures.py +80 -67
- provide/foundation/testing/process/__init__.py +23 -23
- provide/foundation/testing/process/async_fixtures.py +414 -0
- provide/foundation/testing/process/fixtures.py +48 -571
- provide/foundation/testing/process/subprocess_fixtures.py +210 -0
- provide/foundation/testing/threading/__init__.py +17 -17
- provide/foundation/testing/threading/basic_fixtures.py +105 -0
- provide/foundation/testing/threading/data_fixtures.py +101 -0
- provide/foundation/testing/threading/execution_fixtures.py +278 -0
- provide/foundation/testing/threading/fixtures.py +32 -502
- provide/foundation/testing/threading/sync_fixtures.py +100 -0
- provide/foundation/testing/time/__init__.py +11 -11
- provide/foundation/testing/time/fixtures.py +95 -83
- provide/foundation/testing/transport/__init__.py +9 -9
- provide/foundation/testing/transport/fixtures.py +54 -54
- provide/foundation/time/__init__.py +18 -0
- provide/foundation/time/core.py +63 -0
- provide/foundation/tools/__init__.py +2 -2
- provide/foundation/tools/base.py +68 -67
- provide/foundation/tools/cache.py +69 -74
- provide/foundation/tools/downloader.py +68 -62
- provide/foundation/tools/installer.py +51 -57
- provide/foundation/tools/registry.py +38 -45
- provide/foundation/tools/resolver.py +70 -68
- provide/foundation/tools/verifier.py +39 -50
- provide/foundation/tracer/spans.py +2 -14
- provide/foundation/transport/__init__.py +26 -33
- provide/foundation/transport/base.py +32 -30
- provide/foundation/transport/client.py +44 -49
- provide/foundation/transport/config.py +36 -107
- provide/foundation/transport/errors.py +13 -27
- provide/foundation/transport/http.py +69 -55
- provide/foundation/transport/middleware.py +113 -114
- provide/foundation/transport/registry.py +29 -27
- provide/foundation/transport/types.py +6 -6
- provide/foundation/utils/deps.py +17 -14
- provide/foundation/utils/parsing.py +49 -4
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
- provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
- provide_foundation-0.0.0.dev1.dist-info/RECORD +0 -200
- /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
- /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {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(
|
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(
|
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(
|
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
|
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
|
+
]
|