From 3841b47ceb8abc46865410cb6a35ef584c18b03f Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 8 Nov 2017 18:40:12 +0200 Subject: [PATCH 1/9] Make AbstractPayloadWriter.write() an async function --- aiohttp/abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/abc.py b/aiohttp/abc.py index 70513be6f0a..0c3323031d7 100644 --- a/aiohttp/abc.py +++ b/aiohttp/abc.py @@ -123,7 +123,7 @@ class AbstractPayloadWriter(ABC): """Abstract payload writer.""" @abstractmethod - def write(self, chunk): + async def write(self, chunk): """Write chunk into stream.""" @abstractmethod From b398b98bbd17e39703153306c000791311fe8691 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 8 Nov 2017 18:48:03 +0200 Subject: [PATCH 2/9] Drop response.drain() from docs --- docs/web_lowlevel.rst | 3 +-- docs/web_reference.rst | 58 ++++++++++++++++-------------------------- 2 files changed, 23 insertions(+), 38 deletions(-) diff --git a/docs/web_lowlevel.rst b/docs/web_lowlevel.rst index df335195a2d..d9d0b1318da 100644 --- a/docs/web_lowlevel.rst +++ b/docs/web_lowlevel.rst @@ -30,8 +30,7 @@ parameter and performs one of the following actions: 2. Create a :class:`StreamResponse`, send headers by :meth:`StreamResponse.prepare` call, send data chunks by - :meth:`StreamResponse.write` / :meth:`StreamResponse.drain`, - return finished response. + :meth:`StreamResponse.write` and return finished response. 3. Raise :class:`HTTPException` derived exception (see :ref:`aiohttp-web-exceptions` section). diff --git a/docs/web_reference.rst b/docs/web_reference.rst index ad57ea4558d..299805c9a39 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -348,7 +348,7 @@ and :ref:`aiohttp-web-signals` handlers. :return: a cloned :class:`Request` instance. - .. coroutinemethod:: read() + .. comethod:: read() Read request body, returns :class:`bytes` object with body content. @@ -357,7 +357,7 @@ and :ref:`aiohttp-web-signals` handlers. The method **does** store read data internally, subsequent :meth:`~Request.read` call will return the same value. - .. coroutinemethod:: text() + .. comethod:: text() Read request body, decode it using :attr:`charset` encoding or ``UTF-8`` if no encoding was specified in *MIME-type*. @@ -369,7 +369,7 @@ and :ref:`aiohttp-web-signals` handlers. The method **does** store read data internally, subsequent :meth:`~Request.text` call will return the same value. - .. coroutinemethod:: json(*, loads=json.loads) + .. comethod:: json(*, loads=json.loads) Read request body decoded as *json*. @@ -391,7 +391,7 @@ and :ref:`aiohttp-web-signals` handlers. :meth:`~Request.json` call will return the same value. - .. coroutinemethod:: multipart(*, reader=aiohttp.multipart.MultipartReader) + .. comethod:: multipart(*, reader=aiohttp.multipart.MultipartReader) Returns :class:`aiohttp.multipart.MultipartReader` which processes incoming *multipart* request. @@ -412,7 +412,7 @@ and :ref:`aiohttp-web-signals` handlers. .. seealso:: :ref:`aiohttp-multipart` - .. coroutinemethod:: post() + .. comethod:: post() A :ref:`coroutine ` that reads POST parameters from request body. @@ -430,7 +430,7 @@ and :ref:`aiohttp-web-signals` handlers. The method **does** store read data internally, subsequent :meth:`~Request.post` call will return the same value. - .. coroutinemethod:: release() + .. comethod:: release() Release request. @@ -610,7 +610,7 @@ StreamResponse .. method:: enable_chunked_encoding Enables :attr:`chunked` encoding for response. There are no ways to - disable it back. With enabled :attr:`chunked` encoding each `write()` + disable it back. With enabled :attr:`chunked` encoding each :meth:`write` operation encoded in separate chunk. .. warning:: chunked encoding can be enabled for ``HTTP/1.1`` only. @@ -760,7 +760,7 @@ StreamResponse Clear :attr:`tcp_cork` if *value* is ``True``. - .. coroutinemethod:: prepare(request) + .. comethod:: prepare(request) :param aiohttp.web.Request request: HTTP request object, that the response answers. @@ -773,11 +773,13 @@ StreamResponse .. versionadded:: 0.18 - .. method:: write(data) + .. comethod:: write(data) - Send byte-ish data as the part of *response BODY*. + Send byte-ish data as the part of *response BODY*:: - :meth:`prepare` must be called before. + await resp.write(data) + + :meth:`prepare` must be invoked before the call. Raises :exc:`TypeError` if data is not :class:`bytes`, :class:`bytearray` or :class:`memoryview` instance. @@ -786,23 +788,7 @@ StreamResponse Raises :exc:`RuntimeError` if :meth:`write_eof` has been called. - .. coroutinemethod:: drain() - - A :ref:`coroutine` to let the write buffer of the - underlying transport a chance to be flushed. - - The intended use is to write:: - - resp.write(data) - await resp.drain() - - Yielding from :meth:`drain` gives the opportunity for the loop - to schedule the write operation and flush the buffer. It should - especially be used when a possibly large amount of data is - written to the transport, and the coroutine does not yield-from - between calls to :meth:`write`. - - .. coroutinemethod:: write_eof() + .. comethod:: write_eof() A :ref:`coroutine` *may* be called as a mark of the *HTTP response* processing finish. @@ -923,7 +909,7 @@ WebSocketResponse print(msg.data) - .. coroutinemethod:: prepare(request) + .. comethod:: prepare(request) Starts websocket. After the call you can use websocket methods. @@ -1403,7 +1389,7 @@ duplicated like one using :meth:`Application.copy`. await loop.create_server(app.make_handler(), '0.0.0.0', 8080) - .. coroutinemethod:: startup() + .. comethod:: startup() A :ref:`coroutine` that will be called along with the application's request handler. @@ -1411,7 +1397,7 @@ duplicated like one using :meth:`Application.copy`. The purpose of the method is calling :attr:`on_startup` signal handlers. - .. coroutinemethod:: shutdown() + .. comethod:: shutdown() A :ref:`coroutine` that should be called on server stopping but before :meth:`cleanup()`. @@ -1419,7 +1405,7 @@ duplicated like one using :meth:`Application.copy`. The purpose of the method is calling :attr:`on_shutdown` signal handlers. - .. coroutinemethod:: cleanup() + .. comethod:: cleanup() A :ref:`coroutine` that should be called on server stopping but after :meth:`shutdown`. @@ -1465,7 +1451,7 @@ A protocol factory compatible with .. versionadded:: 1.0 - .. coroutinemethod:: Server.shutdown(timeout) + .. comethod:: Server.shutdown(timeout) A :ref:`coroutine` that should be called to close all opened connections. @@ -1707,7 +1693,7 @@ Router is any object that implements :class:`AbstractRouter` interface. .. versionadded:: 1.1 - .. coroutinemethod:: resolve(request) + .. comethod:: resolve(request) A :ref:`coroutine` that returns :class:`AbstractMatchInfo` for *request*. @@ -1860,7 +1846,7 @@ Resource classes hierarchy:: Read-only *name* of resource or ``None``. - .. coroutinemethod:: resolve(method, path) + .. comethod:: resolve(method, path) Resolve resource by finding appropriate :term:`web-handler` for ``(method, path)`` combination. @@ -2057,7 +2043,7 @@ and *405 Method Not Allowed*. Actually it's a shortcut for ``route.resource.url_for(...)``. - .. coroutinemethod:: handle_expect_header(request) + .. comethod:: handle_expect_header(request) ``100-continue`` handler. From c1776229914bee3a67e50cb3b947d617c8477564 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 8 Nov 2017 18:54:52 +0200 Subject: [PATCH 3/9] Add removal changenote --- CHANGES/2483.removal | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 CHANGES/2483.removal diff --git a/CHANGES/2483.removal b/CHANGES/2483.removal new file mode 100644 index 00000000000..d715f282945 --- /dev/null +++ b/CHANGES/2483.removal @@ -0,0 +1,2 @@ +`StreamResponse.drain()` is not a part of public API anymore, just use +`await resp.write()`. From 5840219d1acf4c27c52e5087612bb329d73bc7c3 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 8 Nov 2017 18:57:55 +0200 Subject: [PATCH 4/9] Add a deprecation warning for StreamResponse.drain() --- aiohttp/web_response.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aiohttp/web_response.py b/aiohttp/web_response.py index 5dfd5203b9a..ebe1ce0bf64 100644 --- a/aiohttp/web_response.py +++ b/aiohttp/web_response.py @@ -408,6 +408,9 @@ async def drain(self): assert not self._eof_sent, "EOF has already been sent" assert self._payload_writer is not None, \ "Response has not been started" + warnings.warn("drain method is deprecated, use await resp.write()", + DeprecationWarning, + stacklevel=2) await self._payload_writer.drain() async def write_eof(self, data=b''): From c4e11f558efb7d9fcbae484d73d0cc51f6eed5f0 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 9 Nov 2017 12:40:53 +0200 Subject: [PATCH 5/9] Fix several tests --- aiohttp/test_utils.py | 6 +++--- aiohttp/web_response.py | 4 ++-- tests/test_client_functional.py | 12 ++++-------- tests/test_web_functional.py | 8 ++++---- tests/test_web_protocol.py | 2 +- tests/test_web_response.py | 30 +++++++----------------------- 6 files changed, 21 insertions(+), 41 deletions(-) diff --git a/aiohttp/test_utils.py b/aiohttp/test_utils.py index 1daa3f55146..37fcf29e9e3 100644 --- a/aiohttp/test_utils.py +++ b/aiohttp/test_utils.py @@ -18,7 +18,7 @@ from aiohttp.client import _RequestContextManager from . import ClientSession, hdrs -from .helpers import noop, sentinel +from .helpers import sentinel from .http import HttpVersion, RawRequestMessage from .signals import Signal from .web import Request, Server, UrlMappingMatchInfo @@ -525,8 +525,8 @@ def make_mocked_request(method, path, headers=None, *, if payload_writer is sentinel: payload_writer = mock.Mock() - payload_writer.write_eof.side_effect = noop - payload_writer.drain.side_effect = noop + payload_writer.write_eof = make_mocked_coro(None) + payload_writer.drain = make_mocked_coro(None) protocol.transport = transport protocol.writer = writer diff --git a/aiohttp/web_response.py b/aiohttp/web_response.py index ebe1ce0bf64..f85695f2d95 100644 --- a/aiohttp/web_response.py +++ b/aiohttp/web_response.py @@ -393,7 +393,7 @@ def _start(self, request, return writer - def write(self, data): + async def write(self, data): assert isinstance(data, (bytes, bytearray, memoryview)), \ "data argument must be byte-ish (%r)" % type(data) @@ -402,7 +402,7 @@ def write(self, data): if self._payload_writer is None: raise RuntimeError("Cannot call write() before prepare()") - return self._payload_writer.write(data) + await self._payload_writer.write(data) async def drain(self): assert not self._eof_sent, "EOF has already been sent" diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py index 4187183b074..13697f11966 100644 --- a/tests/test_client_functional.py +++ b/tests/test_client_functional.py @@ -630,8 +630,7 @@ async def handler(request): # make sure connection is closed by client. with pytest.raises(aiohttp.ServerDisconnectedError): for _ in range(10): - resp_.write(b'data\n') - await resp_.drain() + await resp_.write(b'data\n') await asyncio.sleep(0.5, loop=loop) return resp_ @@ -665,8 +664,7 @@ async def test_no_error_on_conn_close_if_eof(loop, test_client): async def handler(request): resp_ = web.StreamResponse() await resp_.prepare(request) - resp_.write(b'data\n') - await resp_.drain() + await resp_.write(b'data\n') await asyncio.sleep(0.5, loop=loop) return resp_ @@ -1782,7 +1780,7 @@ async def handler(request): resp._length_check = False resp.headers['Transfer-Encoding'] = 'chunked' writer = await resp.prepare(request) - writer.write(b'9\r\n\r\n') + await writer.write(b'9\r\n\r\n') await writer.write_eof() return resp @@ -1972,9 +1970,7 @@ async def test_broken_connection_2(loop, test_client): async def handler(request): resp = web.StreamResponse(headers={'content-length': '1000'}) await resp.prepare(request) - await resp.drain() - resp.write(b'answer') - await resp.drain() + await resp.write(b'answer') request.transport.close() return resp diff --git a/tests/test_web_functional.py b/tests/test_web_functional.py index bfedc38e7e9..1dcc7f773c0 100644 --- a/tests/test_web_functional.py +++ b/tests/test_web_functional.py @@ -935,9 +935,9 @@ async def handler(request): resp = web.StreamResponse() resp.enable_chunked_encoding() await resp.prepare(request) - resp.write(b'x') - resp.write(b'y') - resp.write(b'z') + await resp.write(b'x') + await resp.write(b'y') + await resp.write(b'z') return resp app = web.Application() @@ -1464,7 +1464,7 @@ async def handler(request): await resp.prepare(request) await resp.drain() await asyncio.sleep(0.01, loop=loop) - resp.write(b'test') + await resp.write(b'test') await asyncio.sleep(0.01, loop=loop) await resp.write_eof() return resp diff --git a/tests/test_web_protocol.py b/tests/test_web_protocol.py index c0e3baeb55f..d93c65510e9 100644 --- a/tests/test_web_protocol.py +++ b/tests/test_web_protocol.py @@ -749,7 +749,7 @@ async def handle2(request): nonlocal processed resp = web.StreamResponse() await resp.prepare(request) - resp.write(b'test2') + await resp.write(b'test2') await resp.write_eof() processed.append(2) return resp diff --git a/tests/test_web_response.py b/tests/test_web_response.py index c353bc09e64..623b624091c 100644 --- a/tests/test_web_response.py +++ b/tests/test_web_response.py @@ -514,14 +514,14 @@ async def test_write_non_byteish(): await resp.prepare(make_request('GET', '/')) with pytest.raises(AssertionError): - resp.write(123) + await resp.write(123) -def test_write_before_start(): +async def test_write_before_start(): resp = StreamResponse() with pytest.raises(RuntimeError): - resp.write(b'data') + await resp.write(b'data') async def test_cannot_write_after_eof(): @@ -531,12 +531,12 @@ async def test_cannot_write_after_eof(): make_request('GET', '/', writer=writer)) resp_impl.write_eof = make_mocked_coro(None) - resp.write(b'data') + await resp.write(b'data') await resp.write_eof() writer.write.reset_mock() with pytest.raises(RuntimeError): - resp.write(b'next data') + await resp.write(b'next data') assert not writer.write.called @@ -546,7 +546,7 @@ async def test___repr___after_eof(): assert resp.prepared - resp.write(b'data') + await resp.write(b'data') await resp.write_eof() assert not resp.prepared resp_repr = repr(resp) @@ -567,7 +567,7 @@ async def test_cannot_write_eof_twice(): resp_impl.write = make_mocked_coro(None) resp_impl.write_eof = make_mocked_coro(None) - resp.write(b'data') + await resp.write(b'data') assert resp_impl.write.called await resp.write_eof() @@ -577,22 +577,6 @@ async def test_cannot_write_eof_twice(): assert not writer.write.called -async def _test_write_returns_drain(): - resp = StreamResponse() - await resp.prepare(make_request('GET', '/')) - - with mock.patch('aiohttp.http_writer.noop') as noop: - assert noop == resp.write(b'data') - - -async def _test_write_returns_empty_tuple_on_empty_data(): - resp = StreamResponse() - await resp.prepare(make_request('GET', '/')) - - with mock.patch('aiohttp.http_writer.noop') as noop: - assert noop.return_value == resp.write(b'') - - def test_force_close(): resp = StreamResponse() From 0c635d199155abb0e9309df60e71b4c77e80f3c9 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 9 Nov 2017 13:20:50 +0200 Subject: [PATCH 6/9] Fix doc --- aiohttp/http_writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/http_writer.py b/aiohttp/http_writer.py index c9b0d06f650..32228e8b6e1 100644 --- a/aiohttp/http_writer.py +++ b/aiohttp/http_writer.py @@ -116,7 +116,7 @@ async def drain(self): The intended use is to write - w.write(data) + await w.write(data) await w.drain() """ if self._protocol.transport is not None: From 602112e3eccfcf4084a6792107e3b4fe2da46d07 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 9 Nov 2017 13:39:42 +0200 Subject: [PATCH 7/9] Polish README --- README.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index d26430fa352..5a06b4952b0 100644 --- a/README.rst +++ b/README.rst @@ -3,19 +3,22 @@ Async http client/server framework ================================== .. image:: https://raw.githubusercontent.com/aio-libs/aiohttp/master/docs/_static/aiohttp-icon-128x128.png - :height: 64px - :width: 64px - :alt: aiohttp logo + :height: 64px + :width: 64px + :alt: aiohttp logo .. image:: https://travis-ci.org/aio-libs/aiohttp.svg?branch=master - :target: https://travis-ci.org/aio-libs/aiohttp - :align: right + :target: https://travis-ci.org/aio-libs/aiohttp + :align: right + :alt: Travis status for master branch .. image:: https://codecov.io/gh/aio-libs/aiohttp/branch/master/graph/badge.svg - :target: https://codecov.io/gh/aio-libs/aiohttp + :target: https://codecov.io/gh/aio-libs/aiohttp + :alt: codecov.io status for master branch .. image:: https://badge.fury.io/py/aiohttp.svg - :target: https://badge.fury.io/py/aiohttp + :target: https://badge.fury.io/py/aiohttp + :alt: Latest PyPI package version .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/Lobby From eecaffca688fb541a67896f57c25a8690b3e4e91 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 9 Nov 2017 18:31:36 +0200 Subject: [PATCH 8/9] Fix tests --- aiohttp/test_utils.py | 1 + tests/test_client_functional_oldstyle.py | 6 +++--- tests/test_web_response.py | 10 ++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/aiohttp/test_utils.py b/aiohttp/test_utils.py index 37fcf29e9e3..bcdac128bff 100644 --- a/aiohttp/test_utils.py +++ b/aiohttp/test_utils.py @@ -525,6 +525,7 @@ def make_mocked_request(method, path, headers=None, *, if payload_writer is sentinel: payload_writer = mock.Mock() + payload_writer.write = make_mocked_coro(None) payload_writer.write_eof = make_mocked_coro(None) payload_writer.drain = make_mocked_coro(None) diff --git a/tests/test_client_functional_oldstyle.py b/tests/test_client_functional_oldstyle.py index 1a715b2c6ed..5c7314c2914 100644 --- a/tests/test_client_functional_oldstyle.py +++ b/tests/test_client_functional_oldstyle.py @@ -63,7 +63,7 @@ async def handler(request): for hdr, val in request.message.headers.items(): if (hdr.upper() == 'EXPECT') and (val == '100-continue'): - request.writer.write(b'HTTP/1.0 100 Continue\r\n\r\n') + await request.writer.write(b'HTTP/1.0 100 Continue\r\n\r\n') break rob = router(properties, request) @@ -274,7 +274,7 @@ async def _response(self, response, body=None, except Exception: return else: - response.write(body.encode('utf8')) + await response.write(body.encode('utf8')) return response @@ -462,7 +462,7 @@ def test_POST_STREAM_DATA(self): @aiohttp.streamer async def stream(writer): await fut - writer.write(data) + await writer.write(data) self.loop.call_later(0.01, fut.set_result, True) diff --git a/tests/test_web_response.py b/tests/test_web_response.py index 623b624091c..3010f903a5b 100644 --- a/tests/test_web_response.py +++ b/tests/test_web_response.py @@ -526,18 +526,16 @@ async def test_write_before_start(): async def test_cannot_write_after_eof(): resp = StreamResponse() - writer = mock.Mock() - resp_impl = await resp.prepare( - make_request('GET', '/', writer=writer)) - resp_impl.write_eof = make_mocked_coro(None) + req = make_request('GET', '/') + await resp.prepare(req) await resp.write(b'data') await resp.write_eof() - writer.write.reset_mock() + req.writer.write.reset_mock() with pytest.raises(RuntimeError): await resp.write(b'next data') - assert not writer.write.called + assert not req.writer.write.called async def test___repr___after_eof(): From 0fea4b9f951192dc8560812a17b636e11589777f Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 9 Nov 2017 18:46:59 +0200 Subject: [PATCH 9/9] Update CHANGES --- CHANGES/2483.removal | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES/2483.removal b/CHANGES/2483.removal index d715f282945..0b92cfb0fd7 100644 --- a/CHANGES/2483.removal +++ b/CHANGES/2483.removal @@ -1,2 +1,3 @@ `StreamResponse.drain()` is not a part of public API anymore, just use -`await resp.write()`. +`await StreamResponse.write()`. `StreamResponse.write` is converted to +async function.