From 0dd1948fd2bf7d6c8bd4919c8ba8b58d3bb96097 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 14 Sep 2023 08:43:43 +0300 Subject: [PATCH] Add support for listener and signal prioritization --- sanic/app.py | 20 +++++++++++++++----- sanic/mixins/listeners.py | 19 +++++++++++++------ sanic/mixins/signals.py | 3 ++- sanic/models/futures.py | 2 ++ sanic/signals.py | 3 +++ 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index 7f1cb2c5..e256bf45 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -432,7 +432,7 @@ class Sanic( # -------------------------------------------------------------------- # def register_listener( - self, listener: ListenerType[SanicVar], event: str + self, listener: ListenerType[SanicVar], event: str, *, priority: int = 0 ) -> ListenerType[SanicVar]: """Register the listener for a given event. @@ -453,10 +453,14 @@ class Sanic( raise BadRequest(f"Invalid event: {event}. Use one of: {valid}") if "." in _event: - self.signal(_event.value)( + self.signal(_event.value, priority=priority)( partial(self._listener, listener=listener) ) else: + if priority: + error_logger.warning( + f"Priority is not supported for {_event.value}" + ) self.listeners[_event.value].append(listener) return listener @@ -580,7 +584,7 @@ class Sanic( return handler.handler 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( self, route: FutureRoute, overwrite: bool = False @@ -632,7 +636,13 @@ class Sanic( def _apply_signal(self, signal: FutureSignal) -> Signal: 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 def dispatch( @@ -1399,7 +1409,7 @@ class Sanic( if not hasattr(handler, "is_websocket"): raise ServerError( f"Invalid response type {response!r} " - "(need HTTPResponse)" + "(need HTTPResponse)" ) except CancelledError: # type: ignore diff --git a/sanic/mixins/listeners.py b/sanic/mixins/listeners.py index 6b547ca9..d717ba1e 100644 --- a/sanic/mixins/listeners.py +++ b/sanic/mixins/listeners.py @@ -1,7 +1,7 @@ from enum import Enum, auto from functools import partial from typing import Callable, List, Optional, Union, overload - +from operator import attrgetter from sanic.base.meta import SanicMeta from sanic.exceptions import BadRequest from sanic.models.futures import FutureListener @@ -25,7 +25,7 @@ class ListenerEvent(str, Enum): AFTER_RELOAD_TRIGGER = auto() -class ListenerMixin(metaclass=SanicMeta): +class ListenerMixin(metaclass=SanicMeta): def __init__(self, *args, **kwargs) -> None: self._future_listeners: List[FutureListener] = [] @@ -38,6 +38,8 @@ class ListenerMixin(metaclass=SanicMeta): listener_or_event: ListenerType[Sanic], event_or_none: str, apply: bool = ..., + *, + priority: int = 0, ) -> ListenerType[Sanic]: ... @@ -47,6 +49,8 @@ class ListenerMixin(metaclass=SanicMeta): listener_or_event: str, event_or_none: None = ..., apply: bool = ..., + *, + priority: int = 0, ) -> Callable[[ListenerType[Sanic]], ListenerType[Sanic]]: ... @@ -55,6 +59,8 @@ class ListenerMixin(metaclass=SanicMeta): listener_or_event: Union[ListenerType[Sanic], str], event_or_none: Optional[str] = None, apply: bool = True, + *, + priority: int = 0, ) -> Union[ 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. 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`. + priority (int): The priority of the listener. Defaults to `0`. Returns: 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 def register_listener( - listener: ListenerType[Sanic], event: str + listener: ListenerType[Sanic], event: str, priority: int = 0 ) -> ListenerType[Sanic]: """A helper function to register a listener for an event. @@ -112,7 +119,7 @@ class ListenerMixin(metaclass=SanicMeta): """ nonlocal apply - future_listener = FutureListener(listener, event) + future_listener = FutureListener(listener, event, priority) self._future_listeners.append(future_listener) if apply: self._apply_listener(future_listener) @@ -123,9 +130,9 @@ class ListenerMixin(metaclass=SanicMeta): raise BadRequest( "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: - return partial(register_listener, event=listener_or_event) + return partial(register_listener, event=listener_or_event, priority=priority) def main_process_start( self, listener: ListenerType[Sanic] diff --git a/sanic/mixins/signals.py b/sanic/mixins/signals.py index f1419dc4..aa595cc9 100644 --- a/sanic/mixins/signals.py +++ b/sanic/mixins/signals.py @@ -24,6 +24,7 @@ class SignalMixin(metaclass=SanicMeta): apply: bool = True, condition: Optional[Dict[str, Any]] = None, exclusive: bool = True, + priority: int = 0, ) -> Callable[[SignalHandler], SignalHandler]: """ For creating a signal handler, used similar to a route handler: @@ -51,7 +52,7 @@ class SignalMixin(metaclass=SanicMeta): def decorator(handler: SignalHandler): future_signal = FutureSignal( - handler, event_value, HashableDict(condition or {}), exclusive + handler, event_value, HashableDict(condition or {}), exclusive, priority ) self._future_signals.add(future_signal) diff --git a/sanic/models/futures.py b/sanic/models/futures.py index f9e0644c..c47a27e5 100644 --- a/sanic/models/futures.py +++ b/sanic/models/futures.py @@ -33,6 +33,7 @@ class FutureRoute(NamedTuple): class FutureListener(NamedTuple): listener: ListenerType event: str + priority: int class FutureMiddleware(NamedTuple): @@ -65,6 +66,7 @@ class FutureSignal(NamedTuple): event: str condition: Optional[Dict[str, str]] exclusive: bool + priority: int class FutureRegistry(set): diff --git a/sanic/signals.py b/sanic/signals.py index fe252c12..b0a5c777 100644 --- a/sanic/signals.py +++ b/sanic/signals.py @@ -250,6 +250,8 @@ class SignalRouter(BaseRouter): event: str, condition: Optional[Dict[str, Any]] = None, exclusive: bool = True, + *, + priority: int = 0, ) -> Signal: event_definition = event parts = self._build_event_parts(event) @@ -268,6 +270,7 @@ class SignalRouter(BaseRouter): handler, name=name, append=True, + priority=priority, ) # type: ignore signal.ctx.exclusive = exclusive