diff --git a/aiohttp/web_reqrep.py b/aiohttp/web_reqrep.py index a4b5d3a9f67..02432f40890 100644 --- a/aiohttp/web_reqrep.py +++ b/aiohttp/web_reqrep.py @@ -26,7 +26,9 @@ from .streams import EOF_MARKER -__all__ = ('ContentCoding', 'Request', 'StreamResponse', 'Response') +__all__ = ( + 'ContentCoding', 'Request', 'StreamResponse', 'Response', 'JSONResponse' +) sentinel = object() @@ -810,3 +812,18 @@ def write_eof(self): if body is not None: self.write(body) yield from super().write_eof() + + +class JSONResponse(Response): + + def __init__(self, data=sentinel, *, text=None, body=None, status=200, + reason=None, headers=None, content_type='application/json', + dumps=json.dumps): + if data is not sentinel and (text or body): + raise ValueError( + 'only one of data, text, or body should be specified' + ) + text = text or dumps(data) + content_type = content_type or self.content_type + super().__init__(text=text, body=body, status=status, reason=reason, + content_type=content_type) diff --git a/tests/test_web_response.py b/tests/test_web_response.py index 6e4e346917d..c5ee5c4748d 100644 --- a/tests/test_web_response.py +++ b/tests/test_web_response.py @@ -1,10 +1,13 @@ import datetime +import json import pytest import re from unittest import mock from aiohttp import hdrs, signals from aiohttp.multidict import CIMultiDict -from aiohttp.web import ContentCoding, Request, StreamResponse, Response +from aiohttp.web import ( + ContentCoding, Request, StreamResponse, Response, JSONResponse +) from aiohttp.protocol import HttpVersion, HttpVersion11, HttpVersion10 from aiohttp.protocol import RawRequestMessage @@ -836,3 +839,46 @@ def test_text_with_empty_payload(): resp = Response(status=200) assert resp.body is None assert resp.text is None + + +class TestJSONResponse: + + def test_content_type_is_application_json_by_default(self): + resp = JSONResponse('') + assert 'application/json' == resp.content_type + + def test_passing_text_only(self): + resp = JSONResponse(text=json.dumps('jaysawn')) + assert resp.text == json.dumps('jaysawn') + + def test_data_and_text_raises_value_error(self): + with pytest.raises(ValueError) as excinfo: + JSONResponse(data='foo', text='bar') + expected_message = ( + 'only one of data, text, or body should be specified' + ) + assert expected_message == excinfo.value.args[0] + + def test_data_and_body_raises_value_error(self): + with pytest.raises(ValueError) as excinfo: + JSONResponse(data='foo', body=b'bar') + expected_message = ( + 'only one of data, text, or body should be specified' + ) + assert expected_message == excinfo.value.args[0] + + def test_text_is_json_encoded(self): + resp = JSONResponse({'foo': 42}) + assert json.dumps({'foo': 42}) == resp.text + + def test_content_type_is_overrideable(self): + resp = JSONResponse({'foo': 42}, + content_type='application/vnd.json+api') + assert 'application/vnd.json+api' == resp.content_type + + def test_content_type_is_overrideable_as_class_var(self): + class MyJSONResponse(JSONResponse): + content_type = 'application/vnd.json+api' + + resp = MyJSONResponse('jaysawhn') + assert 'application/vnd.json+api' == resp.content_type