httpware 0.8.0__tar.gz → 0.8.1__tar.gz
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.
- {httpware-0.8.0 → httpware-0.8.1}/PKG-INFO +2 -2
- {httpware-0.8.0 → httpware-0.8.1}/README.md +1 -1
- {httpware-0.8.0 → httpware-0.8.1}/pyproject.toml +1 -1
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/__init__.py +2 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/client.py +9 -3
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/decoders/__init__.py +6 -1
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/errors.py +42 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/_internal/__init__.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/_internal/exception_mapping.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/_internal/import_checker.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/_internal/observability.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/_internal/status.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/decoders/msgspec.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/decoders/pydantic.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/middleware/__init__.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/middleware/chain.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/middleware/resilience/__init__.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/middleware/resilience/_backoff.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/middleware/resilience/budget.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/middleware/resilience/bulkhead.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/middleware/resilience/retry.py +0 -0
- {httpware-0.8.0 → httpware-0.8.1}/src/httpware/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpware
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Resilience-first async HTTP client framework for Python
|
|
5
5
|
Keywords: http,async,client,resilience,retry,circuit-breaker,middleware,httpx,pydantic
|
|
6
6
|
Author: Artur Shiriev
|
|
@@ -79,7 +79,7 @@ with Client(base_url="https://example.test") as client:
|
|
|
79
79
|
print(response.json())
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
Typed decoding via `response_model=` works in both worlds — requires `pip install httpware[pydantic]
|
|
82
|
+
Typed decoding via `response_model=` works in both worlds — requires `pip install httpware[pydantic]`. Decode failures (malformed body, schema mismatch) raise `httpware.DecodeError`, a `ClientError` subclass — so `except httpware.ClientError` covers them alongside transport and status errors.
|
|
83
83
|
|
|
84
84
|
```python
|
|
85
85
|
from httpware import AsyncClient
|
|
@@ -49,7 +49,7 @@ with Client(base_url="https://example.test") as client:
|
|
|
49
49
|
print(response.json())
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
Typed decoding via `response_model=` works in both worlds — requires `pip install httpware[pydantic]
|
|
52
|
+
Typed decoding via `response_model=` works in both worlds — requires `pip install httpware[pydantic]`. Decode failures (malformed body, schema mismatch) raise `httpware.DecodeError`, a `ClientError` subclass — so `except httpware.ClientError` covers them alongside transport and status errors.
|
|
53
53
|
|
|
54
54
|
```python
|
|
55
55
|
from httpware import AsyncClient
|
|
@@ -9,6 +9,7 @@ from httpware.errors import (
|
|
|
9
9
|
ClientError,
|
|
10
10
|
ClientStatusError,
|
|
11
11
|
ConflictError,
|
|
12
|
+
DecodeError,
|
|
12
13
|
ForbiddenError,
|
|
13
14
|
InternalServerError,
|
|
14
15
|
NetworkError,
|
|
@@ -52,6 +53,7 @@ __all__ = [
|
|
|
52
53
|
"ClientError",
|
|
53
54
|
"ClientStatusError",
|
|
54
55
|
"ConflictError",
|
|
56
|
+
"DecodeError",
|
|
55
57
|
"ForbiddenError",
|
|
56
58
|
"InternalServerError",
|
|
57
59
|
"Middleware",
|
|
@@ -16,7 +16,7 @@ from httpware._internal.status import (
|
|
|
16
16
|
_raise_on_status_error,
|
|
17
17
|
)
|
|
18
18
|
from httpware.decoders import ResponseDecoder
|
|
19
|
-
from httpware.errors import TransportError
|
|
19
|
+
from httpware.errors import DecodeError, TransportError
|
|
20
20
|
from httpware.middleware import AsyncMiddleware, AsyncNext, Middleware, Next
|
|
21
21
|
from httpware.middleware.chain import compose, compose_async
|
|
22
22
|
|
|
@@ -154,7 +154,10 @@ class AsyncClient:
|
|
|
154
154
|
response = await self._dispatch(request)
|
|
155
155
|
if response_model is None:
|
|
156
156
|
return response
|
|
157
|
-
|
|
157
|
+
try:
|
|
158
|
+
return self._decoder.decode(response.content, response_model)
|
|
159
|
+
except Exception as exc:
|
|
160
|
+
raise DecodeError(response=response, model=response_model, original=exc) from exc
|
|
158
161
|
|
|
159
162
|
def build_request(self, method: str, url: str, **kwargs: typing.Any) -> httpx2.Request:
|
|
160
163
|
"""Delegate request construction to the wrapped httpx2.AsyncClient."""
|
|
@@ -871,7 +874,10 @@ class Client:
|
|
|
871
874
|
response = self._dispatch(request)
|
|
872
875
|
if response_model is None:
|
|
873
876
|
return response
|
|
874
|
-
|
|
877
|
+
try:
|
|
878
|
+
return self._decoder.decode(response.content, response_model)
|
|
879
|
+
except Exception as exc:
|
|
880
|
+
raise DecodeError(response=response, model=response_model, original=exc) from exc
|
|
875
881
|
|
|
876
882
|
def build_request(self, method: str, url: str, **kwargs: typing.Any) -> httpx2.Request:
|
|
877
883
|
"""Delegate request construction to the wrapped httpx2.Client."""
|
|
@@ -11,7 +11,12 @@ class ResponseDecoder(Protocol):
|
|
|
11
11
|
"""Structural protocol every response-body decoder satisfies."""
|
|
12
12
|
|
|
13
13
|
def decode(self, content: bytes, model: type[T]) -> T:
|
|
14
|
-
"""Decode `content` (raw response bytes) into an instance of `model`.
|
|
14
|
+
"""Decode `content` (raw response bytes) into an instance of `model`.
|
|
15
|
+
|
|
16
|
+
Any exception raised by `decode` is wrapped by `Client.send` /
|
|
17
|
+
`AsyncClient.send` into `httpware.DecodeError`; implementers do not
|
|
18
|
+
need to raise `DecodeError` directly.
|
|
19
|
+
"""
|
|
15
20
|
...
|
|
16
21
|
|
|
17
22
|
|
|
@@ -212,3 +212,45 @@ class BulkheadFullError(ClientError):
|
|
|
212
212
|
_reconstruct_bulkhead_full,
|
|
213
213
|
(type(self), self.max_concurrent, self.acquire_timeout),
|
|
214
214
|
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _reconstruct_decode_error(
|
|
218
|
+
cls: "type[DecodeError]",
|
|
219
|
+
response: httpx2.Response,
|
|
220
|
+
model: type,
|
|
221
|
+
original: BaseException,
|
|
222
|
+
) -> "DecodeError":
|
|
223
|
+
return cls(response=response, model=model, original=original)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class DecodeError(ClientError):
|
|
227
|
+
"""Raised when the active ResponseDecoder failed to decode response.content.
|
|
228
|
+
|
|
229
|
+
The HTTP call itself succeeded — status was 2xx/3xx and the transport
|
|
230
|
+
delivered the body intact — but the body could not be parsed into the
|
|
231
|
+
requested response_model. Always chained from the underlying library
|
|
232
|
+
exception via ``raise ... from exc``; that exception is also exposed as
|
|
233
|
+
``self.original`` for structured handling.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
response: httpx2.Response
|
|
237
|
+
model: type
|
|
238
|
+
original: BaseException
|
|
239
|
+
|
|
240
|
+
def __init__(
|
|
241
|
+
self,
|
|
242
|
+
*,
|
|
243
|
+
response: httpx2.Response,
|
|
244
|
+
model: type,
|
|
245
|
+
original: BaseException,
|
|
246
|
+
) -> None:
|
|
247
|
+
self.response = response
|
|
248
|
+
self.model = model
|
|
249
|
+
self.original = original
|
|
250
|
+
super().__init__(f"failed to decode response into {model.__name__}: {original}")
|
|
251
|
+
|
|
252
|
+
def __reduce__(self) -> tuple[Any, ...]:
|
|
253
|
+
return (
|
|
254
|
+
_reconstruct_decode_error,
|
|
255
|
+
(type(self), self.response, self.model, self.original),
|
|
256
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|