Skip to content

Commit a3dd07d

Browse files
authored
feat(py): add json() method for Response object (#277)
Adds a `Response.json()` method that parses `Response.text` using the native `json` module. Closes #275
1 parent f47c814 commit a3dd07d

4 files changed

Lines changed: 54 additions & 31 deletions

File tree

‎impit-python/python/impit/impit.pyi‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ from __future__ import annotations
22
from http.cookiejar import CookieJar
33
from .cookies import Cookies
44

5-
from typing import Literal
5+
from typing import Literal, Any
66
from collections.abc import Iterator, AsyncIterator
77
from contextlib import AbstractAsyncContextManager, AbstractContextManager
88

@@ -188,6 +188,13 @@ class Response:
188188
def aiter_bytes(self) -> AsyncIterator[bytes]:
189189
"""Asynchronously iterate over the response content in chunks."""
190190

191+
def json(self) -> Any:
192+
"""Parse the response content as JSON.
193+
194+
Returns:
195+
Parsed JSON data as a Python object (dict, list, str, int, float, bool, or None)
196+
"""
197+
191198
def close(self) -> None:
192199
"""Close the response and release resources."""
193200

‎impit-python/src/response.rs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,13 @@ impl ImpitPyResponse {
460460
Ok(decoded_text)
461461
}
462462

463+
fn json(&mut self, py: Python<'_>) -> PyResult<Py<PyAny>> {
464+
let text = self.text(py)?;
465+
let json_module = py.import("json")?;
466+
let parsed = json_module.call_method1("loads", (text,))?;
467+
Ok(parsed.into())
468+
}
469+
463470
fn read(&mut self, py: Python<'_>) -> PyResult<Vec<u8>> {
464471
match self.inner_state {
465472
InnerResponseState::Read => self

‎impit-python/test/async_client_test.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,15 @@ async def test_content(self, browser: Browser) -> None:
432432
assert isinstance(response.text, str)
433433
assert response.content.decode('utf-8') == response.text
434434

435+
@pytest.mark.asyncio
436+
async def test_json(self, browser: Browser) -> None:
437+
impit = AsyncClient(browser=browser)
438+
439+
response = await impit.get(get_httpbin_url('/get'))
440+
441+
assert response.status_code == 200
442+
assert response.json() == json.loads(response.text)
443+
435444

436445
@pytest.mark.parametrize(
437446
('browser'),

‎impit-python/test/basic_client_test.py‎

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -71,22 +71,22 @@ def test_headers_work(self, browser: Browser) -> None:
7171

7272
response = impit.get(get_httpbin_url('/headers'), headers={'Impit-Test': 'foo'})
7373
assert response.status_code == 200
74-
assert json.loads(response.text)['headers']['Impit-Test'] == 'foo'
74+
assert response.json()['headers']['Impit-Test'] == 'foo'
7575

7676
def test_client_wide_headers_work(self, browser: Browser) -> None:
7777
impit = Client(browser=browser, headers={'Impit-Test': 'foo'})
7878

7979
response = impit.get(get_httpbin_url('/headers'))
8080
assert response.status_code == 200
81-
assert json.loads(response.text)['headers']['Impit-Test'] == 'foo'
81+
assert response.json()['headers']['Impit-Test'] == 'foo'
8282

8383
def test_request_headers_over_client_headers(self, browser: Browser) -> None:
8484
impit = Client(browser=browser, headers={'Auth': '123', 'Exception': 'nope'})
8585

8686
response = impit.get(get_httpbin_url('/headers'), headers={'Exception': 'yes'})
8787
assert response.status_code == 200
88-
assert json.loads(response.text)['headers']['Auth'] == '123'
89-
assert json.loads(response.text)['headers']['Exception'] == 'yes'
88+
assert response.json()['headers']['Auth'] == '123'
89+
assert response.json()['headers']['Exception'] == 'yes'
9090

9191
def test_cookies_nonstandard(self, browser: Browser) -> None:
9292
cookies_jar = CookieJar()
@@ -161,23 +161,19 @@ def test_cookie_jar_works(self, browser: Browser) -> None:
161161
cookie_jar=cookies.jar,
162162
)
163163

164-
response = json.loads(
165-
impit.get(
166-
get_httpbin_url('/cookies/'),
167-
).text
168-
)
164+
response = impit.get(
165+
get_httpbin_url('/cookies/'),
166+
).json()
169167

170168
assert response['cookies'] == {'preset-cookie': '123'}
171169

172170
impit.get(
173171
get_httpbin_url('/cookies/set', query={'set-by-server': '321'}),
174172
)
175173

176-
response = json.loads(
177-
impit.get(
178-
get_httpbin_url('/cookies/'),
179-
).text
180-
)
174+
response = impit.get(
175+
get_httpbin_url('/cookies/'),
176+
).json()
181177

182178
assert response['cookies'] == {
183179
'preset-cookie': '123',
@@ -194,23 +190,19 @@ def test_cookies_param_works(self, browser: Browser) -> None:
194190
cookies=cookies,
195191
)
196192

197-
response = json.loads(
198-
impit.get(
199-
get_httpbin_url('/cookies/'),
200-
).text
201-
)
193+
response = impit.get(
194+
get_httpbin_url('/cookies/'),
195+
).json()
202196

203197
assert response['cookies'] == {'preset-cookie': '123'}
204198

205199
impit.get(
206200
get_httpbin_url('/cookies/set', query={'set-by-server': '321'}),
207201
)
208202

209-
response = json.loads(
210-
impit.get(
211-
get_httpbin_url('/cookies/'),
212-
).text
213-
)
203+
response = impit.get(
204+
get_httpbin_url('/cookies/'),
205+
).json()
214206

215207
assert response['cookies'] == {
216208
'preset-cookie': '123',
@@ -226,7 +218,7 @@ def test_overwriting_headers_work(self, browser: Browser) -> None:
226218

227219
response = impit.get(get_httpbin_url('/headers'), headers={'User-Agent': 'this is impit!'})
228220
assert response.status_code == 200
229-
assert json.loads(response.text)['headers']['User-Agent'] == 'this is impit!'
221+
assert response.json()['headers']['User-Agent'] == 'this is impit!'
230222

231223
@pytest.mark.skip(reason='Flaky under the CI environment')
232224
def test_http3_works(self, browser: Browser) -> None:
@@ -330,7 +322,7 @@ def test_passing_string_body(self, browser: Browser) -> None:
330322
headers={'Content-Type': 'application/json'},
331323
)
332324
assert response.status_code == 200
333-
assert json.loads(response.text)['data'] == '{"Impit-Test":"foořžš"}'
325+
assert response.json()['data'] == '{"Impit-Test":"foořžš"}'
334326

335327
def test_passing_string_body_in_data(self, browser: Browser) -> None:
336328
impit = Client(browser=browser)
@@ -341,7 +333,7 @@ def test_passing_string_body_in_data(self, browser: Browser) -> None:
341333
headers={'Content-Type': 'application/json'},
342334
)
343335
assert response.status_code == 200
344-
assert json.loads(response.text)['data'] == '{"Impit-Test":"foořžš"}'
336+
assert response.json()['data'] == '{"Impit-Test":"foořžš"}'
345337

346338
def test_form_non_ascii(self, browser: Browser) -> None:
347339
impit = Client(browser=browser)
@@ -351,7 +343,7 @@ def test_form_non_ascii(self, browser: Browser) -> None:
351343
data={'Impit-Test': '👾🕵🏻‍♂️🧑‍💻'},
352344
)
353345
assert response.status_code == 200
354-
assert json.loads(response.text)['form']['Impit-Test'] == '👾🕵🏻‍♂️🧑‍💻'
346+
assert response.json()['form']['Impit-Test'] == '👾🕵🏻‍♂️🧑‍💻'
355347

356348
def test_passing_binary_body(self, browser: Browser) -> None:
357349
impit = Client(browser=browser)
@@ -383,7 +375,7 @@ def test_passing_binary_body(self, browser: Browser) -> None:
383375
headers={'Content-Type': 'application/json'},
384376
)
385377
assert response.status_code == 200
386-
assert json.loads(response.text)['data'] == 'Impit-Test:foořžš'
378+
assert response.json()['data'] == 'Impit-Test:foořžš'
387379

388380
@pytest.mark.parametrize(
389381
('method'),
@@ -396,7 +388,7 @@ def test_methods_accept_request_body(self, browser: Browser, method: str) -> Non
396388

397389
response = m(get_httpbin_url(f'/{method.lower()}'), content=b'foo')
398390
assert response.status_code == 200
399-
assert json.loads(response.text)['data'] == 'foo'
391+
assert response.json()['data'] == 'foo'
400392

401393
def test_content(self, browser: Browser) -> None:
402394
impit = Client(browser=browser)
@@ -408,6 +400,14 @@ def test_content(self, browser: Browser) -> None:
408400
assert isinstance(response.text, str)
409401
assert response.content.decode('utf-8') == response.text
410402

403+
def test_json(self, browser: Browser) -> None:
404+
impit = Client(browser=browser)
405+
406+
response = impit.get(get_httpbin_url('/get'))
407+
408+
assert response.status_code == 200
409+
assert response.json() == json.loads(response.text)
410+
411411

412412
@pytest.mark.parametrize(
413413
('browser'),

0 commit comments

Comments
 (0)