From ecc8e634cc6613001cb832707a8160eb7e97d4aa Mon Sep 17 00:00:00 2001 From: Pau Freixes Date: Thu, 27 Jul 2017 16:23:27 +0200 Subject: [PATCH] =?UTF-8?q?Raise=20ContentTypeError=20by=20json()=20when?= =?UTF-8?q?=20headers=20does=20not=20=E2=80=A6=20(#2136)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Raise ClientResponseContentTypeError by json() when headers does not meet the spec. * Docs changed to reflect the exception raised by json() * Added #2136 as a new feature change * Renamed long ClientResonseContentTypeError to ContentTypeError * Update client_exceptions.py --- aiohttp/client_exceptions.py | 6 ++++++ aiohttp/client_reqrep.py | 4 ++-- changes/2136.feature | 2 ++ docs/client_reference.rst | 10 +++++++--- tests/test_client_response.py | 13 +++++++++++-- 5 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 changes/2136.feature diff --git a/aiohttp/client_exceptions.py b/aiohttp/client_exceptions.py index e3e6c3c9556..72b0fc6d5d6 100644 --- a/aiohttp/client_exceptions.py +++ b/aiohttp/client_exceptions.py @@ -13,6 +13,8 @@ 'ServerFingerprintMismatch', 'ClientResponseError', 'ClientPayloadError', + 'ContentTypeError', + 'ClientHttpProxyError', 'WSServerHandshakeError') @@ -37,6 +39,10 @@ def __init__(self, request_info, history, *, super().__init__("%s, message='%s'" % (code, message)) +class ContentTypeError(ClientResponseError): + """ContentType found is not valid.""" + + class WSServerHandshakeError(ClientResponseError): """websocket server handshake error.""" diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 3fa52de8d52..5eff7de626a 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -13,7 +13,7 @@ from . import hdrs, helpers, http, payload from .client_exceptions import (ClientConnectionError, ClientOSError, - ClientResponseError) + ClientResponseError, ContentTypeError) from .formdata import FormData from .helpers import PY_35, HeadersMixin, SimpleCookie, TimerNoop, noop from .http import SERVER_SOFTWARE, HttpVersion10, HttpVersion11, PayloadWriter @@ -722,7 +722,7 @@ def json(self, *, encoding=None, loads=json.loads, if content_type: ctype = self.headers.get(hdrs.CONTENT_TYPE, '').lower() if content_type not in ctype: - raise ClientResponseError( + raise ContentTypeError( self.request_info, self.history, message=('Attempt to decode JSON with ' diff --git a/changes/2136.feature b/changes/2136.feature new file mode 100644 index 00000000000..1aca0e0e2cb --- /dev/null +++ b/changes/2136.feature @@ -0,0 +1,2 @@ +json() raises a ContentTypeError exception if the content-type +does not meet the requirements instead of raising a generic ClientResponseError. diff --git a/docs/client_reference.rst b/docs/client_reference.rst index 889d6cb41ef..553bea43a40 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -1050,6 +1050,9 @@ Response object Close underlying connection if data reading gets an error, release connection otherwise. + Raise an :exc:`aiohttp.ClientResponseError` if the data can't + be read. + :return bytes: read *BODY*. .. seealso:: :meth:`close`, :meth:`release`. @@ -1104,15 +1107,16 @@ Response object content_type='application/json') Read response's body as *JSON*, return :class:`dict` using - specified *encoding* and *loader*. + specified *encoding* and *loader*. If data is not still available + a ``read`` call will be done, If *encoding* is ``None`` content encoding is autocalculated using :term:`cchardet` or :term:`chardet` as fallback if *cchardet* is not available. if response's `content-type` does not match `content_type` parameter - :exc:`aiohttp.ClientResponseError` get raised. To disable content type - check pass ``None`` value. + :exc:`aiohttp.ContentTypeError` get raised. + To disable content type check pass ``None`` value. :param str encoding: text encoding used for *BODY* decoding, or ``None`` for encoding autodetection diff --git a/tests/test_client_response.py b/tests/test_client_response.py index 5188730ae25..82b30792cec 100644 --- a/tests/test_client_response.py +++ b/tests/test_client_response.py @@ -352,18 +352,27 @@ def custom(content): @asyncio.coroutine -def test_json_no_content(loop, session): +def test_json_invalid_content_type(loop, session): response = ClientResponse('get', URL('http://def-cl-resp.org')) response._post_init(loop, session) response.headers = { 'Content-Type': 'data/octet-stream'} response._content = b'' - with pytest.raises(aiohttp.ClientResponseError) as info: + with pytest.raises(aiohttp.ContentTypeError) as info: yield from response.json() assert info.value.request_info == response.request_info + +@asyncio.coroutine +def test_json_no_content(loop, session): + response = ClientResponse('get', URL('http://def-cl-resp.org')) + response._post_init(loop, session) + response.headers = { + 'Content-Type': 'data/octet-stream'} + response._content = b'' + res = yield from response.json(content_type=None) assert res is None