Add support for listener and signal prioritization

This commit is contained in:
Adam Hopkins 2023-09-14 08:43:43 +03:00
parent 86a414fd58
commit 0dd1948fd2
No known key found for this signature in database
GPG Key ID: 9F85EE6C807303FB
5 changed files with 35 additions and 12 deletions

View File

@ -432,7 +432,7 @@ class Sanic(
# -------------------------------------------------------------------- # # -------------------------------------------------------------------- #
def register_listener( def register_listener(
self, listener: ListenerType[SanicVar], event: str self, listener: ListenerType[SanicVar], event: str, *, priority: int = 0
) -> ListenerType[SanicVar]: ) -> ListenerType[SanicVar]:
"""Register the listener for a given event. """Register the listener for a given event.
@ -453,10 +453,14 @@ class Sanic(
raise BadRequest(f"Invalid event: {event}. Use one of: {valid}") raise BadRequest(f"Invalid event: {event}. Use one of: {valid}")
if "." in _event: if "." in _event:
self.signal(_event.value)( self.signal(_event.value, priority=priority)(
partial(self._listener, listener=listener) partial(self._listener, listener=listener)
) )
else: else:
if priority:
error_logger.warning(
f"Priority is not supported for {_event.value}"
)
self.listeners[_event.value].append(listener) self.listeners[_event.value].append(listener)
return listener return listener
@ -580,7 +584,7 @@ class Sanic(
return handler.handler return handler.handler
def _apply_listener(self, listener: FutureListener): def _apply_listener(self, listener: FutureListener):
return self.register_listener(listener.listener, listener.event) return self.register_listener(listener.listener, listener.event, priority=listener.priority)
def _apply_route( def _apply_route(
self, route: FutureRoute, overwrite: bool = False self, route: FutureRoute, overwrite: bool = False
@ -632,7 +636,13 @@ class Sanic(
def _apply_signal(self, signal: FutureSignal) -> Signal: def _apply_signal(self, signal: FutureSignal) -> Signal:
with self.amend(): with self.amend():
return self.signal_router.add(*signal) return self.signal_router.add(
handler=signal.handler,
event=signal.event,
condition=signal.condition,
exclusive=signal.exclusive,
priority=signal.priority,
)
@overload @overload
def dispatch( def dispatch(
@ -1399,7 +1409,7 @@ class Sanic(
if not hasattr(handler, "is_websocket"): if not hasattr(handler, "is_websocket"):
raise ServerError( raise ServerError(
f"Invalid response type {response!r} " f"Invalid response type {response!r} "
"(need HTTPResponse)" "(need HTTPResponse)"
) )
except CancelledError: # type: ignore except CancelledError: # type: ignore

View File

@ -1,7 +1,7 @@
from enum import Enum, auto from enum import Enum, auto
from functools import partial from functools import partial
from typing import Callable, List, Optional, Union, overload from typing import Callable, List, Optional, Union, overload
from operator import attrgetter
from sanic.base.meta import SanicMeta from sanic.base.meta import SanicMeta
from sanic.exceptions import BadRequest from sanic.exceptions import BadRequest
from sanic.models.futures import FutureListener from sanic.models.futures import FutureListener
@ -38,6 +38,8 @@ class ListenerMixin(metaclass=SanicMeta):
listener_or_event: ListenerType[Sanic], listener_or_event: ListenerType[Sanic],
event_or_none: str, event_or_none: str,
apply: bool = ..., apply: bool = ...,
*,
priority: int = 0,
) -> ListenerType[Sanic]: ) -> ListenerType[Sanic]:
... ...
@ -47,6 +49,8 @@ class ListenerMixin(metaclass=SanicMeta):
listener_or_event: str, listener_or_event: str,
event_or_none: None = ..., event_or_none: None = ...,
apply: bool = ..., apply: bool = ...,
*,
priority: int = 0,
) -> Callable[[ListenerType[Sanic]], ListenerType[Sanic]]: ) -> Callable[[ListenerType[Sanic]], ListenerType[Sanic]]:
... ...
@ -55,6 +59,8 @@ class ListenerMixin(metaclass=SanicMeta):
listener_or_event: Union[ListenerType[Sanic], str], listener_or_event: Union[ListenerType[Sanic], str],
event_or_none: Optional[str] = None, event_or_none: Optional[str] = None,
apply: bool = True, apply: bool = True,
*,
priority: int = 0,
) -> Union[ ) -> Union[
ListenerType[Sanic], ListenerType[Sanic],
Callable[[ListenerType[Sanic]], ListenerType[Sanic]], Callable[[ListenerType[Sanic]], ListenerType[Sanic]],
@ -81,6 +87,7 @@ class ListenerMixin(metaclass=SanicMeta):
listener_or_event (Union[ListenerType[Sanic], str]): A listener function or an event name. listener_or_event (Union[ListenerType[Sanic], str]): A listener function or an event name.
event_or_none (Optional[str]): The event name to listen for if `listener_or_event` is a function. Defaults to `None`. event_or_none (Optional[str]): The event name to listen for if `listener_or_event` is a function. Defaults to `None`.
apply (bool): Whether to apply the listener immediately. Defaults to `True`. apply (bool): Whether to apply the listener immediately. Defaults to `True`.
priority (int): The priority of the listener. Defaults to `0`.
Returns: Returns:
Union[ListenerType[Sanic], Callable[[ListenerType[Sanic]], ListenerType[Sanic]]]: The listener or a callable that takes a listener. Union[ListenerType[Sanic], Callable[[ListenerType[Sanic]], ListenerType[Sanic]]]: The listener or a callable that takes a listener.
@ -96,7 +103,7 @@ class ListenerMixin(metaclass=SanicMeta):
""" # noqa: E501 """ # noqa: E501
def register_listener( def register_listener(
listener: ListenerType[Sanic], event: str listener: ListenerType[Sanic], event: str, priority: int = 0
) -> ListenerType[Sanic]: ) -> ListenerType[Sanic]:
"""A helper function to register a listener for an event. """A helper function to register a listener for an event.
@ -112,7 +119,7 @@ class ListenerMixin(metaclass=SanicMeta):
""" """
nonlocal apply nonlocal apply
future_listener = FutureListener(listener, event) future_listener = FutureListener(listener, event, priority)
self._future_listeners.append(future_listener) self._future_listeners.append(future_listener)
if apply: if apply:
self._apply_listener(future_listener) self._apply_listener(future_listener)
@ -123,9 +130,9 @@ class ListenerMixin(metaclass=SanicMeta):
raise BadRequest( raise BadRequest(
"Invalid event registration: Missing event name." "Invalid event registration: Missing event name."
) )
return register_listener(listener_or_event, event_or_none) return register_listener(listener_or_event, event_or_none, priority)
else: else:
return partial(register_listener, event=listener_or_event) return partial(register_listener, event=listener_or_event, priority=priority)
def main_process_start( def main_process_start(
self, listener: ListenerType[Sanic] self, listener: ListenerType[Sanic]

View File

@ -24,6 +24,7 @@ class SignalMixin(metaclass=SanicMeta):
apply: bool = True, apply: bool = True,
condition: Optional[Dict[str, Any]] = None, condition: Optional[Dict[str, Any]] = None,
exclusive: bool = True, exclusive: bool = True,
priority: int = 0,
) -> Callable[[SignalHandler], SignalHandler]: ) -> Callable[[SignalHandler], SignalHandler]:
""" """
For creating a signal handler, used similar to a route handler: For creating a signal handler, used similar to a route handler:
@ -51,7 +52,7 @@ class SignalMixin(metaclass=SanicMeta):
def decorator(handler: SignalHandler): def decorator(handler: SignalHandler):
future_signal = FutureSignal( future_signal = FutureSignal(
handler, event_value, HashableDict(condition or {}), exclusive handler, event_value, HashableDict(condition or {}), exclusive, priority
) )
self._future_signals.add(future_signal) self._future_signals.add(future_signal)

View File

@ -33,6 +33,7 @@ class FutureRoute(NamedTuple):
class FutureListener(NamedTuple): class FutureListener(NamedTuple):
listener: ListenerType listener: ListenerType
event: str event: str
priority: int
class FutureMiddleware(NamedTuple): class FutureMiddleware(NamedTuple):
@ -65,6 +66,7 @@ class FutureSignal(NamedTuple):
event: str event: str
condition: Optional[Dict[str, str]] condition: Optional[Dict[str, str]]
exclusive: bool exclusive: bool
priority: int
class FutureRegistry(set): class FutureRegistry(set):

View File

@ -250,6 +250,8 @@ class SignalRouter(BaseRouter):
event: str, event: str,
condition: Optional[Dict[str, Any]] = None, condition: Optional[Dict[str, Any]] = None,
exclusive: bool = True, exclusive: bool = True,
*,
priority: int = 0,
) -> Signal: ) -> Signal:
event_definition = event event_definition = event
parts = self._build_event_parts(event) parts = self._build_event_parts(event)
@ -268,6 +270,7 @@ class SignalRouter(BaseRouter):
handler, handler,
name=name, name=name,
append=True, append=True,
priority=priority,
) # type: ignore ) # type: ignore
signal.ctx.exclusive = exclusive signal.ctx.exclusive = exclusive