From 4d4f38fb35f5a1e736b2a16c661559835aa5732e Mon Sep 17 00:00:00 2001 From: 38elements Date: Mon, 8 May 2017 23:04:45 +0900 Subject: [PATCH] is_request_stream for CompositionView and HTTPMethodView --- sanic/app.py | 15 ++- sanic/router.py | 2 +- sanic/views.py | 3 +- tests/test_blueprints.py | 2 + tests/test_request_stream.py | 252 +++++++++++++++++++++++++++-------- tests/test_routes.py | 3 + tests/test_views.py | 11 +- 7 files changed, 228 insertions(+), 60 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index 4ae186ed..0216be34 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -131,7 +131,8 @@ class Sanic: self.is_request_stream = True def response(handler): - handler.is_stream = stream + if stream: + handler.is_stream = stream self.router.add(uri=uri, methods=methods, handler=handler, host=host, strict_slashes=strict_slashes) return handler @@ -187,20 +188,28 @@ class Sanic: :param host: :return: function or class instance """ + stream = False # Handle HTTPMethodView differently if hasattr(handler, 'view_class'): methods = set() for method in HTTP_METHODS: - if getattr(handler.view_class, method.lower(), None): + _handler = getattr(handler.view_class, method.lower(), None) + if _handler: methods.add(method) + if hasattr(_handler, 'is_stream'): + stream = True # handle composition view differently if isinstance(handler, CompositionView): methods = handler.handlers.keys() + for _handler in handler.handlers.values(): + if hasattr(_handler, 'is_stream'): + stream = True + break self.route(uri=uri, methods=methods, host=host, - strict_slashes=strict_slashes)(handler) + strict_slashes=strict_slashes, stream=stream)(handler) return handler # Decorator diff --git a/sanic/router.py b/sanic/router.py index 0677ecb5..2581e178 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -355,4 +355,4 @@ class Router: if (hasattr(handler, 'view_class') and hasattr(handler.view_class, request.method.lower())): handler = getattr(handler.view_class, request.method.lower()) - return hasattr(handler, 'is_stream') and handler.is_stream + return hasattr(handler, 'is_stream') diff --git a/sanic/views.py b/sanic/views.py index 65a76859..f47f2044 100644 --- a/sanic/views.py +++ b/sanic/views.py @@ -89,7 +89,8 @@ class CompositionView: self.handlers = {} def add(self, methods, handler, stream=False): - handler.is_stream = stream + if stream: + handler.is_stream = stream for method in methods: if method not in HTTP_METHODS: raise InvalidUsage( diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 46726836..ad638dd5 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -17,10 +17,12 @@ def test_bp(): @bp.route('/') def handler(request): + assert request.stream is None return text('Hello') app.blueprint(bp) request, response = app.test_client.get('/') + assert app.is_request_stream is False assert response.text == 'Hello' diff --git a/tests/test_request_stream.py b/tests/test_request_stream.py index 5ae5df24..3098963f 100644 --- a/tests/test_request_stream.py +++ b/tests/test_request_stream.py @@ -1,3 +1,4 @@ +import asyncio from sanic import Sanic from sanic.blueprints import Blueprint from sanic.views import CompositionView @@ -5,17 +6,86 @@ from sanic.views import HTTPMethodView from sanic.views import stream as stream_decorator from sanic.response import stream, text -bp = Blueprint('test_blueprint_request_stream') -app = Sanic('test_request_stream') +data = "abc" * 100000 -class SimpleView(HTTPMethodView): +def test_request_stream_method_view(): + '''for self.is_request_stream = True''' - def get(self, request): + app = Sanic('test_request_stream_method_view') + + class SimpleView(HTTPMethodView): + + def get(self, request): + assert request.stream is None + return text('OK') + + @stream_decorator + async def post(self, 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) + + app.add_route(SimpleView.as_view(), '/method_view') + + assert app.is_request_stream is True + + request, response = app.test_client.get('/method_view') + assert response.status == 200 + assert response.text == 'OK' + + request, response = app.test_client.post('/method_view', data=data) + assert response.status == 200 + assert response.text == data + + +def test_request_stream_app(): + '''for self.is_request_stream = True''' + + app = Sanic('test_request_stream_app') + + @app.stream('/stream') + async def handler(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.get('/get') + async def get(request): + assert request.stream is None return text('OK') - @stream_decorator - async def post(self, request): + assert app.is_request_stream is True + + request, response = app.test_client.get('/get') + assert response.status == 200 + assert response.text == 'OK' + + request, response = app.test_client.post('/stream', data=data) + assert response.status == 200 + assert response.text == data + + +def test_request_stream_blueprint(): + '''for self.is_request_stream = True''' + + 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() @@ -24,66 +94,140 @@ class SimpleView(HTTPMethodView): result += body.decode('utf-8') return text(result) + @bp.get('/bp_get') + async def bp_get(request): + assert request.stream is None + return text('OK') -@app.stream('/stream') -async def handler(request): - async def streaming(response): + app.blueprint(bp) + + assert app.is_request_stream is True + + request, response = app.test_client.get('/bp_get') + assert response.status == 200 + assert response.text == 'OK' + + request, response = app.test_client.post('/bp_stream', data=data) + assert response.status == 200 + assert response.text == data + + +def test_request_stream_composition_view(): + '''for self.is_request_stream = True''' + + app = Sanic('test_request_stream__composition_view') + + def get_handler(request): + assert request.stream is None + return text('OK') + + async def post_handler(request): + assert isinstance(request.stream, asyncio.Queue) + result = '' while True: body = await request.stream.get() if body is None: break - response.write(body.decode('utf-8')) - return stream(streaming) + result += body.decode('utf-8') + return text(result) + view = CompositionView() + view.add(['GET'], get_handler) + view.add(['POST'], post_handler, stream=True) + app.add_route(view, '/composition_view') -@app.get('/get') -async def get(request): - return text('OK') + assert app.is_request_stream is True + request, response = app.test_client.get('/composition_view') + assert response.status == 200 + assert response.text == 'OK' -@bp.stream('/bp_stream') -async def bp_stream(request): - 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): - return text('OK') - - -def get_handler(request): - return text('OK') - - -async def post_handler(request): - result = '' - while True: - body = await request.stream.get() - if body is None: - break - result += body.decode('utf-8') - return text(result) - - -app.add_route(SimpleView.as_view(), '/method_view') - -view = CompositionView() -view.add(['GET'], get_handler) -view.add(['POST'], post_handler, stream=True) - -app.blueprint(bp) - -app.add_route(view, '/composition_view') + request, response = app.test_client.post('/composition_view', data=data) + assert response.status == 200 + assert response.text == data def test_request_stream(): - data = "abc" * 100000 + '''test for complex application''' + + bp = Blueprint('test_blueprint_request_stream') + app = Sanic('test_request_stream') + + class SimpleView(HTTPMethodView): + + def get(self, request): + assert request.stream is None + return text('OK') + + @stream_decorator + async def post(self, 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) + + @app.stream('/stream') + async def handler(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.get('/get') + async def get(request): + assert request.stream is None + return text('OK') + + @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): + assert request.stream is None + return text('OK') + + def get_handler(request): + assert request.stream is None + return text('OK') + + async def post_handler(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) + + app.add_route(SimpleView.as_view(), '/method_view') + + view = CompositionView() + view.add(['GET'], get_handler) + view.add(['POST'], post_handler, stream=True) + + app.blueprint(bp) + + app.add_route(view, '/composition_view') + + assert app.is_request_stream is True request, response = app.test_client.get('/method_view') assert response.status == 200 diff --git a/tests/test_routes.py b/tests/test_routes.py index b3e19355..2219b07a 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -28,12 +28,15 @@ def test_route_strict_slash(): @app.get('/get', strict_slashes=True) def handler(request): + assert request.stream is None return text('OK') @app.post('/post/', strict_slashes=True) def handler(request): return text('OK') + assert app.is_request_stream is False + request, response = app.test_client.get('/get') assert response.text == 'OK' diff --git a/tests/test_views.py b/tests/test_views.py index 40fc1adf..71d32a7f 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -16,6 +16,7 @@ def test_methods(method): class DummyView(HTTPMethodView): async def get(self, request): + assert request.stream is None return text('', headers={'method': 'GET'}) def post(self, request): @@ -37,6 +38,7 @@ def test_methods(method): return text('', headers={'method': 'DELETE'}) app.add_route(DummyView.as_view(), '/') + assert app.is_request_stream is False request, response = getattr(app.test_client, method.lower())('/') assert response.headers['method'] == method @@ -79,6 +81,7 @@ def test_with_bp(): class DummyView(HTTPMethodView): def get(self, request): + assert request.stream is None return text('I am get method') bp.add_route(DummyView.as_view(), '/') @@ -86,6 +89,7 @@ def test_with_bp(): app.blueprint(bp) request, response = app.test_client.get('/') + assert app.is_request_stream is False assert response.text == 'I am get method' @@ -227,10 +231,15 @@ def test_composition_view_runs_methods_as_expected(method): app = Sanic('test_composition_view') view = CompositionView() - view.add(['GET', 'POST', 'PUT'], lambda x: text('first method')) + + def first(request): + assert request.stream is None + return text('first method') + view.add(['GET', 'POST', 'PUT'], first) view.add(['DELETE', 'PATCH'], lambda x: text('second method')) app.add_route(view, '/') + assert app.is_request_stream is False if method in ['GET', 'POST', 'PUT']: request, response = getattr(app.test_client, method.lower())('/')