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