Skip to content

Commit 4e0ab18

Browse files
committed
added json decoding to Response
1 parent 9a11bb4 commit 4e0ab18

5 files changed

Lines changed: 82 additions & 2 deletions

File tree

extapi/http/backends/aiohttp.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
from collections.abc import Callable
12
from typing import Any
23

34
import aiohttp
45

56
from extapi.http.abc import AbstractExecutor
6-
from extapi.http.types import BackendResponseProtocol, RequestData, Response
7+
from extapi.http.types import (
8+
DEFAULT_JSON_DECODER,
9+
BackendResponseProtocol,
10+
RequestData,
11+
Response,
12+
)
713

814

915
class AiohttpResponseWrap(BackendResponseProtocol[aiohttp.ClientResponse]):
@@ -22,6 +28,14 @@ async def close(self) -> None:
2228
async def read(self) -> bytes:
2329
return await self._original.read()
2430

31+
async def json(
32+
self,
33+
*,
34+
encoding: str | None,
35+
loads: Callable[[str], Any] = DEFAULT_JSON_DECODER,
36+
) -> Any:
37+
return await self._original.json(encoding=encoding, loads=loads)
38+
2539

2640
class AiohttpExecutor(AbstractExecutor[aiohttp.ClientResponse]):
2741
__slots__ = (

extapi/http/backends/httpx.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
from yarl import URL
66

77
from extapi.http.abc import AbstractExecutor
8-
from extapi.http.types import BackendResponseProtocol, RequestData, Response
8+
from extapi.http.types import (
9+
BackendResponseProtocol,
10+
RequestData,
11+
Response,
12+
)
913

1014

1115
class HttpxResponseWrap(BackendResponseProtocol[httpx.Response]):

extapi/http/types.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import json
12
from dataclasses import dataclass, field
23
from typing import (
34
Any,
5+
Callable,
46
Generic,
57
Literal,
68
Protocol,
@@ -15,6 +17,8 @@
1517
HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE"] | str
1618
StrOrURL = str | URL
1719

20+
DEFAULT_JSON_DECODER = json.loads
21+
1822

1923
@dataclass(slots=True, kw_only=True)
2024
class RequestData:
@@ -34,9 +38,24 @@ class RequestData:
3438
@runtime_checkable
3539
class BackendResponseProtocol(Protocol[T]):
3640
def original(self) -> T: ...
41+
3742
async def close(self) -> None: ...
43+
3844
async def read(self) -> bytes: ...
3945

46+
async def json(
47+
self,
48+
*,
49+
encoding: str | None,
50+
loads: Callable[[str], Any] = DEFAULT_JSON_DECODER,
51+
) -> Any:
52+
data = await self.read()
53+
if encoding is None:
54+
s = data.decode()
55+
else:
56+
s = data.decode(encoding=encoding)
57+
return loads(s)
58+
4059

4160
@dataclass(kw_only=True)
4261
class Response(Generic[T]):
@@ -54,6 +73,14 @@ async def read(self) -> bytes:
5473
self._data = await self.backend_response.read()
5574
return self._data
5675

76+
async def json(
77+
self,
78+
*,
79+
encoding: str | None = None,
80+
loads: Callable[[str], Any] = DEFAULT_JSON_DECODER,
81+
) -> Any:
82+
return await self.backend_response.json(encoding=encoding, loads=loads)
83+
5784
async def __aenter__(self) -> Self:
5885
return self
5986

tests/exthttp/executors/test_retry.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,16 @@ async def process_error(
266266

267267
assert response.status == 200
268268
assert base.call_count == 2
269+
270+
async def test_default_addons(self, request_simple: RequestData):
271+
base = _DummyExecutor(responses=[500, 429, 200])
272+
executor = RetryableExecutor(
273+
base,
274+
max_retries=3,
275+
retry_sleep_timeout=0,
276+
)
277+
278+
response = await executor.execute(request_simple)
279+
280+
assert response.status == 200
281+
assert base.call_count == 3

tests/exthttp/test_types.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@ async def test_has_data(self):
1818
assert response.headers == CIMultiDict()
1919
assert await response.read() == b"some-data"
2020

21+
async def test_json(self):
22+
response = Response(
23+
url="example.com",
24+
status=200,
25+
backend_response=DummyBackendResponse(b'{"a": 1, "b": [10, 20]}'),
26+
)
27+
28+
assert response.status == 200
29+
assert response.headers == CIMultiDict()
30+
assert await response.json() == {
31+
"a": 1,
32+
"b": [10, 20],
33+
}
34+
35+
assert await response.json(encoding="latin-1") == {
36+
"a": 1,
37+
"b": [10, 20],
38+
}
39+
2140
async def test_has_data_double(self):
2241
response = Response(
2342
url="example.com",
@@ -78,6 +97,9 @@ async def close(self) -> None:
7897
async def read(self) -> bytes:
7998
return b"" # pragma: no cover
8099

100+
async def json(self, **kwargs) -> Any:
101+
return None # pragma: no cover
102+
81103
response = Response[Any](
82104
url="example.com", status=200, backend_response=_Resp()
83105
)

0 commit comments

Comments
 (0)