Skip to content

Commit

Permalink
Implement req.clone() (#1361)
Browse files Browse the repository at this point in the history
* Implement req.clone()

* Update CHANGES
  • Loading branch information
asvetlov authored Nov 3, 2016
1 parent 7e2b762 commit 101a9ab
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 2 deletions.
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ CHANGES

- Don't flap `tcp_cork` in client code, use TCP_NODELAY mode by default.

-
- Implement `web.Request.clone()` #1361

-

Expand Down
36 changes: 35 additions & 1 deletion aiohttp/web_reqrep.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ class Request(collections.MutableMapping, HeadersMixin):
def __init__(self, message, payload, transport, reader, writer,
time_service, *,
secure_proxy_ssl_header=None):
self._app = None
self._message = message
self._transport = transport
self._reader = reader
Expand All @@ -74,6 +73,41 @@ def __init__(self, message, payload, transport, reader, writer,
self._state = {}
self._cache = {}

def clone(self, *, method=sentinel, rel_url=sentinel,
headers=sentinel):
"""Clone itself with replacement some attributes.
Creates and returns a new instance of Request object. If no parameters
are given, an exact copy is returned. If a parameter is not passed, it
will reuse the one from the current request object.
"""

if self._read_bytes:
raise RuntimeError("Cannot clone request "
"after reading it's content")

dct = {}
if method is not sentinel:
dct['method'] = method
if rel_url is not sentinel:
dct['path'] = str(URL(rel_url))
if headers is not sentinel:
dct['headers'] = CIMultiDict(headers)
dct['raw_headers'] = [(k.encode('utf-8'), v.encode('utf-8'))
for k, v in headers.items()]

message = self._message._replace(**dct)

return Request(
message,
self._payload,
self._transport,
self._reader,
self._writer,
self._time_service,
secure_proxy_ssl_header=self._secure_proxy_ssl_header)

# MutableMapping API

def __getitem__(self, key):
Expand Down
17 changes: 17 additions & 0 deletions docs/web_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,23 @@ like one using :meth:`Request.copy`.
*If-Modified-Since* header is absent or is not a valid
HTTP date.

.. method:: clone(*, method=..., rel_url=..., headers=...)

Clone itself with replacement some attributes.

Creates and returns a new instance of Request object. If no parameters
are given, an exact copy is returned. If a parameter is not passed, it
will reuse the one from the current request object.

:param str method: http method

:param rel_url: url to use, :class:`str` or :class:`~yarl.URL`

:param headers: :class:`~multidict.CIMultidict` or compatible
headers container.

:return: a cloned :class:`Request` instance.

.. coroutinemethod:: read()

Read request body, returns :class:`bytes` object with body content.
Expand Down
52 changes: 52 additions & 0 deletions tests/test_web_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from yarl import URL

from aiohttp.protocol import HttpVersion
from aiohttp.streams import StreamReader
from aiohttp.test_utils import make_mocked_request


Expand Down Expand Up @@ -266,3 +267,54 @@ def test_rel_url(make_request):
def test_url_url(make_request):
req = make_request('GET', '/path', headers={'HOST': 'example.com'})
assert URL('http://example.com/path') == req.url


def test_clone():
req = make_mocked_request('GET', '/path')
req2 = req.clone()
assert req2.method == 'GET'
assert req2.rel_url == URL('/path')


def test_clone_method():
req = make_mocked_request('GET', '/path')
req2 = req.clone(method='POST')
assert req2.method == 'POST'
assert req2.rel_url == URL('/path')


def test_clone_rel_url():
req = make_mocked_request('GET', '/path')
req2 = req.clone(rel_url=URL('/path2'))
assert req2.rel_url == URL('/path2')


def test_clone_rel_url_str():
req = make_mocked_request('GET', '/path')
req2 = req.clone(rel_url='/path2')
assert req2.rel_url == URL('/path2')


def test_clone_headers():
req = make_mocked_request('GET', '/path', headers={'A': 'B'})
req2 = req.clone(headers=CIMultiDict({'B': 'C'}))
assert req2.headers == CIMultiDict({'B': 'C'})
assert req2.raw_headers == ((b'B', b'C'),)


def test_clone_headers_dict():
req = make_mocked_request('GET', '/path', headers={'A': 'B'})
req2 = req.clone(headers={'B': 'C'})
assert req2.headers == CIMultiDict({'B': 'C'})
assert req2.raw_headers == ((b'B', b'C'),)


@asyncio.coroutine
def test_cannot_clone_after_read(loop):
payload = StreamReader(loop=loop)
payload.feed_data(b'data')
payload.feed_eof()
req = make_mocked_request('GET', '/path', payload=payload)
yield from req.read()
with pytest.raises(RuntimeError):
req.clone()

0 comments on commit 101a9ab

Please sign in to comment.