Add runtime checking to create_server to verify that startup has been run (#2328)
This commit is contained in:
parent
3d383d7b97
commit
264453459e
|
@ -1,7 +1,5 @@
|
|||
import asyncio
|
||||
|
||||
from signal import SIGINT, signal
|
||||
|
||||
import uvloop
|
||||
|
||||
from sanic import Sanic, response
|
||||
|
@ -15,17 +13,18 @@ async def test(request):
|
|||
return response.json({"answer": "42"})
|
||||
|
||||
|
||||
asyncio.set_event_loop(uvloop.new_event_loop())
|
||||
server = app.create_server(
|
||||
host="0.0.0.0", port=8000, return_asyncio_server=True
|
||||
)
|
||||
loop = asyncio.get_event_loop()
|
||||
task = asyncio.ensure_future(server)
|
||||
server = loop.run_until_complete(task)
|
||||
loop.run_until_complete(server.startup())
|
||||
signal(SIGINT, lambda s, f: loop.stop())
|
||||
async def main():
|
||||
server = await app.create_server(
|
||||
port=8000, host="0.0.0.0", return_asyncio_server=True
|
||||
)
|
||||
|
||||
try:
|
||||
loop.run_forever()
|
||||
finally:
|
||||
loop.stop()
|
||||
if server is None:
|
||||
return
|
||||
|
||||
await server.startup()
|
||||
await server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.set_event_loop(uvloop.new_event_loop())
|
||||
asyncio.run(main())
|
||||
|
|
|
@ -1703,6 +1703,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
|||
self.error_handler, fallback=self.config.FALLBACK_ERROR_FORMAT
|
||||
)
|
||||
TouchUp.run(self)
|
||||
self.state.is_started = True
|
||||
|
||||
async def _server_event(
|
||||
self,
|
||||
|
|
|
@ -42,6 +42,7 @@ class ApplicationState:
|
|||
reload_dirs: Set[Path] = field(default_factory=set)
|
||||
server: Server = field(default=Server.SANIC)
|
||||
is_running: bool = field(default=False)
|
||||
is_started: bool = field(default=False)
|
||||
is_stopping: bool = field(default=False)
|
||||
verbosity: int = field(default=0)
|
||||
workers: int = field(default=0)
|
||||
|
|
|
@ -2,20 +2,27 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from warnings import warn
|
||||
|
||||
from sanic.exceptions import SanicException
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sanic import Sanic
|
||||
|
||||
|
||||
class AsyncioServer:
|
||||
"""
|
||||
Wraps an asyncio server with functionality that might be useful to
|
||||
a user who needs to manage the server lifecycle manually.
|
||||
"""
|
||||
|
||||
__slots__ = ("app", "connections", "loop", "serve_coro", "server", "init")
|
||||
__slots__ = ("app", "connections", "loop", "serve_coro", "server")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
app: Sanic,
|
||||
loop,
|
||||
serve_coro,
|
||||
connections,
|
||||
|
@ -27,13 +34,20 @@ class AsyncioServer:
|
|||
self.loop = loop
|
||||
self.serve_coro = serve_coro
|
||||
self.server = None
|
||||
self.init = False
|
||||
|
||||
@property
|
||||
def init(self):
|
||||
warn(
|
||||
"AsyncioServer.init has been deprecated and will be removed "
|
||||
"in v22.6. Use Sanic.state.is_started instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return self.app.state.is_started
|
||||
|
||||
def startup(self):
|
||||
"""
|
||||
Trigger "before_server_start" events
|
||||
"""
|
||||
self.init = True
|
||||
return self.app._startup()
|
||||
|
||||
def before_start(self):
|
||||
|
@ -77,30 +91,33 @@ class AsyncioServer:
|
|||
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."
|
||||
)
|
||||
return self._serve(self.server.start_serving)
|
||||
|
||||
def serve_forever(self):
|
||||
return self._serve(self.server.serve_forever)
|
||||
|
||||
def _serve(self, serve_func):
|
||||
if self.server:
|
||||
if not self.app.state.is_started:
|
||||
raise SanicException(
|
||||
"Cannot run Sanic server without first running "
|
||||
"await server.startup()"
|
||||
)
|
||||
|
||||
try:
|
||||
return self.server.serve_forever()
|
||||
return serve_func()
|
||||
except AttributeError:
|
||||
name = serve_func.__name__
|
||||
raise NotImplementedError(
|
||||
"server.serve_forever not available in this version "
|
||||
f"server.{name} not available in this version "
|
||||
"of asyncio or uvloop."
|
||||
)
|
||||
|
||||
def _server_event(self, concern: str, action: str):
|
||||
if not self.init:
|
||||
if not self.app.state.is_started:
|
||||
raise SanicException(
|
||||
"Cannot dispatch server event without "
|
||||
"first running server.startup()"
|
||||
"first running await server.startup()"
|
||||
)
|
||||
return self.app._server_event(concern, action, loop=self.loop)
|
||||
|
||||
|
|
|
@ -2,12 +2,10 @@ import asyncio
|
|||
import logging
|
||||
import re
|
||||
|
||||
from email import message
|
||||
from inspect import isawaitable
|
||||
from os import environ
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import py
|
||||
import pytest
|
||||
|
||||
from sanic import Sanic
|
||||
|
@ -41,41 +39,39 @@ def test_app_loop_running(app):
|
|||
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
def test_asyncio_server_no_start_serving(app):
|
||||
if not uvloop_installed():
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio_srv_coro = app.create_server(
|
||||
port=43123,
|
||||
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 = asyncio.get_event_loop()
|
||||
asyncio_srv_coro = app.create_server(
|
||||
port=43123,
|
||||
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_asyncio_server_start_serving(app):
|
||||
if not uvloop_installed():
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio_srv_coro = app.create_server(
|
||||
port=43124,
|
||||
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
|
||||
wait_close = srv.close()
|
||||
loop.run_until_complete(wait_close)
|
||||
# Looks like we can't easily test `serve_forever()`
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio_srv_coro = app.create_server(
|
||||
port=43124,
|
||||
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.startup())
|
||||
loop.run_until_complete(srv.start_serving())
|
||||
assert srv.is_serving() is True
|
||||
wait_close = srv.close()
|
||||
loop.run_until_complete(wait_close)
|
||||
# Looks like we can't easily test `serve_forever()`
|
||||
|
||||
|
||||
def test_create_server_main(app, caplog):
|
||||
|
@ -92,6 +88,21 @@ def test_create_server_main(app, caplog):
|
|||
) in caplog.record_tuples
|
||||
|
||||
|
||||
def test_create_server_no_startup(app):
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio_srv_coro = app.create_server(
|
||||
port=43124,
|
||||
return_asyncio_server=True,
|
||||
asyncio_server_kwargs=dict(start_serving=False),
|
||||
)
|
||||
srv = loop.run_until_complete(asyncio_srv_coro)
|
||||
message = (
|
||||
"Cannot run Sanic server without first running await server.startup()"
|
||||
)
|
||||
with pytest.raises(SanicException, match=message):
|
||||
loop.run_until_complete(srv.start_serving())
|
||||
|
||||
|
||||
def test_create_server_main_convenience(app, caplog):
|
||||
app.main_process_start(lambda *_: ...)
|
||||
loop = asyncio.get_event_loop()
|
||||
|
@ -106,6 +117,19 @@ def test_create_server_main_convenience(app, caplog):
|
|||
) in caplog.record_tuples
|
||||
|
||||
|
||||
def test_create_server_init(app, caplog):
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio_srv_coro = app.create_server(return_asyncio_server=True)
|
||||
server = loop.run_until_complete(asyncio_srv_coro)
|
||||
|
||||
message = (
|
||||
"AsyncioServer.init has been deprecated and will be removed in v22.6. "
|
||||
"Use Sanic.state.is_started instead."
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=message):
|
||||
server.init
|
||||
|
||||
|
||||
def test_app_loop_not_running(app):
|
||||
with pytest.raises(SanicException) as excinfo:
|
||||
app.loop
|
||||
|
|
Loading…
Reference in New Issue
Block a user