Fixes "after_server_start" when using return_asyncio_server. (#1676)
* Fixes ability to trigger "after_server_start", "before_server_stop", "after_server_stop" server events when using app.create_server to start your own asyncio_server See example file run_async_advanced for a full example * Fix a missing method on AsyncServer that some tests need Add a tiny bit more documentation in-code Change name of AsyncServerCoro to AsyncioServer
This commit is contained in:
parent
e13f42c17b
commit
7674e917e4
@ -157,4 +157,35 @@ server = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True
|
|||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
task = asyncio.ensure_future(server)
|
task = asyncio.ensure_future(server)
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
```
|
||||||
|
|
||||||
|
Caveat: using this method, calling `app.create_server()` will trigger "before_server_start" server events, but not
|
||||||
|
"after_server_start", "before_server_stop", or "after_server_stop" server events.
|
||||||
|
|
||||||
|
For more advanced use-cases, you can trigger these events using the AsyncioServer object, returned by awaiting
|
||||||
|
the server task.
|
||||||
|
|
||||||
|
Here is an incomplete example (please see `run_async_advanced.py` in examples for something more complete):
|
||||||
|
|
||||||
|
```python
|
||||||
|
serv_coro = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True)
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
serv_task = asyncio.ensure_future(serv_coro, loop=loop)
|
||||||
|
server = loop.run_until_complete(serv_task)
|
||||||
|
server.after_start()
|
||||||
|
try:
|
||||||
|
loop.run_forever()
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
loop.stop()
|
||||||
|
finally:
|
||||||
|
server.before_stop()
|
||||||
|
|
||||||
|
# Wait for server to close
|
||||||
|
close_task = server.close()
|
||||||
|
loop.run_until_complete(close_task)
|
||||||
|
|
||||||
|
# Complete all tasks on the loop
|
||||||
|
for connection in server.connections:
|
||||||
|
connection.close_if_idle()
|
||||||
|
server.after_stop()
|
||||||
```
|
```
|
38
examples/run_async_advanced.py
Normal file
38
examples/run_async_advanced.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from sanic import Sanic
|
||||||
|
from sanic import response
|
||||||
|
from signal import signal, SIGINT
|
||||||
|
import asyncio
|
||||||
|
import uvloop
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
@app.listener('after_server_start')
|
||||||
|
async def after_start_test(app, loop):
|
||||||
|
print("Async Server Started!")
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
async def test(request):
|
||||||
|
return response.json({"answer": "42"})
|
||||||
|
|
||||||
|
asyncio.set_event_loop(uvloop.new_event_loop())
|
||||||
|
serv_coro = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True)
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
serv_task = asyncio.ensure_future(serv_coro, loop=loop)
|
||||||
|
signal(SIGINT, lambda s, f: loop.stop())
|
||||||
|
server = loop.run_until_complete(serv_task)
|
||||||
|
server.after_start()
|
||||||
|
try:
|
||||||
|
loop.run_forever()
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
loop.stop()
|
||||||
|
finally:
|
||||||
|
server.before_stop()
|
||||||
|
|
||||||
|
# Wait for server to close
|
||||||
|
close_task = server.close()
|
||||||
|
loop.run_until_complete(close_task)
|
||||||
|
|
||||||
|
# Complete all tasks on the loop
|
||||||
|
for connection in server.connections:
|
||||||
|
connection.close_if_idle()
|
||||||
|
server.after_stop()
|
@ -634,6 +634,78 @@ def trigger_events(events, loop):
|
|||||||
loop.run_until_complete(result)
|
loop.run_until_complete(result)
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncioServer:
|
||||||
|
"""
|
||||||
|
Wraps an asyncio server with functionality that might be useful to
|
||||||
|
a user who needs to manage the server lifecycle manually.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
"loop",
|
||||||
|
"serve_coro",
|
||||||
|
"_after_start",
|
||||||
|
"_before_stop",
|
||||||
|
"_after_stop",
|
||||||
|
"server",
|
||||||
|
"connections",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
loop,
|
||||||
|
serve_coro,
|
||||||
|
connections,
|
||||||
|
after_start,
|
||||||
|
before_stop,
|
||||||
|
after_stop,
|
||||||
|
):
|
||||||
|
# Note, Sanic already called "before_server_start" events
|
||||||
|
# before this helper was even created. So we don't need it here.
|
||||||
|
self.loop = loop
|
||||||
|
self.serve_coro = serve_coro
|
||||||
|
self._after_start = after_start
|
||||||
|
self._before_stop = before_stop
|
||||||
|
self._after_stop = after_stop
|
||||||
|
self.server = None
|
||||||
|
self.connections = connections
|
||||||
|
|
||||||
|
def after_start(self):
|
||||||
|
"""Trigger "after_server_start" events"""
|
||||||
|
trigger_events(self._after_start, self.loop)
|
||||||
|
|
||||||
|
def before_stop(self):
|
||||||
|
"""Trigger "before_server_stop" events"""
|
||||||
|
trigger_events(self._before_stop, self.loop)
|
||||||
|
|
||||||
|
def after_stop(self):
|
||||||
|
"""Trigger "after_server_stop" events"""
|
||||||
|
trigger_events(self._after_stop, self.loop)
|
||||||
|
|
||||||
|
def is_serving(self):
|
||||||
|
if self.server:
|
||||||
|
return self.server.is_serving()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def wait_closed(self):
|
||||||
|
if self.server:
|
||||||
|
return self.server.wait_closed()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.server:
|
||||||
|
self.server.close()
|
||||||
|
coro = self.wait_closed()
|
||||||
|
task = asyncio.ensure_future(coro, loop=self.loop)
|
||||||
|
return task
|
||||||
|
|
||||||
|
def __await__(self):
|
||||||
|
"""Starts the asyncio server, returns AsyncServerCoro"""
|
||||||
|
task = asyncio.ensure_future(self.serve_coro)
|
||||||
|
while not task.done():
|
||||||
|
yield
|
||||||
|
self.server = task.result()
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
def serve(
|
def serve(
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
@ -700,6 +772,8 @@ def serve(
|
|||||||
:param reuse_port: `True` for multiple workers
|
:param reuse_port: `True` for multiple workers
|
||||||
:param loop: asyncio compatible event loop
|
:param loop: asyncio compatible event loop
|
||||||
:param protocol: subclass of asyncio protocol class
|
:param protocol: subclass of asyncio protocol class
|
||||||
|
:param run_async: bool: Do not create a new event loop for the server,
|
||||||
|
and return an AsyncServer object rather than running it
|
||||||
:param request_class: Request class to use
|
:param request_class: Request class to use
|
||||||
:param access_log: disable/enable access log
|
:param access_log: disable/enable access log
|
||||||
:param websocket_max_size: enforces the maximum size for
|
:param websocket_max_size: enforces the maximum size for
|
||||||
@ -771,7 +845,14 @@ def serve(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if run_async:
|
if run_async:
|
||||||
return server_coroutine
|
return AsyncioServer(
|
||||||
|
loop,
|
||||||
|
server_coroutine,
|
||||||
|
connections,
|
||||||
|
after_start,
|
||||||
|
before_stop,
|
||||||
|
after_stop,
|
||||||
|
)
|
||||||
|
|
||||||
trigger_events(before_start, loop)
|
trigger_events(before_start, loop)
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import asyncio
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -89,3 +90,52 @@ async def test_trigger_before_events_create_server(app):
|
|||||||
|
|
||||||
assert hasattr(app, "db")
|
assert hasattr(app, "db")
|
||||||
assert isinstance(app.db, MySanicDb)
|
assert isinstance(app.db, MySanicDb)
|
||||||
|
|
||||||
|
def test_create_server_trigger_events(app):
|
||||||
|
"""Test if create_server can trigger server events"""
|
||||||
|
|
||||||
|
flag1 = False
|
||||||
|
flag2 = False
|
||||||
|
flag3 = False
|
||||||
|
|
||||||
|
async def stop(app, loop):
|
||||||
|
nonlocal flag1
|
||||||
|
flag1 = True
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
app.stop()
|
||||||
|
|
||||||
|
async def before_stop(app, loop):
|
||||||
|
nonlocal flag2
|
||||||
|
flag2 = True
|
||||||
|
|
||||||
|
async def after_stop(app, loop):
|
||||||
|
nonlocal flag3
|
||||||
|
flag3 = True
|
||||||
|
|
||||||
|
app.listener("after_server_start")(stop)
|
||||||
|
app.listener("before_server_stop")(before_stop)
|
||||||
|
app.listener("after_server_stop")(after_stop)
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
serv_coro = app.create_server(return_asyncio_server=True)
|
||||||
|
serv_task = asyncio.ensure_future(serv_coro, loop=loop)
|
||||||
|
server = loop.run_until_complete(serv_task)
|
||||||
|
server.after_start()
|
||||||
|
try:
|
||||||
|
loop.run_forever()
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
loop.stop()
|
||||||
|
finally:
|
||||||
|
# Run the on_stop function if provided
|
||||||
|
server.before_stop()
|
||||||
|
|
||||||
|
# Wait for server to close
|
||||||
|
close_task = server.close()
|
||||||
|
loop.run_until_complete(close_task)
|
||||||
|
|
||||||
|
# Complete all tasks on the loop
|
||||||
|
signal.stopped = True
|
||||||
|
for connection in server.connections:
|
||||||
|
connection.close_if_idle()
|
||||||
|
server.after_stop()
|
||||||
|
assert flag1 and flag2 and flag3
|
||||||
|
Loading…
x
Reference in New Issue
Block a user