diff --git a/docs/sanic/streaming.md b/docs/sanic/streaming.md index a2ff8955..a314592b 100644 --- a/docs/sanic/streaming.md +++ b/docs/sanic/streaming.md @@ -33,7 +33,7 @@ async def index(request): ## Request Streaming -Sanic allows you to get request data by stream, as below. When the request ends, `request.stream.get()` returns `None`. +Sanic allows you to get request data by stream, as below. When the request ends, `request.stream.get()` returns `None`. Only post, put and patch decorator have stream argument. ```python from sanic import Sanic @@ -60,7 +60,7 @@ class SimpleView(HTTPMethodView): return text(result) -@app.stream('/stream') +@app.post('/stream', stream=True) async def handler(request): async def streaming(response): while True: @@ -72,7 +72,7 @@ async def handler(request): return stream(streaming) -@bp.stream('/bp_stream') +@bp.put('/bp_stream', stream=True) async def bp_handler(request): result = '' while True: diff --git a/examples/request_stream/client.py b/examples/request_stream/client.py index 26f76800..30fcff06 100644 --- a/examples/request_stream/client.py +++ b/examples/request_stream/client.py @@ -6,5 +6,5 @@ data = "" for i in range(1, 250000): data += str(i) -r = requests.post('http://127.0.0.1:8000/method_view', data=data) +r = requests.post('http://127.0.0.1:8000/stream', data=data) print(r.text) diff --git a/examples/request_stream/server.py b/examples/request_stream/server.py index a825de6b..37acfd5d 100644 --- a/examples/request_stream/server.py +++ b/examples/request_stream/server.py @@ -22,7 +22,7 @@ class SimpleView(HTTPMethodView): return text(result) -@app.stream('/stream') +@app.post('/stream', stream=True) async def handler(request): async def streaming(response): while True: @@ -34,7 +34,7 @@ async def handler(request): return stream(streaming) -@bp.stream('/bp_stream') +@bp.put('/bp_stream', stream=True) async def bp_handler(request): result = '' while True: diff --git a/sanic/app.py b/sanic/app.py index 0216be34..090daa24 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -144,13 +144,13 @@ class Sanic: return self.route(uri, methods=frozenset({"GET"}), host=host, strict_slashes=strict_slashes) - def post(self, uri, host=None, strict_slashes=False): + def post(self, uri, host=None, strict_slashes=False, stream=False): return self.route(uri, methods=frozenset({"POST"}), host=host, - strict_slashes=strict_slashes) + strict_slashes=strict_slashes, stream=stream) - def put(self, uri, host=None, strict_slashes=False): + def put(self, uri, host=None, strict_slashes=False, stream=False): return self.route(uri, methods=frozenset({"PUT"}), host=host, - strict_slashes=strict_slashes) + strict_slashes=strict_slashes, stream=stream) def head(self, uri, host=None, strict_slashes=False): return self.route(uri, methods=frozenset({"HEAD"}), host=host, @@ -160,21 +160,14 @@ class Sanic: return self.route(uri, methods=frozenset({"OPTIONS"}), host=host, strict_slashes=strict_slashes) - def patch(self, uri, host=None, strict_slashes=False): + def patch(self, uri, host=None, strict_slashes=False, stream=False): return self.route(uri, methods=frozenset({"PATCH"}), host=host, - strict_slashes=strict_slashes) + strict_slashes=strict_slashes, stream=stream) def delete(self, uri, host=None, strict_slashes=False): return self.route(uri, methods=frozenset({"DELETE"}), host=host, strict_slashes=strict_slashes) - def stream( - self, uri, methods=frozenset({"POST"}), host=None, - strict_slashes=False): - return self.route(uri, methods=methods, host=host, - strict_slashes=strict_slashes, - stream=True) - def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None, strict_slashes=False): """A helper method to register class instance or diff --git a/sanic/blueprints.py b/sanic/blueprints.py index fe1bbf90..315cdbda 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -196,13 +196,13 @@ class Blueprint: return self.route(uri, methods=["GET"], host=host, strict_slashes=strict_slashes) - def post(self, uri, host=None, strict_slashes=False): + def post(self, uri, host=None, strict_slashes=False, stream=False): return self.route(uri, methods=["POST"], host=host, - strict_slashes=strict_slashes) + strict_slashes=strict_slashes, stream=stream) - def put(self, uri, host=None, strict_slashes=False): + def put(self, uri, host=None, strict_slashes=False, stream=False): return self.route(uri, methods=["PUT"], host=host, - strict_slashes=strict_slashes) + strict_slashes=strict_slashes, stream=stream) def head(self, uri, host=None, strict_slashes=False): return self.route(uri, methods=["HEAD"], host=host, @@ -212,14 +212,10 @@ class Blueprint: return self.route(uri, methods=["OPTIONS"], host=host, strict_slashes=strict_slashes) - def patch(self, uri, host=None, strict_slashes=False): + def patch(self, uri, host=None, strict_slashes=False, stream=False): return self.route(uri, methods=["PATCH"], host=host, - strict_slashes=strict_slashes) + strict_slashes=strict_slashes, stream=stream) def delete(self, uri, host=None, strict_slashes=False): return self.route(uri, methods=["DELETE"], host=host, strict_slashes=strict_slashes) - - def stream(self, uri, methods=["POST"], host=None, strict_slashes=False): - return self.route(uri, methods=methods, host=host, - strict_slashes=strict_slashes, stream=True) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index ad638dd5..d9f5cd61 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -17,7 +17,6 @@ def test_bp(): @bp.route('/') def handler(request): - assert request.stream is None return text('Hello') app.blueprint(bp) @@ -270,38 +269,48 @@ def test_bp_shorthand(): @blueprint.get('/get') def handler(request): + assert request.stream is None return text('OK') @blueprint.put('/put') def handler(request): + assert request.stream is None return text('OK') @blueprint.post('/post') def handler(request): + assert request.stream is None return text('OK') @blueprint.head('/head') def handler(request): + assert request.stream is None return text('OK') @blueprint.options('/options') def handler(request): + assert request.stream is None return text('OK') @blueprint.patch('/patch') def handler(request): + assert request.stream is None return text('OK') @blueprint.delete('/delete') def handler(request): + assert request.stream is None return text('OK') @blueprint.websocket('/ws') async def handler(request, ws): + assert request.stream is None ev.set() app.blueprint(blueprint) + assert app.is_request_stream is False + request, response = app.test_client.get('/get') assert response.text == 'OK' diff --git a/tests/test_request_stream.py b/tests/test_request_stream.py index 3098963f..0ceccf60 100644 --- a/tests/test_request_stream.py +++ b/tests/test_request_stream.py @@ -45,12 +45,37 @@ def test_request_stream_method_view(): def test_request_stream_app(): - '''for self.is_request_stream = True''' + '''for self.is_request_stream = True and decorators''' app = Sanic('test_request_stream_app') - @app.stream('/stream') - async def handler(request): + @app.get('/get') + async def get(request): + assert request.stream is None + return text('GET') + + @app.head('/head') + async def head(request): + assert request.stream is None + return text('HEAD') + + @app.delete('/delete') + async def delete(request): + assert request.stream is None + return text('DELETE') + + @app.options('/options') + async def options(request): + assert request.stream is None + return text('OPTIONS') + + @app.post('/_post/') + async def _post(request, id): + assert request.stream is None + return text('_POST') + + @app.post('/post/', stream=True) + async def post(request, id): assert isinstance(request.stream, asyncio.Queue) async def streaming(response): @@ -61,18 +86,79 @@ def test_request_stream_app(): response.write(body.decode('utf-8')) return stream(streaming) - @app.get('/get') - async def get(request): + @app.put('/_put') + async def _put(request): assert request.stream is None - return text('OK') + return text('_PUT') + + @app.put('/put', stream=True) + async def put(request): + assert isinstance(request.stream, asyncio.Queue) + + async def streaming(response): + while True: + body = await request.stream.get() + if body is None: + break + response.write(body.decode('utf-8')) + return stream(streaming) + + @app.patch('/_patch') + async def _patch(request): + assert request.stream is None + return text('_PATCH') + + @app.patch('/patch', stream=True) + async def patch(request): + assert isinstance(request.stream, asyncio.Queue) + + async def streaming(response): + while True: + body = await request.stream.get() + if body is None: + break + response.write(body.decode('utf-8')) + return stream(streaming) assert app.is_request_stream is True request, response = app.test_client.get('/get') assert response.status == 200 - assert response.text == 'OK' + assert response.text == 'GET' - request, response = app.test_client.post('/stream', data=data) + request, response = app.test_client.head('/head') + assert response.status == 200 + assert response.text == '' + + request, response = app.test_client.delete('/delete') + assert response.status == 200 + assert response.text == 'DELETE' + + request, response = app.test_client.options('/options') + assert response.status == 200 + assert response.text == 'OPTIONS' + + request, response = app.test_client.post('/_post/1', data=data) + assert response.status == 200 + assert response.text == '_POST' + + request, response = app.test_client.post('/post/1', data=data) + assert response.status == 200 + assert response.text == data + + request, response = app.test_client.put('/_put', data=data) + assert response.status == 200 + assert response.text == '_PUT' + + request, response = app.test_client.put('/put', data=data) + assert response.status == 200 + assert response.text == data + + request, response = app.test_client.patch('/_patch', data=data) + assert response.status == 200 + assert response.text == '_PATCH' + + request, response = app.test_client.patch('/patch', data=data) assert response.status == 200 assert response.text == data @@ -83,31 +169,118 @@ def test_request_stream_blueprint(): app = Sanic('test_request_stream_blueprint') bp = Blueprint('test_blueprint_request_stream_blueprint') - @bp.stream('/bp_stream') - async def bp_stream(request): - assert isinstance(request.stream, asyncio.Queue) - result = '' - while True: - body = await request.stream.get() - if body is None: - break - result += body.decode('utf-8') - return text(result) - - @bp.get('/bp_get') - async def bp_get(request): + @app.get('/get') + async def get(request): assert request.stream is None - return text('OK') + return text('GET') + + @bp.head('/head') + async def head(request): + assert request.stream is None + return text('HEAD') + + @bp.delete('/delete') + async def delete(request): + assert request.stream is None + return text('DELETE') + + @bp.options('/options') + async def options(request): + assert request.stream is None + return text('OPTIONS') + + @bp.post('/_post/') + async def _post(request, id): + assert request.stream is None + return text('_POST') + + @bp.post('/post/', stream=True) + async def post(request, id): + assert isinstance(request.stream, asyncio.Queue) + + async def streaming(response): + while True: + body = await request.stream.get() + if body is None: + break + response.write(body.decode('utf-8')) + return stream(streaming) + + @bp.put('/_put') + async def _put(request): + assert request.stream is None + return text('_PUT') + + @bp.put('/put', stream=True) + async def put(request): + assert isinstance(request.stream, asyncio.Queue) + + async def streaming(response): + while True: + body = await request.stream.get() + if body is None: + break + response.write(body.decode('utf-8')) + return stream(streaming) + + @bp.patch('/_patch') + async def _patch(request): + assert request.stream is None + return text('_PATCH') + + @bp.patch('/patch', stream=True) + async def patch(request): + assert isinstance(request.stream, asyncio.Queue) + + async def streaming(response): + while True: + body = await request.stream.get() + if body is None: + break + response.write(body.decode('utf-8')) + return stream(streaming) app.blueprint(bp) assert app.is_request_stream is True - request, response = app.test_client.get('/bp_get') + request, response = app.test_client.get('/get') assert response.status == 200 - assert response.text == 'OK' + assert response.text == 'GET' - request, response = app.test_client.post('/bp_stream', data=data) + request, response = app.test_client.head('/head') + assert response.status == 200 + assert response.text == '' + + request, response = app.test_client.delete('/delete') + assert response.status == 200 + assert response.text == 'DELETE' + + request, response = app.test_client.options('/options') + assert response.status == 200 + assert response.text == 'OPTIONS' + + request, response = app.test_client.post('/_post/1', data=data) + assert response.status == 200 + assert response.text == '_POST' + + request, response = app.test_client.post('/post/1', data=data) + assert response.status == 200 + assert response.text == data + + request, response = app.test_client.put('/_put', data=data) + assert response.status == 200 + assert response.text == '_PUT' + + request, response = app.test_client.put('/put', data=data) + assert response.status == 200 + assert response.text == data + + request, response = app.test_client.patch('/_patch', data=data) + assert response.status == 200 + assert response.text == '_PATCH' + + request, response = app.test_client.patch('/patch', data=data) assert response.status == 200 assert response.text == data @@ -170,7 +343,7 @@ def test_request_stream(): result += body.decode('utf-8') return text(result) - @app.stream('/stream') + @app.post('/stream', stream=True) async def handler(request): assert isinstance(request.stream, asyncio.Queue) @@ -187,7 +360,7 @@ def test_request_stream(): assert request.stream is None return text('OK') - @bp.stream('/bp_stream') + @bp.post('/bp_stream', stream=True) async def bp_stream(request): assert isinstance(request.stream, asyncio.Queue) result = '' diff --git a/tests/test_routes.py b/tests/test_routes.py index 2219b07a..4afb4a9c 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -33,6 +33,7 @@ def test_route_strict_slash(): @app.post('/post/', strict_slashes=True) def handler(request): + assert request.stream is None return text('OK') assert app.is_request_stream is False @@ -80,21 +81,43 @@ def test_shorthand_routes_put(): @app.put('/put') def handler(request): + assert request.stream is None return text('OK') + assert app.is_request_stream is False + request, response = app.test_client.put('/put') assert response.text == 'OK' request, response = app.test_client.get('/put') assert response.status == 405 +def test_shorthand_routes_delete(): + app = Sanic('test_shorhand_routes_delete') + + @app.delete('/delete') + def handler(request): + assert request.stream is None + return text('OK') + + assert app.is_request_stream is False + + request, response = app.test_client.delete('/delete') + assert response.text == 'OK' + + request, response = app.test_client.get('/delete') + assert response.status == 405 + def test_shorthand_routes_patch(): app = Sanic('test_shorhand_routes_patch') @app.patch('/patch') def handler(request): + assert request.stream is None return text('OK') + assert app.is_request_stream is False + request, response = app.test_client.patch('/patch') assert response.text == 'OK' @@ -106,8 +129,11 @@ def test_shorthand_routes_head(): @app.head('/head') def handler(request): + assert request.stream is None return text('OK') + assert app.is_request_stream is False + request, response = app.test_client.head('/head') assert response.status == 200 @@ -119,8 +145,11 @@ def test_shorthand_routes_options(): @app.options('/options') def handler(request): + assert request.stream is None return text('OK') + assert app.is_request_stream is False + request, response = app.test_client.options('/options') assert response.status == 200