diff --git a/CHANGES/2483.removal b/CHANGES/2483.removal new file mode 100644 index 00000000000..0b92cfb0fd7 --- /dev/null +++ b/CHANGES/2483.removal @@ -0,0 +1,3 @@ +`StreamResponse.drain()` is not a part of public API anymore, just use +`await StreamResponse.write()`. `StreamResponse.write` is converted to +async function. 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 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: diff --git a/aiohttp/test_utils.py b/aiohttp/test_utils.py index 47bf226f8c2..8a2caad4cc9 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 @@ -526,8 +526,9 @@ 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 = make_mocked_coro(None) + 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 5dfd5203b9a..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,12 +402,15 @@ 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" 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''): 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. 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_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_functional.py b/tests/test_web_functional.py index dffae48f509..e638ed4aa66 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 0db4ea96c1a..f303904d677 100644 --- a/tests/test_web_response.py +++ b/tests/test_web_response.py @@ -517,30 +517,28 @@ 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(): 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) - resp.write(b'data') + await resp.write(b'data') await resp.write_eof() - writer.write.reset_mock() + req.writer.write.reset_mock() with pytest.raises(RuntimeError): - resp.write(b'next data') - assert not writer.write.called + await resp.write(b'next data') + assert not req.writer.write.called async def test___repr___after_eof(): @@ -549,7 +547,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) @@ -570,7 +568,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() @@ -580,22 +578,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()