From 350d703595f54a7104e75d1c27774dbaa0e4a069 Mon Sep 17 00:00:00 2001 From: Daniel Reed Date: Sun, 8 Sep 2024 12:22:08 -0700 Subject: [PATCH] Accept Request(json=...) and provide Response.json(). See https://urllib3.readthedocs.io/en/stable/user-guide.html#json and https://urllib3.readthedocs.io/en/stable/user-guide.html#json-content. --- nh2/connection.py | 5 +++-- nh2/rex.py | 14 +++++++++++++- nh2/test_connection.py | 8 +++----- nh2/test_rex.py | 17 +++++++++++++++++ 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/nh2/connection.py b/nh2/connection.py index 5c74734..0b2bbe0 100644 --- a/nh2/connection.py +++ b/nh2/connection.py @@ -30,10 +30,11 @@ def __init__(self, host, port): self.streams = {} - def request(self, method, path, *, headers=(), body=None): + def request(self, method, path, *, headers=(), body=None, json=None): # pylint: disable=too-many-arguments """Send a method request for path.""" - return self.send(nh2.rex.Request(method, self.host, path, headers=headers, body=body)) + return self.send( + nh2.rex.Request(method, self.host, path, headers=headers, body=body, json=json)) def send(self, request): """Send the given Request.""" diff --git a/nh2/rex.py b/nh2/rex.py index fb77fc4..66b204f 100644 --- a/nh2/rex.py +++ b/nh2/rex.py @@ -1,5 +1,7 @@ """HTTP/2 requests and responses.""" +import json as _json + class ContentType: """A structured view of the content-type header.""" @@ -25,7 +27,7 @@ def __str__(self): class Request: """An HTTP/2 request.""" - def __init__(self, method, host, path, *, headers=(), body=None): # pylint: disable=too-many-arguments + def __init__(self, method, host, path, *, headers=(), body=None, json=None): # pylint: disable=too-many-arguments self.method = method self.host = host self.path = path @@ -37,6 +39,11 @@ def __init__(self, method, host, path, *, headers=(), body=None): # pylint: dis } self.headers.update(headers) self.contenttype = ContentType(self.headers.get('content-type', '')) + if json is not None: + assert not body + assert self.contenttype.mediatype is None + self.contenttype = ContentType('application/json') + body = _json.dumps(json, separators=(',', ':')) if body and isinstance(body, str): assert self.contenttype.charset is None if self.contenttype.mediatype is None: @@ -57,3 +64,8 @@ def __init__(self, request, headers, body): self.status = int(headers[':status']) self.contenttype = ContentType(headers.get('content-type', '')) self.body = body + + def json(self): + """Parse (and return) self.body as a JSON object.""" + + return _json.loads(self.body) diff --git a/nh2/test_connection.py b/nh2/test_connection.py index a0abaef..b29ddf8 100644 --- a/nh2/test_connection.py +++ b/nh2/test_connection.py @@ -1,7 +1,5 @@ """Tests for nh2.connection.""" -import json - import nh2.connection import nh2.rex @@ -19,10 +17,10 @@ def test_simple(): assert len(responses) == 1 assert responses[0].status == 200 assert responses[0].headers['content-type'] == 'application/json' - response = json.loads(responses[0].body) + response = responses[0].json() assert response['args'] == {'a': 'b'} - conn.request('POST', '/post', body='{"c": "d"}') + conn.request('POST', '/post', json={'c': 'd'}) responses = None while not responses: responses = conn.read() @@ -30,7 +28,7 @@ def test_simple(): assert len(responses) == 1 assert responses[0].status == 200 assert responses[0].headers['content-type'] == 'application/json' - response = json.loads(responses[0].body) + response = responses[0].json() assert response['json'] == {'c': 'd'} finally: conn.close() diff --git a/nh2/test_rex.py b/nh2/test_rex.py index c67f3f3..7a34a5b 100644 --- a/nh2/test_rex.py +++ b/nh2/test_rex.py @@ -60,6 +60,14 @@ def test_request_simple(): assert request.body == b'\xe2\x80\xa2' +def test_request_json(): + """Verify JSON-related logic.""" + + request = nh2.rex.Request('PUT', 'example.com', '/test', json={'a': '\u2022'}) + assert request.contenttype.mediatype == 'application/json' + assert request.body == b'{"a":"\\u2022"}' + + def test_response_simple(): """Basic Response functionality.""" @@ -72,3 +80,12 @@ def test_response_simple(): assert response.contenttype.charset is None assert response.contenttype.boundary is None assert response.body == b'' + + +def test_response_json(): + """Verify JSON-related logic.""" + + request = nh2.rex.Request('PUT', 'example.com', '/test') + response = nh2.rex.Response(request, {':status': '200'}, b'{"a": "\\u2022 \xe2\x80\xa2"}') + assert response.body == b'{"a": "\\u2022 \xe2\x80\xa2"}' + assert response.json() == {'a': '\u2022 \u2022'}