From c95151d7c8bc4288333de08d42d2338a28160df3 Mon Sep 17 00:00:00 2001 From: Steven Loria Date: Wed, 28 Oct 2015 01:01:20 -0400 Subject: [PATCH 1/2] Add JSONResponse closes #592 --- aiohttp/web_reqrep.py | 21 ++++++++++++++++++++- tests/test_web_response.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/aiohttp/web_reqrep.py b/aiohttp/web_reqrep.py index a4b5d3a9f67..e299ef2316d 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,20 @@ def write_eof(self): if body is not None: self.write(body) yield from super().write_eof() + + +def json_dumps(data): + return json.dumps(data).encode('utf-8') + + +class JSONResponse(Response): + content_type = 'application/json' + + def __init__(self, data, *, body=None, status=200, + reason=None, headers=None, content_type=None, + dumps=json_dumps): + self.data = data + body = dumps(self.data) + content_type = content_type or self.content_type + super().__init__(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..c45d23dd559 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,30 @@ def test_text_with_empty_payload(): resp = Response(status=200) assert resp.body is None assert resp.text is None + + +class TestJSONResponse: + + def test_data_attribute(self): + resp = JSONResponse('jaysawhn') + assert 'jaysawhn' == resp.data + + def test_content_type_is_application_json_by_default(self): + resp = JSONResponse('') + assert 'application/json' == resp.content_type + + def test_body_is_json_encoded(self): + resp = JSONResponse({'foo': 42}) + assert json.dumps({'foo': 42}).encode('utf-8') == resp.body + + 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 From b7ec83ce3d0e6eaa9fa893737d80f3ec74c413ec Mon Sep 17 00:00:00 2001 From: Steven Loria Date: Wed, 28 Oct 2015 22:50:01 -0400 Subject: [PATCH 2/2] Make data, text, and body mutually exclusive Also, pass `text` rather than `body` --- aiohttp/web_reqrep.py | 20 +++++++++----------- tests/test_web_response.py | 28 ++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/aiohttp/web_reqrep.py b/aiohttp/web_reqrep.py index e299ef2316d..02432f40890 100644 --- a/aiohttp/web_reqrep.py +++ b/aiohttp/web_reqrep.py @@ -814,18 +814,16 @@ def write_eof(self): yield from super().write_eof() -def json_dumps(data): - return json.dumps(data).encode('utf-8') - - class JSONResponse(Response): - content_type = 'application/json' - def __init__(self, data, *, body=None, status=200, - reason=None, headers=None, content_type=None, - dumps=json_dumps): - self.data = data - body = dumps(self.data) + 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__(body=body, status=status, reason=reason, + 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 c45d23dd559..c5ee5c4748d 100644 --- a/tests/test_web_response.py +++ b/tests/test_web_response.py @@ -843,17 +843,33 @@ def test_text_with_empty_payload(): class TestJSONResponse: - def test_data_attribute(self): - resp = JSONResponse('jaysawhn') - assert 'jaysawhn' == resp.data - def test_content_type_is_application_json_by_default(self): resp = JSONResponse('') assert 'application/json' == resp.content_type - def test_body_is_json_encoded(self): + 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}).encode('utf-8') == resp.body + assert json.dumps({'foo': 42}) == resp.text def test_content_type_is_overrideable(self): resp = JSONResponse({'foo': 42},