Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make Sanic.create_server return an asyncio.Server #1470

Merged
merged 10 commits into from
Jan 20, 2019
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Guides
sanic/changelog
sanic/contributing
sanic/api_reference
sanic/asyncio_python37


Module Documentation
Expand Down
58 changes: 58 additions & 0 deletions docs/sanic/asyncio_python37.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
Python 3.7 AsyncIO examples
###########################

With Python 3.7 AsyncIO got major update for the following types:

- asyncio.AbstractEventLoop
- asyncio.AnstractServer

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: AbstractServer



This example shows how to use sanic with Python 3.7, to be precise: how to retrieve an asyncio server instance:

.. code:: python

import asyncio
import socket
import os

from sanic import Sanic
from sanic.response import json

app = Sanic(__name__)


@app.route("/")
async def test(request):
return json({"hello": "world"})


server_socket = '/tmp/sanic.sock'

sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

try:
os.remote(server_socket)
finally:
sock.bind(server_socket)

if __name__ == "__main__":
loop = asyncio.get_event_loop()
srv_coro = app.create_server(
sock=sock,
return_asyncio_server=True,
asyncio_server_args=dict(
start_serving=False
)
)
srv = loop.run_until_complete(srv_coro)
try:
assert srv.is_serving() is False
loop.run_until_complete(srv.start_serving())
assert srv.is_serving() is True
loop.run_until_complete(srv.serve_forever())
except KeyboardInterrupt:
srv.close()
loop.close()


Please note that uvloop does not support these features yet.
15 changes: 13 additions & 2 deletions sanic/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,8 @@ async def create_server(
backlog: int = 100,
stop_event: Any = None,
access_log: Optional[bool] = None,
return_asyncio_server=False,
asyncio_server_kwargs=None,
) -> None:
"""
Asynchronous version of :func:`run`.
Expand Down Expand Up @@ -1154,6 +1156,13 @@ async def create_server(
:type stop_event: None
:param access_log: Enables writing access logs (slows server)
:type access_log: bool
:param return_asyncio_server: flag that defines whether there's a need
to return asyncio.Server or
start it serving right away
:type return_asyncio_server: bool
:param asyncio_server_kwargs: key-value arguments for
asyncio/uvloop create_server method
:type asyncio_server_kwargs: dict
:return: Nothing
"""

Expand Down Expand Up @@ -1184,7 +1193,7 @@ async def create_server(
loop=get_event_loop(),
protocol=protocol,
backlog=backlog,
run_async=True,
run_async=return_asyncio_server,
)

# Trigger before_start events
Expand All @@ -1193,7 +1202,9 @@ async def create_server(
server_settings.get("loop"),
)

return await serve(**server_settings)
return await serve(
asyncio_server_kwargs=asyncio_server_kwargs, **server_settings
)

async def trigger_events(self, events, loop):
"""Trigger events (functions or async)
Expand Down
8 changes: 7 additions & 1 deletion sanic/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ def serve(
websocket_write_limit=2 ** 16,
state=None,
graceful_shutdown_timeout=15.0,
asyncio_server_kwargs=None,
):
"""Start asynchronous HTTP Server on an individual process.

Expand Down Expand Up @@ -700,6 +701,8 @@ def serve(
:param router: Router object
:param graceful_shutdown_timeout: How long take to Force close non-idle
connection
:param asyncio_server_kwargs: key-value args for asyncio/uvloop
create_server method
:return: Nothing
"""
if not run_async:
Expand Down Expand Up @@ -734,7 +737,9 @@ def serve(
state=state,
debug=debug,
)

asyncio_server_kwargs = (
asyncio_server_kwargs if asyncio_server_kwargs else {}
)
server_coroutine = loop.create_server(
server,
host,
Expand All @@ -743,6 +748,7 @@ def serve(
reuse_port=reuse_port,
sock=sock,
backlog=backlog,
**asyncio_server_kwargs
)

# Instead of pulling time at the end of every request,
Expand Down
36 changes: 36 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import asyncio
import logging
import sys

from inspect import isawaitable
import pytest

from sanic.exceptions import SanicException
from sanic.response import text


def uvloop_installed():
try:
import uvloop
return True
except ImportError:
return False


def test_app_loop_running(app):
@app.get("/test")
async def handler(request):
Expand All @@ -17,6 +27,32 @@ async def handler(request):
assert response.text == "pass"


@pytest.mark.skipif(sys.version_info < (3, 7),
reason="requires python3.7 or higher")
def test_create_asyncio_server(app):
if not uvloop_installed():
loop = asyncio.get_event_loop()
asyncio_srv_coro = app.create_server(
return_asyncio_server=True)
assert isawaitable(asyncio_srv_coro)
srv = loop.run_until_complete(asyncio_srv_coro)
assert srv.is_serving() is True


@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


def test_app_loop_not_running(app):
with pytest.raises(SanicException) as excinfo:
app.loop
Expand Down
6 changes: 4 additions & 2 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,12 @@ async def test_config_access_log_passing_in_create_server(app):
async def _request(sanic, loop):
app.stop()

await app.create_server(port=1341, access_log=False)
await app.create_server(port=1341, access_log=False,
return_asyncio_server=True)
assert app.config.ACCESS_LOG == False

await app.create_server(port=1342, access_log=True)
await app.create_server(port=1342, access_log=True,
return_asyncio_server=True)
assert app.config.ACCESS_LOG == True


Expand Down
4 changes: 3 additions & 1 deletion tests/test_keep_alive_timeout.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ def _sanic_endpoint_test(
uri="/",
gather_request=True,
debug=False,
server_kwargs={},
server_kwargs={
"return_asyncio_server": True,
},
*request_args,
**request_kwargs
):
Expand Down
12 changes: 8 additions & 4 deletions tests/test_logo.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@


def test_logo_base(app, caplog):
server = app.create_server(debug=True)
server = app.create_server(
debug=True, return_asyncio_server=True)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop._stopping = False
Expand All @@ -31,7 +32,8 @@ def test_logo_base(app, caplog):
def test_logo_false(app, caplog):
app.config.LOGO = False

server = app.create_server(debug=True)
server = app.create_server(
debug=True, return_asyncio_server=True)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop._stopping = False
Expand All @@ -50,7 +52,8 @@ def test_logo_false(app, caplog):
def test_logo_true(app, caplog):
app.config.LOGO = True

server = app.create_server(debug=True)
server = app.create_server(
debug=True, return_asyncio_server=True)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop._stopping = False
Expand All @@ -69,7 +72,8 @@ def test_logo_true(app, caplog):
def test_logo_custom(app, caplog):
app.config.LOGO = "My Custom Logo"

server = app.create_server(debug=True)
server = app.create_server(
debug=True, return_asyncio_server=True)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop._stopping = False
Expand Down
3 changes: 2 additions & 1 deletion tests/test_server_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ class MySanicDb:
async def init_db(app, loop):
app.db = MySanicDb()

await app.create_server()
await app.create_server(
debug=True, return_asyncio_server=True)

assert hasattr(app, "db")
assert isinstance(app.db, MySanicDb)