RFC/1630 Signals (#2042)

* Temp working version of initial signal api

* fix signals router finalizing

* Additional tests

* Add event test

* finalize test

* remove old comment

* Add some missing annotations

* multiple apps per BP support

* deepsource?

* rtemove deepsource

* nominal change

* fix blueprints test

* trivial change to trigger build

* signal docstring

* squash

* squash

* Add a couple new tests

* Add some suggestions from review

* Remove inaccessible code

* Change where to condition
This commit is contained in:
Adam Hopkins
2021-03-14 10:09:07 +02:00
committed by GitHub
parent 1165663ec1
commit 824f41d6e0
16 changed files with 658 additions and 59 deletions

View File

@@ -3,7 +3,13 @@ import logging.config
import os
import re
from asyncio import CancelledError, Protocol, ensure_future, get_event_loop
from asyncio import (
CancelledError,
Protocol,
ensure_future,
get_event_loop,
wait_for,
)
from asyncio.futures import Future
from collections import defaultdict, deque
from functools import partial
@@ -13,7 +19,9 @@ from ssl import Purpose, SSLContext, create_default_context
from traceback import format_exc
from typing import (
Any,
Awaitable,
Callable,
Coroutine,
Deque,
Dict,
Iterable,
@@ -26,6 +34,7 @@ from typing import (
from urllib.parse import urlencode, urlunparse
from sanic_routing.exceptions import FinalizationError # type: ignore
from sanic_routing.exceptions import NotFound # type: ignore
from sanic_routing.route import Route # type: ignore
from sanic import reloader_helpers
@@ -48,20 +57,17 @@ from sanic.models.futures import (
FutureListener,
FutureMiddleware,
FutureRoute,
FutureSignal,
FutureStatic,
)
from sanic.models.handler_types import ListenerType, MiddlewareType
from sanic.request import Request
from sanic.response import BaseHTTPResponse, HTTPResponse
from sanic.router import Router
from sanic.server import (
AsyncioServer,
HttpProtocol,
Signal,
serve,
serve_multiple,
serve_single,
)
from sanic.server import AsyncioServer, HttpProtocol
from sanic.server import Signal as ServerSignal
from sanic.server import serve, serve_multiple, serve_single
from sanic.signals import Signal, SignalRouter
from sanic.websocket import ConnectionClosed, WebSocketProtocol
@@ -76,10 +82,11 @@ class Sanic(BaseSanic):
def __init__(
self,
name: str = None,
router: Router = None,
error_handler: ErrorHandler = None,
router: Optional[Router] = None,
signal_router: Optional[SignalRouter] = None,
error_handler: Optional[ErrorHandler] = None,
load_env: bool = True,
request_class: Type[Request] = None,
request_class: Optional[Type[Request]] = None,
strict_slashes: bool = False,
log_config: Optional[Dict[str, Any]] = None,
configure_logging: bool = True,
@@ -100,6 +107,7 @@ class Sanic(BaseSanic):
self.name = name
self.asgi = False
self.router = router or Router()
self.signal_router = signal_router or SignalRouter()
self.request_class = request_class
self.error_handler = error_handler or ErrorHandler()
self.config = Config(load_env=load_env)
@@ -162,7 +170,7 @@ class Sanic(BaseSanic):
also return a future, and the actual ensure_future call
is delayed until before server start.
`See user guide
`See user guide re: background tasks
<https://sanicframework.org/guide/basics/tasks.html#background-tasks>`__
:param task: future, couroutine or awaitable
@@ -309,6 +317,28 @@ class Sanic(BaseSanic):
middleware.middleware, middleware.attach_to
)
def _apply_signal(self, signal: FutureSignal) -> Signal:
return self.signal_router.add(*signal)
def dispatch(
self,
event: str,
*,
condition: Optional[Dict[str, str]] = None,
context: Optional[Dict[str, Any]] = None,
) -> Coroutine[Any, Any, Awaitable[Any]]:
return self.signal_router.dispatch(
event,
context=context,
condition=condition,
)
def event(self, event: str, timeout: Optional[Union[int, float]] = None):
signal = self.signal_router.name_index.get(event)
if not signal:
raise NotFound("Could not find signal %s" % event)
return wait_for(signal.ctx.event.wait(), timeout=timeout)
def enable_websocket(self, enable=True):
"""Enable or disable the support for websocket.
@@ -382,7 +412,7 @@ class Sanic(BaseSanic):
app.config.SERVER_NAME = "myserver:7777"
`See user guide
`See user guide re: routing
<https://sanicframework.org/guide/basics/routing.html#generating-a-url>`__
:param view_name: string referencing the view name
@@ -1031,11 +1061,9 @@ class Sanic(BaseSanic):
):
"""Helper function used by `run` and `create_server`."""
try:
self.router.finalize()
except FinalizationError as e:
if not Sanic.test_mode:
raise e
self.listeners["before_server_start"] = [
self.finalize
] + self.listeners["before_server_start"]
if isinstance(ssl, dict):
# try common aliaseses
@@ -1064,7 +1092,7 @@ class Sanic(BaseSanic):
"unix": unix,
"ssl": ssl,
"app": self,
"signal": Signal(),
"signal": ServerSignal(),
"loop": loop,
"register_sys_signals": register_sys_signals,
"backlog": backlog,
@@ -1159,7 +1187,7 @@ class Sanic(BaseSanic):
"""
Update app.config. Full implementation can be found in the user guide.
`See user guide
`See user guide re: configuration
<https://sanicframework.org/guide/deployment/configuration.html#basics>`__
"""
@@ -1196,7 +1224,7 @@ class Sanic(BaseSanic):
'Multiple Sanic apps found, use Sanic.get_app("app_name")'
)
elif len(cls._app_registry) == 0:
raise SanicException(f"No Sanic apps have been registered.")
raise SanicException("No Sanic apps have been registered.")
else:
return list(cls._app_registry.values())[0]
try:
@@ -1205,3 +1233,17 @@ class Sanic(BaseSanic):
if force_create:
return cls(name)
raise SanicException(f'Sanic app name "{name}" not found.')
# -------------------------------------------------------------------- #
# Static methods
# -------------------------------------------------------------------- #
@staticmethod
async def finalize(app, _):
try:
app.router.finalize()
if app.signal_router.routes:
app.signal_router.finalize() # noqa
except FinalizationError as e:
if not Sanic.test_mode:
raise e # noqa