From 2bc162f357bc24a354ade1fe7143d0e978b13a94 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 14 Mar 2021 11:55:02 +0200 Subject: [PATCH 1/2] Add signal reservations --- sanic/signals.py | 61 ++++++++++++++++++++++++++++++++++++------- tests/test_signals.py | 21 +++++++++++++++ 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/sanic/signals.py b/sanic/signals.py index 27f6bfa443..8406a729d7 100644 --- a/sanic/signals.py +++ b/sanic/signals.py @@ -2,8 +2,9 @@ import asyncio +from collections import namedtuple from inspect import isawaitable -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Tuple, Union from sanic_routing import BaseRouter, Route # type: ignore from sanic_routing.exceptions import NotFound # type: ignore @@ -13,6 +14,14 @@ from sanic.models.handler_types import SignalHandler +Reservation = namedtuple("Reservation", ("namespace", "reference", "action")) + +RESERVED_EVENTS = ( + Reservation("server,http", "*", "*"), + Reservation("sanic", "notice,log", None), +) + + class Signal(Route): def get_handler(self, raw_path, method, _): method = method or self.router.DEFAULT_METHOD @@ -97,15 +106,7 @@ def add( # type: ignore event: str, condition: Optional[Dict[str, Any]] = None, ) -> Signal: - parts = path_to_parts(event, self.delimiter) - - if ( - len(parts) != 3 - or parts[0].startswith("<") - or parts[1].startswith("<") - ): - raise InvalidSignal(f"Invalid signal event: {event}") - + parts = self._build_event_parts(event) if parts[2].startswith("<"): name = ".".join([*parts[:-1], "*"]) else: @@ -131,3 +132,43 @@ def finalize(self, do_compile: bool = True): signal.ctx.event = asyncio.Event() return super().finalize(do_compile=do_compile) + + def _build_event_parts(self, event: str) -> Tuple[str, str, str]: + parts = path_to_parts(event, self.delimiter) + if ( + len(parts) != 3 + or parts[0].startswith("<") + or parts[1].startswith("<") + ): + raise InvalidSignal("Invalid signal event: %s" % event) + + current = Reservation(*parts) + for reservation in RESERVED_EVENTS: + if reservation.namespace == "*": + raise InvalidSignal( + "Cannot declare reserved signal event: %s" % event + ) + + if reservation.namespace is not None and ( + current.namespace in reservation.namespace.split(",") + ): + if ( + reservation.reference == "*" + or current.reference + not in reservation.reference.split(",") + ): + raise InvalidSignal( + "Cannot declare reserved signal event: %s" % event + ) + + if reservation.reference is None or reservation.action is None: + continue + + if ( + current.action not in reservation.action.split(",") + or reservation.action == "*" + ): + raise InvalidSignal( + "Cannot declare reserved signal event: %s" % event + ) + return parts diff --git a/tests/test_signals.py b/tests/test_signals.py index a2e8669576..7d606f1d41 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -271,3 +271,24 @@ def bp_signal(): match=" has not yet been registered to an app", ): bp.event("foo.bar.baz") + + +@pytest.mark.parametrize( + "event,expected", + ( + ("foo.bar.baz", True), + ("server.init.before", False), + ("http.request.start", False), + ("sanic.notice.anything", True), + ("sanic.no.way", False), + ), +) +def test_signal_reservation(app, event, expected): + if not expected: + with pytest.raises( + InvalidSignal, + match=f"Cannot declare reserved signal event: {event}", + ): + app.signal(event)(lambda: ...) + else: + app.signal(event)(lambda: ...) From f7dc70bf5d6d3933cab4fd917eb4e6f69253b2c7 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 14 Mar 2021 14:27:26 +0200 Subject: [PATCH 2/2] Simplify reservations --- sanic/signals.py | 42 +++++++----------------------------------- tests/test_signals.py | 1 - 2 files changed, 7 insertions(+), 36 deletions(-) diff --git a/sanic/signals.py b/sanic/signals.py index 8406a729d7..0e6f73f1bd 100644 --- a/sanic/signals.py +++ b/sanic/signals.py @@ -2,7 +2,6 @@ import asyncio -from collections import namedtuple from inspect import isawaitable from typing import Any, Dict, List, Optional, Tuple, Union @@ -14,11 +13,9 @@ from sanic.models.handler_types import SignalHandler -Reservation = namedtuple("Reservation", ("namespace", "reference", "action")) - -RESERVED_EVENTS = ( - Reservation("server,http", "*", "*"), - Reservation("sanic", "notice,log", None), +RESERVED_NAMESPACES = ( + "server", + "http", ) @@ -142,33 +139,8 @@ def _build_event_parts(self, event: str) -> Tuple[str, str, str]: ): raise InvalidSignal("Invalid signal event: %s" % event) - current = Reservation(*parts) - for reservation in RESERVED_EVENTS: - if reservation.namespace == "*": - raise InvalidSignal( - "Cannot declare reserved signal event: %s" % event - ) - - if reservation.namespace is not None and ( - current.namespace in reservation.namespace.split(",") - ): - if ( - reservation.reference == "*" - or current.reference - not in reservation.reference.split(",") - ): - raise InvalidSignal( - "Cannot declare reserved signal event: %s" % event - ) - - if reservation.reference is None or reservation.action is None: - continue - - if ( - current.action not in reservation.action.split(",") - or reservation.action == "*" - ): - raise InvalidSignal( - "Cannot declare reserved signal event: %s" % event - ) + if parts[0] in RESERVED_NAMESPACES: + raise InvalidSignal( + "Cannot declare reserved signal event: %s" % event + ) return parts diff --git a/tests/test_signals.py b/tests/test_signals.py index 7d606f1d41..5380a7e0bc 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -280,7 +280,6 @@ def bp_signal(): ("server.init.before", False), ("http.request.start", False), ("sanic.notice.anything", True), - ("sanic.no.way", False), ), ) def test_signal_reservation(app, event, expected):