Merge pull request #1470 from denismakogon/create-server

make Sanic.create_server return an asyncio.Server
This commit is contained in:
7 2019-01-20 14:21:11 -08:00 committed by GitHub
commit 9cf2e1b519
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 132 additions and 11 deletions

View File

@ -33,6 +33,7 @@ Guides
sanic/changelog sanic/changelog
sanic/contributing sanic/contributing
sanic/api_reference sanic/api_reference
sanic/asyncio_python37
Module Documentation Module Documentation

View File

@ -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
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.

View File

@ -1121,6 +1121,8 @@ class Sanic:
backlog: int = 100, backlog: int = 100,
stop_event: Any = None, stop_event: Any = None,
access_log: Optional[bool] = None, access_log: Optional[bool] = None,
return_asyncio_server=False,
asyncio_server_kwargs=None,
) -> None: ) -> None:
""" """
Asynchronous version of :func:`run`. Asynchronous version of :func:`run`.
@ -1154,6 +1156,13 @@ class Sanic:
:type stop_event: None :type stop_event: None
:param access_log: Enables writing access logs (slows server) :param access_log: Enables writing access logs (slows server)
:type access_log: bool :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 :return: Nothing
""" """
@ -1184,7 +1193,7 @@ class Sanic:
loop=get_event_loop(), loop=get_event_loop(),
protocol=protocol, protocol=protocol,
backlog=backlog, backlog=backlog,
run_async=True, run_async=return_asyncio_server,
) )
# Trigger before_start events # Trigger before_start events
@ -1193,7 +1202,9 @@ class Sanic:
server_settings.get("loop"), 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): async def trigger_events(self, events, loop):
"""Trigger events (functions or async) """Trigger events (functions or async)

View File

@ -656,6 +656,7 @@ def serve(
websocket_write_limit=2 ** 16, websocket_write_limit=2 ** 16,
state=None, state=None,
graceful_shutdown_timeout=15.0, graceful_shutdown_timeout=15.0,
asyncio_server_kwargs=None,
): ):
"""Start asynchronous HTTP Server on an individual process. """Start asynchronous HTTP Server on an individual process.
@ -700,6 +701,8 @@ def serve(
:param router: Router object :param router: Router object
:param graceful_shutdown_timeout: How long take to Force close non-idle :param graceful_shutdown_timeout: How long take to Force close non-idle
connection connection
:param asyncio_server_kwargs: key-value args for asyncio/uvloop
create_server method
:return: Nothing :return: Nothing
""" """
if not run_async: if not run_async:
@ -734,7 +737,9 @@ def serve(
state=state, state=state,
debug=debug, debug=debug,
) )
asyncio_server_kwargs = (
asyncio_server_kwargs if asyncio_server_kwargs else {}
)
server_coroutine = loop.create_server( server_coroutine = loop.create_server(
server, server,
host, host,
@ -743,6 +748,7 @@ def serve(
reuse_port=reuse_port, reuse_port=reuse_port,
sock=sock, sock=sock,
backlog=backlog, backlog=backlog,
**asyncio_server_kwargs
) )
# Instead of pulling time at the end of every request, # Instead of pulling time at the end of every request,

View File

@ -1,12 +1,22 @@
import asyncio import asyncio
import logging import logging
import sys
from inspect import isawaitable
import pytest import pytest
from sanic.exceptions import SanicException from sanic.exceptions import SanicException
from sanic.response import text from sanic.response import text
def uvloop_installed():
try:
import uvloop
return True
except ImportError:
return False
def test_app_loop_running(app): def test_app_loop_running(app):
@app.get("/test") @app.get("/test")
async def handler(request): async def handler(request):
@ -17,6 +27,32 @@ def test_app_loop_running(app):
assert response.text == "pass" 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): def test_app_loop_not_running(app):
with pytest.raises(SanicException) as excinfo: with pytest.raises(SanicException) as excinfo:
app.loop app.loop

View File

@ -231,10 +231,12 @@ async def test_config_access_log_passing_in_create_server(app):
async def _request(sanic, loop): async def _request(sanic, loop):
app.stop() 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 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 assert app.config.ACCESS_LOG == True

View File

@ -51,7 +51,9 @@ class ReuseableSanicTestClient(SanicTestClient):
uri="/", uri="/",
gather_request=True, gather_request=True,
debug=False, debug=False,
server_kwargs={}, server_kwargs={
"return_asyncio_server": True,
},
*request_args, *request_args,
**request_kwargs **request_kwargs
): ):

View File

@ -12,7 +12,8 @@ except BaseException:
def test_logo_base(app, caplog): 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() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
loop._stopping = False loop._stopping = False
@ -31,7 +32,8 @@ def test_logo_base(app, caplog):
def test_logo_false(app, caplog): def test_logo_false(app, caplog):
app.config.LOGO = False 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() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
loop._stopping = False loop._stopping = False
@ -50,7 +52,8 @@ def test_logo_false(app, caplog):
def test_logo_true(app, caplog): def test_logo_true(app, caplog):
app.config.LOGO = True 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() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
loop._stopping = False loop._stopping = False
@ -69,7 +72,8 @@ def test_logo_true(app, caplog):
def test_logo_custom(app, caplog): def test_logo_custom(app, caplog):
app.config.LOGO = "My Custom Logo" 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() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
loop._stopping = False loop._stopping = False

View File

@ -83,7 +83,8 @@ async def test_trigger_before_events_create_server(app):
async def init_db(app, loop): async def init_db(app, loop):
app.db = MySanicDb() app.db = MySanicDb()
await app.create_server() await app.create_server(
debug=True, return_asyncio_server=True)
assert hasattr(app, "db") assert hasattr(app, "db")
assert isinstance(app.db, MySanicDb) assert isinstance(app.db, MySanicDb)