From b565072ed9792debb017ba3dc2b3de67f8f03514 Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Sat, 11 Jan 2020 15:50:16 +1000 Subject: [PATCH] Allow route decorators to stack up again (#1764) * Allow route decorators to stack up without causing a function signature inspection crash Fix #1742 * Apply fix to @websocket routes docorator too Add test for double-stacked websocket decorator remove introduction of new variable in route wrapper, extend routes in-place. Add explanation of why a handler will be a tuple in the case of a double-stacked route decorator --- sanic/app.py | 47 ++++++++++++++++++++++++++++++-------------- tests/test_routes.py | 29 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index 65e84809..abdd36fb 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -194,6 +194,12 @@ class Sanic: strict_slashes = self.strict_slashes def response(handler): + if isinstance(handler, tuple): + # if a handler fn is already wrapped in a route, the handler + # variable will be a tuple of (existing routes, handler fn) + routes, handler = handler + else: + routes = [] args = list(signature(handler).parameters.keys()) if not args: @@ -205,14 +211,16 @@ class Sanic: if stream: handler.is_stream = stream - routes = self.router.add( - uri=uri, - methods=methods, - handler=handler, - host=host, - strict_slashes=strict_slashes, - version=version, - name=name, + routes.extend( + self.router.add( + uri=uri, + methods=methods, + handler=handler, + host=host, + strict_slashes=strict_slashes, + version=version, + name=name, + ) ) return routes, handler @@ -476,6 +484,13 @@ class Sanic: strict_slashes = self.strict_slashes def response(handler): + if isinstance(handler, tuple): + # if a handler fn is already wrapped in a route, the handler + # variable will be a tuple of (existing routes, handler fn) + routes, handler = handler + else: + routes = [] + async def websocket_handler(request, *args, **kwargs): request.app = self if not getattr(handler, "__blueprintname__", False): @@ -516,13 +531,15 @@ class Sanic: self.websocket_tasks.remove(fut) await ws.close() - routes = self.router.add( - uri=uri, - handler=websocket_handler, - methods=frozenset({"GET"}), - host=host, - strict_slashes=strict_slashes, - name=name, + routes.extend( + self.router.add( + uri=uri, + handler=websocket_handler, + methods=frozenset({"GET"}), + host=host, + strict_slashes=strict_slashes, + name=name, + ) ) return routes, handler diff --git a/tests/test_routes.py b/tests/test_routes.py index 3b24389f..31fa1a56 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -551,6 +551,35 @@ def test_route_duplicate(app): pass +def test_double_stack_route(app): + @app.route("/test/1") + @app.route("/test/2") + async def handler1(request): + return text("OK") + + request, response = app.test_client.get("/test/1") + assert response.status == 200 + request, response = app.test_client.get("/test/2") + assert response.status == 200 + + +@pytest.mark.asyncio +async def test_websocket_route_asgi(app): + ev = asyncio.Event() + + @app.websocket("/test/1") + @app.websocket("/test/2") + async def handler(request, ws): + ev.set() + + request, response = await app.asgi_client.websocket("/test/1") + first_set = ev.is_set() + ev.clear() + request, response = await app.asgi_client.websocket("/test/1") + second_set = ev.is_set() + assert(first_set and second_set) + + def test_method_not_allowed(app): @app.route("/test", methods=["GET"]) async def handler(request):