Skip to content

Commit

Permalink
Merge pull request #1470 from denismakogon/create-server
Browse files Browse the repository at this point in the history
make Sanic.create_server return an asyncio.Server
  • Loading branch information
yunstanford authored Jan 20, 2019
2 parents 99f34c9 + 1473753 commit 9cf2e1b
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 11 deletions.
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


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)

0 comments on commit 9cf2e1b

Please sign in to comment.