Compare commits

...

4 Commits

Author SHA1 Message Date
Adam Hopkins
c8fa52e2d2
Allow application creation from Blueprint at startup 2021-12-15 09:36:40 +02:00
Adam Hopkins
266af1e279
Allow empty lazy, and multiple pre-registrations 2021-12-15 09:12:56 +02:00
Adam Hopkins
6d3f1e9982
Add lazy classmethod 2021-12-15 01:44:56 +02:00
Adam Hopkins
a0a3840094
Add pre-registry 2021-12-15 01:07:42 +02:00
3 changed files with 94 additions and 1 deletions

View File

@ -67,6 +67,7 @@ from sanic.exceptions import (
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
@ -536,6 +537,30 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
blueprint.strict_slashes = self.strict_slashes
blueprint.register(self, options)
def _register_lazy_blueprints(self):
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."
)
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()
}
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.
@ -1677,6 +1702,32 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
return cls(name)
raise SanicException(f'Sanic app name "{name}" not found.')
@classmethod
def lazy(
cls,
app_name: Union[str, Default] = _default,
*,
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:
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,
host=host,
version=version,
strict_slashes=strict_slashes,
version_prefix=version_prefix,
)
bp.pre_register(app_name)
return bp
# -------------------------------------------------------------------- #
# Lifecycle
# -------------------------------------------------------------------- #
@ -1697,6 +1748,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
async def _startup(self):
self._future_registry.clear()
self._register_lazy_blueprints()
self.signalize()
self.finalize()
ErrorHandler.finalize(

View File

@ -107,6 +107,7 @@ class Blueprint(BaseSanic):
"version_prefix",
"websocket_routes",
)
__pre_registry__: Dict[Union[str, Default], Any] = defaultdict(list)
def __init__(
self,
@ -461,3 +462,30 @@ class Blueprint(BaseSanic):
):
for app in apps:
app._future_registry.update(set((bp, item) for item in futures))
def pre_register(
self,
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:
if not hasattr(self.ctx, "_prereg"):
self.ctx._prereg = []
self.ctx._prereg.append(name)
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
}
)

View File

@ -10,7 +10,9 @@ from typing import Any, List, Union
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 @@ Or, a path to a directory to run as a simple HTTP server:
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?"