From a0a3840094f768a47064f43c5ed2acc5b02602bb Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Wed, 15 Dec 2021 01:07:42 +0200 Subject: [PATCH 1/4] Add pre-registry --- sanic/app.py | 9 ++++++++- sanic/blueprints.py | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/sanic/app.py b/sanic/app.py index 6a28747175..b070a1e999 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -50,7 +50,7 @@ ) from sanic_routing.route import Route # type: ignore -from sanic import reloader_helpers +from sanic import blueprint_group, reloader_helpers from sanic.application.logo import get_logo from sanic.application.motd import MOTD from sanic.application.state import ApplicationState, Mode @@ -536,6 +536,12 @@ def blueprint( blueprint.strict_slashes = self.strict_slashes blueprint.register(self, options) + def _register_lazy_blueprints(self): + for name, reg_info in Blueprint.__pre_registry__.items(): + blueprint = reg_info.pop("bp") + if name == self.name and blueprint.name not in self.blueprints: + self.blueprint(blueprint, **reg_info) + def url_for(self, view_name: str, **kwargs): """Build a URL based on a view name and the values provided. @@ -1697,6 +1703,7 @@ def signalize(self): async def _startup(self): self._future_registry.clear() + self._register_lazy_blueprints() self.signalize() self.finalize() ErrorHandler.finalize( diff --git a/sanic/blueprints.py b/sanic/blueprints.py index 6a6c2e8237..d4e347248f 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -107,6 +107,7 @@ class Blueprint(BaseSanic): "version_prefix", "websocket_routes", ) + __pre_registry__: Dict[str, Any] = {} def __init__( self, @@ -461,3 +462,25 @@ def register_futures( ): for app in apps: app._future_registry.update(set((bp, item) for item in futures)) + + def pre_register( + self, + name: str = None, + url_prefix: Optional[str] = None, + host: Optional[Union[List[str], str]] = None, + version: Optional[Union[int, str, float]] = None, + strict_slashes: Optional[bool] = None, + version_prefix: Union[str, Default] = _default, + ) -> None: + self.__class__.__pre_registry__[name] = { + k: v + for k, v in { + "bp": self, + "url_prefix": url_prefix, + "host": host, + "version": version, + "strict_slashes": strict_slashes, + "version_prefix": version_prefix, + }.items() + if v is not None and v is not _default + } From 6d3f1e9982a0969bf9f20a9715018c954931af5d Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Wed, 15 Dec 2021 01:44:56 +0200 Subject: [PATCH 2/4] Add lazy classmethod --- sanic/app.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/sanic/app.py b/sanic/app.py index b070a1e999..dbce6c6ca2 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -1683,6 +1683,31 @@ def get_app( return cls(name) raise SanicException(f'Sanic app name "{name}" not found.') + @classmethod + def lazy( + cls, + app_name: str, + *, + name: str = None, + url_prefix: Optional[str] = None, + host: Optional[Union[List[str], str]] = None, + version: Optional[Union[int, str, float]] = None, + strict_slashes: Optional[bool] = None, + version_prefix: str = "/v", + ) -> Blueprint: + if not name: + name = f"bp{len(Blueprint.__pre_registry__)}" + bp = Blueprint( + name=name, + url_prefix=url_prefix, + host=host, + version=version, + strict_slashes=strict_slashes, + version_prefix=version_prefix, + ) + bp.pre_register(app_name) + return bp + # -------------------------------------------------------------------- # # Lifecycle # -------------------------------------------------------------------- # From 266af1e279b641275ad7795505af3df286233133 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Wed, 15 Dec 2021 09:12:56 +0200 Subject: [PATCH 3/4] Allow empty lazy, and multiple pre-registrations --- sanic/app.py | 28 ++++++++++++++++++++++------ sanic/blueprints.py | 30 ++++++++++++++++-------------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index dbce6c6ca2..fbb3911565 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -50,7 +50,7 @@ ) from sanic_routing.route import Route # type: ignore -from sanic import blueprint_group, reloader_helpers +from sanic import reloader_helpers from sanic.application.logo import get_logo from sanic.application.motd import MOTD from sanic.application.state import ApplicationState, Mode @@ -67,6 +67,7 @@ URLBuildError, ) from sanic.handlers import ErrorHandler +from sanic.helpers import Default, _default from sanic.http import Stage from sanic.log import LOGGING_CONFIG_DEFAULTS, Colors, error_logger, logger from sanic.mixins.listeners import ListenerEvent @@ -537,10 +538,25 @@ def blueprint( blueprint.register(self, options) def _register_lazy_blueprints(self): - for name, reg_info in Blueprint.__pre_registry__.items(): - blueprint = reg_info.pop("bp") - if name == self.name and blueprint.name not in self.blueprints: - self.blueprint(blueprint, **reg_info) + registry = {**Blueprint.__pre_registry__} + if _default in Blueprint.__pre_registry__: + if len(Sanic._app_registry) > 1: + raise SanicException( + "Ambiguous Blueprint pre-registration detected. When " + "there are multiple Sanic application instances, all " + "pre-registrations must use an application name." + ) + + registry = { + self.name if k is _default else k: v + for k, v in registry.items() + } + + for name, registrants in registry.items(): + for reg_info in registrants: + blueprint = reg_info.pop("bp") + if name == self.name and blueprint.name not in self.blueprints: + self.blueprint(blueprint, **reg_info) def url_for(self, view_name: str, **kwargs): """Build a URL based on a view name and the values provided. @@ -1686,7 +1702,7 @@ def get_app( @classmethod def lazy( cls, - app_name: str, + app_name: Union[str, Default] = _default, *, name: str = None, url_prefix: Optional[str] = None, diff --git a/sanic/blueprints.py b/sanic/blueprints.py index d4e347248f..1c313f6717 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -107,7 +107,7 @@ class Blueprint(BaseSanic): "version_prefix", "websocket_routes", ) - __pre_registry__: Dict[str, Any] = {} + __pre_registry__: Dict[Union[str, Default], Any] = defaultdict(list) def __init__( self, @@ -465,22 +465,24 @@ def register_futures( def pre_register( self, - name: str = None, + name: Union[str, Default], url_prefix: Optional[str] = None, host: Optional[Union[List[str], str]] = None, version: Optional[Union[int, str, float]] = None, strict_slashes: Optional[bool] = None, version_prefix: Union[str, Default] = _default, ) -> None: - self.__class__.__pre_registry__[name] = { - k: v - for k, v in { - "bp": self, - "url_prefix": url_prefix, - "host": host, - "version": version, - "strict_slashes": strict_slashes, - "version_prefix": version_prefix, - }.items() - if v is not None and v is not _default - } + self.__class__.__pre_registry__[name].append( + { + k: v + for k, v in { + "bp": self, + "url_prefix": url_prefix, + "host": host, + "version": version, + "strict_slashes": strict_slashes, + "version_prefix": version_prefix, + }.items() + if v is not None and v is not _default + } + ) From c8fa52e2d2e89aa1eebe6a4df92b3a887f098bc7 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Wed, 15 Dec 2021 09:36:40 +0200 Subject: [PATCH 4/4] Allow application creation from Blueprint at startup --- sanic/app.py | 6 +++++- sanic/blueprints.py | 3 +++ sanic/cli/app.py | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index fbb3911565..466fc12be4 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -547,6 +547,9 @@ def _register_lazy_blueprints(self): "pre-registrations must use an application name." ) + if self.name in registry and _default in registry: + registry[_default].extend(registry.pop(self.name)) + registry = { self.name if k is _default else k: v for k, v in registry.items() @@ -1712,7 +1715,8 @@ def lazy( version_prefix: str = "/v", ) -> Blueprint: if not name: - name = f"bp{len(Blueprint.__pre_registry__)}" + flat = [1 for x in Blueprint.__pre_registry__.values() for _ in x] + name = f"bp{len(flat)}" bp = Blueprint( name=name, url_prefix=url_prefix, diff --git a/sanic/blueprints.py b/sanic/blueprints.py index 1c313f6717..b2c1a59cba 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -472,6 +472,9 @@ def pre_register( strict_slashes: Optional[bool] = None, version_prefix: Union[str, Default] = _default, ) -> None: + if not hasattr(self.ctx, "_prereg"): + self.ctx._prereg = [] + self.ctx._prereg.append(name) self.__class__.__pre_registry__[name].append( { k: v diff --git a/sanic/cli/app.py b/sanic/cli/app.py index 3001b6e1fa..65a6504314 100644 --- a/sanic/cli/app.py +++ b/sanic/cli/app.py @@ -10,7 +10,9 @@ from sanic.app import Sanic from sanic.application.logo import get_logo +from sanic.blueprints import Blueprint from sanic.cli.arguments import Group +from sanic.helpers import _default from sanic.log import error_logger from sanic.simple import create_simple_server @@ -20,6 +22,7 @@ class SanicArgumentParser(ArgumentParser): class SanicCLI: + DEFAULT_APP_NAME = "SANIC" DESCRIPTION = indent( f""" {get_logo(True)} @@ -131,7 +134,17 @@ def _get_app(self): app_type_name = type(app).__name__ - if not isinstance(app, Sanic): + if isinstance(app, Blueprint): + bp = app + name = ( + bp.ctx._prereg[0] + if hasattr(bp.ctx, "_prereg") + else _default + ) + if name is _default: + name = self.DEFAULT_APP_NAME + app = Sanic(name) + elif not isinstance(app, Sanic): raise ValueError( f"Module is not a Sanic app, it is a {app_type_name}\n" f" Perhaps you meant {self.args.module}.app?"