From b72fde8fed745d81eb627e115837929c3fef152e Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 3 Oct 2021 16:27:46 +0300 Subject: [PATCH 1/2] Create lazy apply decorator --- sanic/blueprints.py | 53 +++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/sanic/blueprints.py b/sanic/blueprints.py index 617ec6060b..a4fc4740ce 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -4,6 +4,7 @@ from collections import defaultdict from copy import deepcopy +from functools import wraps from types import SimpleNamespace from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Union @@ -26,6 +27,25 @@ from sanic import Sanic # noqa +def lazy(func): + @wraps(func) + def decorator(bp, *args, **kwargs): + kwargs["apply"] = False + + def wrapper(handler): + future = func(bp, *args, **kwargs)(handler) + + if bp.registered: + for app in bp.apps: + bp.register(app, {}) + + return future + + return wrapper + + return decorator + + class Blueprint(BaseSanic): """ In *Sanic* terminology, a **Blueprint** is a logical collection of @@ -115,29 +135,16 @@ def apps(self): ) return self._apps - def route(self, *args, **kwargs): - kwargs["apply"] = False - return super().route(*args, **kwargs) - - def static(self, *args, **kwargs): - kwargs["apply"] = False - return super().static(*args, **kwargs) - - def middleware(self, *args, **kwargs): - kwargs["apply"] = False - return super().middleware(*args, **kwargs) - - def listener(self, *args, **kwargs): - kwargs["apply"] = False - return super().listener(*args, **kwargs) - - def exception(self, *args, **kwargs): - kwargs["apply"] = False - return super().exception(*args, **kwargs) - - def signal(self, event: str, *args, **kwargs): - kwargs["apply"] = False - return super().signal(event, *args, **kwargs) + @property + def registered(self) -> bool: + return bool(self._apps) + + exception = lazy(BaseSanic.exception) + listener = lazy(BaseSanic.listener) + middleware = lazy(BaseSanic.middleware) + route = lazy(BaseSanic.route) + signal = lazy(BaseSanic.signal) + static = lazy(BaseSanic.static) def reset(self): self._apps: Set[Sanic] = set() From 65f891a6a20107596fba5da2f4d454e6afcfa1e3 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Mon, 15 Nov 2021 11:35:25 +0200 Subject: [PATCH 2/2] Passing unit tests --- sanic/app.py | 3 +- sanic/blueprints.py | 61 ++++++++++++++++++++++-------------- tests/test_blueprint_copy.py | 4 +-- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index bc63336408..ea7baa4e4c 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -116,6 +116,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta): "_future_exceptions", "_future_listeners", "_future_middleware", + "_future_registry", "_future_routes", "_future_signals", "_future_statics", @@ -1627,7 +1628,7 @@ def signalize(self): raise e async def _startup(self): - self._future_registry = set() + self._future_registry.clear() self.signalize() self.finalize() ErrorHandler.finalize(self.error_handler) diff --git a/sanic/blueprints.py b/sanic/blueprints.py index ebd34889d2..290773faf6 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -15,7 +15,9 @@ Iterable, List, Optional, + Sequence, Set, + Tuple, Union, ) @@ -173,19 +175,6 @@ def reset(self): self.statics: List[RouteHandler] = [] self.websocket_routes: List[Route] = [] - def register_futures(self, apps: Set[Sanic]): - for app in apps: - app._future_registry |= set( - chain( - self._future_routes, - self._future_statics, - self._future_middleware, - self._future_exceptions, - self._future_listeners, - self._future_signals, - ) - ) - def copy( self, name: str, @@ -312,11 +301,10 @@ def register(self, app, options): middleware = [] exception_handlers = [] listeners = defaultdict(list) + registered = set() # Routes for future in self._future_routes: - if future in app._future_registry: - continue # attach the blueprint name to the handler so that it can be # prefixed properly in the router future.handler.__blueprintname__ = self.name @@ -340,12 +328,15 @@ def register(self, app, options): ) name = app._generate_name(future.name) + host = future.host or self.host + if isinstance(host, list): + host = tuple(host) apply_route = FutureRoute( future.handler, uri[1:] if uri.startswith("//") else uri, future.methods, - future.host or self.host, + host, strict_slashes, future.stream, version, @@ -359,6 +350,10 @@ def register(self, app, options): error_format, ) + if (self, apply_route) in app._future_registry: + continue + + registered.add(apply_route) route = app._apply_route(apply_route) operation = ( routes.extend if isinstance(route, list) else routes.append @@ -367,11 +362,14 @@ def register(self, app, options): # Static Files for future in self._future_statics: - if future in app._future_registry: - continue # Prepend the blueprint URI prefix if available uri = url_prefix + future.uri if url_prefix else future.uri apply_route = FutureStatic(uri, *future[1:]) + + if (self, apply_route) in app._future_registry: + continue + + registered.add(apply_route) route = app._apply_static(apply_route) routes.append(route) @@ -380,13 +378,13 @@ def register(self, app, options): if route_names: # Middleware for future in self._future_middleware: - if future in app._future_registry: + if (self, future) in app._future_registry: continue middleware.append(app._apply_middleware(future, route_names)) # Exceptions for future in self._future_exceptions: - if future in app._future_registry: + if (self, future) in app._future_registry: continue exception_handlers.append( app._apply_exception_handler(future, route_names) @@ -394,13 +392,13 @@ def register(self, app, options): # Event listeners for future in self._future_listeners: - if future in app._future_registry: + if (self, future) in app._future_registry: continue listeners[future.event].append(app._apply_listener(future)) # Signals for future in self._future_signals: - if future in app._future_registry: + if (self, future) in app._future_registry: continue future.condition.update({"blueprint": self.name}) app._apply_signal(future) @@ -414,7 +412,17 @@ def register(self, app, options): self.listeners.update(dict(listeners)) if self.registered: - self.register_futures(self.apps) + self.register_futures( + self.apps, + self, + chain( + registered, + self._future_middleware, + self._future_exceptions, + self._future_listeners, + self._future_signals, + ), + ) async def dispatch(self, *args, **kwargs): condition = kwargs.pop("condition", {}) @@ -446,3 +454,10 @@ def _extract_value(*values): value = v break return value + + @staticmethod + def register_futures( + apps: Set[Sanic], bp: Blueprint, futures: Sequence[Tuple[Any, ...]] + ): + for app in apps: + app._future_registry.update(set((bp, item) for item in futures)) diff --git a/tests/test_blueprint_copy.py b/tests/test_blueprint_copy.py index 033e2e2041..ca8cd67ebf 100644 --- a/tests/test_blueprint_copy.py +++ b/tests/test_blueprint_copy.py @@ -1,6 +1,4 @@ -from copy import deepcopy - -from sanic import Blueprint, Sanic, blueprints, response +from sanic import Blueprint, Sanic from sanic.response import text