From c65fefbef12b2fcee62c21e6705cfaeda44e88a7 Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Wed, 22 Mar 2017 19:47:32 +0300 Subject: [PATCH 1/3] Implement history for ClientResponseError. ClientResponseError will contain history from ClientResponse. Implememnts: #1741 --- CHANGES.rst | 2 ++ aiohttp/client_exceptions.py | 4 ++- aiohttp/client_reqrep.py | 9 ++++-- docs/client_reference.rst | 3 ++ tests/test_client_response.py | 58 +++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 5ed048099db..06351daee04 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,8 @@ Changes - Dropped "%O" in access logger #1673 +- Added `history` to `ClientResponseError`. #1741 + 2.0.2 (2017-03-21) ------------------ diff --git a/aiohttp/client_exceptions.py b/aiohttp/client_exceptions.py index 61c5eec5056..16637c93d17 100644 --- a/aiohttp/client_exceptions.py +++ b/aiohttp/client_exceptions.py @@ -25,11 +25,13 @@ class ClientResponseError(ClientError): :param request_info: instance of RequestInfo """ - def __init__(self, request_info, *, code=0, message='', headers=None): + def __init__(self, request_info, *, code=0, message='', headers=None, + history=None): self.request_info = request_info self.code = code self.message = message self.headers = headers + self.history = history if history is not None else [] super().__init__("%s, message='%s'" % (code, message)) diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 673dccf3007..3f17da2d3cb 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -548,7 +548,8 @@ def start(self, connection, read_until_eof=False): raise ClientResponseError( self.request_info, code=exc.code, - message=exc.message, headers=exc.headers) from exc + message=exc.message, headers=exc.headers, + history=self.history) from exc if (message.code < 100 or message.code > 199 or message.code == 101): @@ -634,7 +635,8 @@ def raise_for_status(self): self.request_info, code=self.status, message=self.reason, - headers=self.headers) + headers=self.headers, + history=self.history) def _cleanup_writer(self): if self._writer is not None and not self._writer.done(): @@ -709,7 +711,8 @@ def json(self, *, encoding=None, loads=json.loads, self.request_info, message=('Attempt to decode JSON with ' 'unexpected mimetype: %s' % ctype), - headers=self.headers) + headers=self.headers, + history=self.history) stripped = self._content.strip() if not stripped: diff --git a/docs/client_reference.rst b/docs/client_reference.rst index e2dc11817d8..597e19483e3 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -1335,6 +1335,9 @@ Hierarchy of exceptions: .. attribute:: request_info Instance of `RequestInfo` object, contains information about request. + .. attribute:: history + History from `ClientResponse` object, if available, else empty list. + * `WSServerHandshakeError` - web socket server response error - `ClientHttpProxyError` - proxy response diff --git a/tests/test_client_response.py b/tests/test_client_response.py index fd66fcabd2b..afb3c4a4ceb 100644 --- a/tests/test_client_response.py +++ b/tests/test_client_response.py @@ -491,3 +491,61 @@ def test_request_info_in_exception(): with pytest.raises(aiohttp.ClientResponseError) as cm: response.raise_for_status() assert cm.value.request_info == response.request_info + + +def test_no_redirect_history_in_exception(): + url = 'http://def-cl-resp.org' + headers = {'Content-Type': 'application/json;charset=cp1251'} + response = ClientResponse( + 'get', + URL(url), + request_info=RequestInfo( + url, + 'get', + headers + ) + ) + response.status = 409 + response.reason = 'CONFLICT' + with pytest.raises(aiohttp.ClientResponseError) as cm: + response.raise_for_status() + assert [] == cm.value.history + + +def test_redirect_history_in_exception(): + hist_url = 'http://def-cl-resp.org' + url = 'http://def-cl-resp.org/index.htm' + hist_headers = {'Content-Type': 'application/json;charset=cp1251', + 'Location': url + } + headers = {'Content-Type': 'application/json;charset=cp1251'} + response = ClientResponse( + 'get', + URL(url), + request_info=RequestInfo( + url, + 'get', + headers + ) + ) + response.status = 409 + response.reason = 'CONFLICT' + + hist_response = ClientResponse( + 'get', + URL(hist_url), + request_info=RequestInfo( + url, + 'get', + headers + ) + ) + + hist_response.headers = hist_headers + hist_response.status = 301 + hist_response.reason = 'REDIRECT' + + response._history = [hist_response] + with pytest.raises(aiohttp.ClientResponseError) as cm: + response.raise_for_status() + assert [hist_response] == cm.value.history From 1d2d6ebdbed7059c8060a259a4dfea12481e9bee Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Wed, 22 Mar 2017 19:56:23 +0300 Subject: [PATCH 2/3] Fix typing History type is tuple --- aiohttp/client_exceptions.py | 2 +- docs/client_reference.rst | 2 +- tests/test_client_response.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aiohttp/client_exceptions.py b/aiohttp/client_exceptions.py index 16637c93d17..d53af3f85f5 100644 --- a/aiohttp/client_exceptions.py +++ b/aiohttp/client_exceptions.py @@ -31,7 +31,7 @@ def __init__(self, request_info, *, code=0, message='', headers=None, self.code = code self.message = message self.headers = headers - self.history = history if history is not None else [] + self.history = history if history is not None else () super().__init__("%s, message='%s'" % (code, message)) diff --git a/docs/client_reference.rst b/docs/client_reference.rst index 597e19483e3..24a308286a4 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -1336,7 +1336,7 @@ Hierarchy of exceptions: Instance of `RequestInfo` object, contains information about request. .. attribute:: history - History from `ClientResponse` object, if available, else empty list. + History from `ClientResponse` object, if available, else empty tuple. * `WSServerHandshakeError` - web socket server response error diff --git a/tests/test_client_response.py b/tests/test_client_response.py index afb3c4a4ceb..46e582cf637 100644 --- a/tests/test_client_response.py +++ b/tests/test_client_response.py @@ -509,7 +509,7 @@ def test_no_redirect_history_in_exception(): response.reason = 'CONFLICT' with pytest.raises(aiohttp.ClientResponseError) as cm: response.raise_for_status() - assert [] == cm.value.history + assert () == cm.value.history def test_redirect_history_in_exception(): From ae64ee6f794a4bdc592c7ec534473f98165e21b8 Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Wed, 22 Mar 2017 20:22:53 +0300 Subject: [PATCH 3/3] Make history attribute mandatory for ClientResponseError --- aiohttp/client.py | 4 ++++ aiohttp/client_exceptions.py | 6 +++--- aiohttp/client_reqrep.py | 13 ++++++------- aiohttp/connector.py | 1 + 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index 6937b43f973..ae46d7f12f2 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -386,6 +386,7 @@ def _ws_connect(self, url, *, if resp.status != 101: raise WSServerHandshakeError( resp.request_info, + resp.history, message='Invalid response status', code=resp.status, headers=resp.headers) @@ -393,6 +394,7 @@ def _ws_connect(self, url, *, if resp.headers.get(hdrs.UPGRADE, '').lower() != 'websocket': raise WSServerHandshakeError( resp.request_info, + resp.history, message='Invalid upgrade header', code=resp.status, headers=resp.headers) @@ -400,6 +402,7 @@ def _ws_connect(self, url, *, if resp.headers.get(hdrs.CONNECTION, '').lower() != 'upgrade': raise WSServerHandshakeError( resp.request_info, + resp.history, message='Invalid connection header', code=resp.status, headers=resp.headers) @@ -411,6 +414,7 @@ def _ws_connect(self, url, *, if key != match: raise WSServerHandshakeError( resp.request_info, + resp.history, message='Invalid challenge response', code=resp.status, headers=resp.headers) diff --git a/aiohttp/client_exceptions.py b/aiohttp/client_exceptions.py index d53af3f85f5..75fbaad8058 100644 --- a/aiohttp/client_exceptions.py +++ b/aiohttp/client_exceptions.py @@ -25,13 +25,13 @@ class ClientResponseError(ClientError): :param request_info: instance of RequestInfo """ - def __init__(self, request_info, *, code=0, message='', headers=None, - history=None): + def __init__(self, request_info, history, *, + code=0, message='', headers=None): self.request_info = request_info self.code = code self.message = message self.headers = headers - self.history = history if history is not None else () + self.history = history super().__init__("%s, message='%s'" % (code, message)) diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 3f17da2d3cb..16d1385d64d 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -546,10 +546,9 @@ def start(self, connection, read_until_eof=False): (message, payload) = yield from self._protocol.read() except http.HttpProcessingError as exc: raise ClientResponseError( - self.request_info, + self.request_info, self.history, code=exc.code, - message=exc.message, headers=exc.headers, - history=self.history) from exc + message=exc.message, headers=exc.headers) from exc if (message.code < 100 or message.code > 199 or message.code == 101): @@ -633,10 +632,10 @@ def raise_for_status(self): if 400 <= self.status: raise ClientResponseError( self.request_info, + self.history, code=self.status, message=self.reason, - headers=self.headers, - history=self.history) + headers=self.headers) def _cleanup_writer(self): if self._writer is not None and not self._writer.done(): @@ -709,10 +708,10 @@ def json(self, *, encoding=None, loads=json.loads, if content_type not in ctype: raise ClientResponseError( self.request_info, + self.history, message=('Attempt to decode JSON with ' 'unexpected mimetype: %s' % ctype), - headers=self.headers, - history=self.history) + headers=self.headers) stripped = self._content.strip() if not stripped: diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 70a517a77f2..5e7f5a2fb4f 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -727,6 +727,7 @@ def _create_proxy_connection(self, req): if resp.status != 200: raise ClientHttpProxyError( proxy_resp.request_info, + resp.history, code=resp.status, message=resp.reason, headers=resp.headers)