Skip to content

Commit

Permalink
Cherry pick PRs to backport to 19.12LTS
Browse files Browse the repository at this point in the history
  • Loading branch information
ashleysommer committed May 14, 2020
1 parent bb9ff7c commit abca1fc
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 21 deletions.
57 changes: 41 additions & 16 deletions sanic/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@ def route(
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:
Expand All @@ -205,14 +211,16 @@ def response(handler):
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

Expand Down Expand Up @@ -476,6 +484,13 @@ def websocket(
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):
Expand Down Expand Up @@ -516,13 +531,15 @@ async def websocket_handler(request, *args, **kwargs):
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

Expand Down Expand Up @@ -813,6 +830,14 @@ def url_for(self, view_name: str, **kwargs):
"Endpoint with name `{}` was not found".format(view_name)
)

# If the route has host defined, split that off
# TODO: Retain netloc and path separately in Route objects
host = uri.find("/")
if host > 0:
host, uri = uri[:host], uri[host:]
else:
host = None

if view_name == "static" or view_name.endswith(".static"):
filename = kwargs.pop("filename", None)
# it's static folder
Expand Down Expand Up @@ -845,7 +870,7 @@ def url_for(self, view_name: str, **kwargs):

netloc = kwargs.pop("_server", None)
if netloc is None and external:
netloc = self.config.get("SERVER_NAME", "")
netloc = host or self.config.get("SERVER_NAME", "")

if external:
if not scheme:
Expand Down
6 changes: 2 additions & 4 deletions sanic/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,16 +202,14 @@ def cookies(self):
return self._cookies


def empty(
status=204, headers=None,
):
def empty(status=204, headers=None):
"""
Returns an empty response to the client.
:param status Response code.
:param headers Custom Headers.
"""
return HTTPResponse(body_bytes=b"", status=status, headers=headers,)
return HTTPResponse(body_bytes=b"", status=status, headers=headers)


def json(
Expand Down
20 changes: 20 additions & 0 deletions sanic/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,26 @@ def close(self):
task = asyncio.ensure_future(coro, loop=self.loop)
return task

def start_serving(self):
if self.server:
try:
return self.server.start_serving()
except AttributeError:
raise NotImplementedError(
"server.start_serving not available in this version "
"of asyncio or uvloop."
)

def serve_forever(self):
if self.server:
try:
return self.server.serve_forever()
except AttributeError:
raise NotImplementedError(
"server.serve_forever not available in this version "
"of asyncio or uvloop."
)

def __await__(self):
"""Starts the asyncio server, returns AsyncServerCoro"""
task = asyncio.ensure_future(self.serve_coro)
Expand Down
18 changes: 17 additions & 1 deletion tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_create_asyncio_server(app):
@pytest.mark.skipif(
sys.version_info < (3, 7), reason="requires python3.7 or higher"
)
def test_asyncio_server_start_serving(app):
def test_asyncio_server_no_start_serving(app):
if not uvloop_installed():
loop = asyncio.get_event_loop()
asyncio_srv_coro = app.create_server(
Expand All @@ -54,6 +54,22 @@ def test_asyncio_server_start_serving(app):
srv = loop.run_until_complete(asyncio_srv_coro)
assert srv.is_serving() is False

@pytest.mark.skipif(
sys.version_info < (3, 7), reason="requires python3.7 or higher"
)
def test_asyncio_server_start_serving(app):
if not uvloop_installed():
loop = asyncio.get_event_loop()
asyncio_srv_coro = app.create_server(
return_asyncio_server=True,
asyncio_server_kwargs=dict(start_serving=False),
)
srv = loop.run_until_complete(asyncio_srv_coro)
assert srv.is_serving() is False
loop.run_until_complete(srv.start_serving())
assert srv.is_serving() is True
srv.close()
# Looks like we can't easily test `serve_forever()`

def test_app_loop_not_running(app):
with pytest.raises(SanicException) as excinfo:
Expand Down
29 changes: 29 additions & 0 deletions tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,35 @@ async def handler4(request, dynamic):
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):
Expand Down

0 comments on commit abca1fc

Please sign in to comment.