Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client response #1733

Merged
merged 7 commits into from
Mar 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Changes

- Content disposition with semicolon in filename #917

- Added `request_info` to response object and `ClientResponseError`.

2.0.0 (2017-03-20)
------------------
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Alexander Travov
Alexandru Mihai
Alexey Firsov
Alexey Popravka
Alexey Stepanov
Alex Key
Alex Khomchenko
Alex Lisovoy
Expand Down
4 changes: 3 additions & 1 deletion aiohttp/client_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ class ClientResponseError(ClientError):
message = ''
headers = None

def __init__(self, *, code=None, message='', headers=None):
def __init__(self, *, code=None, message='', headers=None,
request_info=None):
if code is not None:
self.code = code
self.message = message
self.headers = headers
self.request_info = request_info

super().__init__("%s, message='%s'" % (self.code, message))

Expand Down
19 changes: 16 additions & 3 deletions aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import collections
import io
import json
import sys
Expand Down Expand Up @@ -27,6 +28,9 @@
__all__ = ('ClientRequest', 'ClientResponse')


RequestInfo = collections.namedtuple('RequestInfo', ('url', 'headers'))


class ClientRequest:

GET_METHODS = {hdrs.METH_GET, hdrs.METH_HEAD, hdrs.METH_OPTIONS}
Expand Down Expand Up @@ -385,7 +389,9 @@ def send(self, conn):

self.response = self.response_class(
self.method, self.original_url,
writer=self._writer, continue100=self._continue, timer=self._timer)
writer=self._writer, continue100=self._continue, timer=self._timer,
request_info=RequestInfo(self.url, self.headers)
)

self.response._post_init(self.loop)
return self.response
Expand Down Expand Up @@ -426,7 +432,8 @@ class ClientResponse(HeadersMixin):
_closed = True # to allow __del__ for non-initialized properly response

def __init__(self, method, url, *,
writer=None, continue100=None, timer=None):
writer=None, continue100=None, timer=None,
request_info=None):
assert isinstance(url, URL)

self.method = method
Expand All @@ -439,6 +446,7 @@ def __init__(self, method, url, *,
self._continue = continue100
self._closed = True
self._history = ()
self._request_info = request_info
self._timer = timer if timer is not None else TimerNoop()

@property
Expand All @@ -459,6 +467,10 @@ def host(self):
def _headers(self):
return self.headers

@property
def request_info(self):
return self._request_info

def _post_init(self, loop):
self._loop = loop
if loop.get_debug():
Expand Down Expand Up @@ -609,7 +621,8 @@ def raise_for_status(self):
raise ClientResponseError(
code=self.status,
message=self.reason,
headers=self.headers)
headers=self.headers,
request_info=self.request_info)

def _cleanup_writer(self):
if self._writer is not None and not self._writer.done():
Expand Down
7 changes: 7 additions & 0 deletions docs/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ It is not possible to use :meth:`~ClientResponse.read`,
:meth:`~ClientResponse.json` and :meth:`~ClientResponse.text` after
explicit reading from :attr:`~ClientResponse.content`.

RequestInfo
-----------

`ClientResponse` object contains :attr:`~ClientResponse.request_info` property,
which contains request fields: `url` and `headers`.
On `raise_for_status` structure is copied to `ClientResponseError` instance.


Custom Headers
--------------
Expand Down
5 changes: 5 additions & 0 deletions docs/client_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,11 @@ Response object
:return: *BODY* as *JSON* data parsed by *loads* parameter or
``None`` if *BODY* is empty or contains white-spaces only.

.. attribute:: request_info

A namedtuple with request URL and headers from :class:`ClientRequest`
object.


ClientWebSocketResponse
-----------------------
Expand Down
42 changes: 41 additions & 1 deletion tests/test_client_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import aiohttp
from aiohttp import helpers
from aiohttp.client_reqrep import ClientResponse
from aiohttp.client_reqrep import ClientResponse, RequestInfo


def test_del():
Expand Down Expand Up @@ -425,3 +425,43 @@ def test_charset_no_charset():
response.headers = {'Content-Type': 'application/json'}

assert response.charset is None


def test_response_request_info():
url = 'http://def-cl-resp.org'
headers = {'Content-Type': 'application/json;charset=cp1251'}
response = ClientResponse(
'get', URL(url),
request_info=RequestInfo(
url,
headers
)
)
assert url == response.request_info.url
assert headers == response.request_info.headers


def test_response_request_info_empty():
url = 'http://def-cl-resp.org'
response = ClientResponse(
'get', URL(url),
)
assert response.request_info is None


def test_request_info_in_exception():
url = 'http://def-cl-resp.org'
headers = {'Content-Type': 'application/json;charset=cp1251'}
response = ClientResponse(
'get',
URL(url),
request_info=RequestInfo(
url,
headers
)
)
response.status = 409
response.reason = 'CONFLICT'
with pytest.raises(aiohttp.ClientResponseError) as cm:
response.raise_for_status()
assert cm.value.request_info == response.request_info