Streaming migration for 20.3 release (#1800)

* Compatibility and deprecations for Sanic 20.3 in preparation of the streaming branch.

* Add test for new API.

* isort tests

* More coverage

* json takes str, not bytes

Co-authored-by: L. Kärkkäinen <tronic@users.noreply.github.com>
This commit is contained in:
L. Kärkkäinen 2020-03-24 19:11:09 +02:00 committed by GitHub
parent 60b4efad67
commit 4db075ffc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 2 deletions

View File

@ -56,6 +56,14 @@ class StreamBuffer:
self._queue.task_done()
return payload
async def __aiter__(self):
"""Support `async for data in request.stream`"""
while True:
data = await self.read()
if not data:
break
yield data
async def put(self, payload):
await self._queue.put(payload)
@ -128,14 +136,33 @@ class Request:
)
def body_init(self):
""".. deprecated:: 20.3"""
self.body = []
def body_push(self, data):
""".. deprecated:: 20.3"""
self.body.append(data)
def body_finish(self):
""".. deprecated:: 20.3"""
self.body = b"".join(self.body)
async def receive_body(self):
"""Receive request.body, if not already received.
Streaming handlers may call this to receive the full body.
This is added as a compatibility shim in Sanic 20.3 because future
versions of Sanic will make all requests streaming and will use this
function instead of the non-async body_init/push/finish functions.
Please make an issue if your code depends on the old functionality and
cannot be upgraded to the new API.
"""
if not self.stream:
return
self.body = b"".join([data async for data in self.stream])
@property
def json(self):
if self.parsed_json is None:

View File

@ -3,7 +3,7 @@ import pytest
from sanic.blueprints import Blueprint
from sanic.exceptions import HeaderExpectationFailed
from sanic.request import StreamBuffer
from sanic.response import stream, text
from sanic.response import json, stream, text
from sanic.views import CompositionView, HTTPMethodView
from sanic.views import stream as stream_decorator
@ -613,3 +613,43 @@ def test_request_stream(app):
request, response = app.test_client.post("/bp_stream", data=data)
assert response.status == 200
assert response.text == data
def test_streaming_new_api(app):
@app.post("/non-stream")
async def handler(request):
assert request.body == b"x"
await request.receive_body() # This should do nothing
assert request.body == b"x"
return text("OK")
@app.post("/1", stream=True)
async def handler(request):
assert request.stream
assert not request.body
await request.receive_body()
return text(request.body.decode().upper())
@app.post("/2", stream=True)
async def handler(request):
ret = []
async for data in request.stream:
# We should have no b"" or None, just proper chunks
assert data
assert isinstance(data, bytes)
ret.append(data.decode("ASCII"))
return json(ret)
request, response = app.test_client.post("/non-stream", data="x")
assert response.status == 200
request, response = app.test_client.post("/1", data="TEST data")
assert request.body == b"TEST data"
assert response.status == 200
assert response.text == "TEST DATA"
request, response = app.test_client.post("/2", data=data)
assert response.status == 200
res = response.json
assert isinstance(res, list)
assert len(res) > 1
assert "".join(res) == data

View File

@ -15,6 +15,7 @@ from aiofiles import os as async_os
from sanic.response import (
HTTPResponse,
StreamingHTTPResponse,
empty,
file,
file_stream,
json,
@ -22,7 +23,6 @@ from sanic.response import (
stream,
text,
)
from sanic.response import empty
from sanic.server import HttpProtocol
from sanic.testing import HOST, PORT