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:
parent
60b4efad67
commit
4db075ffc1
|
@ -56,6 +56,14 @@ class StreamBuffer:
|
||||||
self._queue.task_done()
|
self._queue.task_done()
|
||||||
return payload
|
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):
|
async def put(self, payload):
|
||||||
await self._queue.put(payload)
|
await self._queue.put(payload)
|
||||||
|
|
||||||
|
@ -128,14 +136,33 @@ class Request:
|
||||||
)
|
)
|
||||||
|
|
||||||
def body_init(self):
|
def body_init(self):
|
||||||
|
""".. deprecated:: 20.3"""
|
||||||
self.body = []
|
self.body = []
|
||||||
|
|
||||||
def body_push(self, data):
|
def body_push(self, data):
|
||||||
|
""".. deprecated:: 20.3"""
|
||||||
self.body.append(data)
|
self.body.append(data)
|
||||||
|
|
||||||
def body_finish(self):
|
def body_finish(self):
|
||||||
|
""".. deprecated:: 20.3"""
|
||||||
self.body = b"".join(self.body)
|
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
|
@property
|
||||||
def json(self):
|
def json(self):
|
||||||
if self.parsed_json is None:
|
if self.parsed_json is None:
|
||||||
|
|
|
@ -3,7 +3,7 @@ import pytest
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
from sanic.exceptions import HeaderExpectationFailed
|
from sanic.exceptions import HeaderExpectationFailed
|
||||||
from sanic.request import StreamBuffer
|
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 CompositionView, HTTPMethodView
|
||||||
from sanic.views import stream as stream_decorator
|
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)
|
request, response = app.test_client.post("/bp_stream", data=data)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.text == data
|
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
|
||||||
|
|
|
@ -15,6 +15,7 @@ from aiofiles import os as async_os
|
||||||
from sanic.response import (
|
from sanic.response import (
|
||||||
HTTPResponse,
|
HTTPResponse,
|
||||||
StreamingHTTPResponse,
|
StreamingHTTPResponse,
|
||||||
|
empty,
|
||||||
file,
|
file,
|
||||||
file_stream,
|
file_stream,
|
||||||
json,
|
json,
|
||||||
|
@ -22,7 +23,6 @@ from sanic.response import (
|
||||||
stream,
|
stream,
|
||||||
text,
|
text,
|
||||||
)
|
)
|
||||||
from sanic.response import empty
|
|
||||||
from sanic.server import HttpProtocol
|
from sanic.server import HttpProtocol
|
||||||
from sanic.testing import HOST, PORT
|
from sanic.testing import HOST, PORT
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user