Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PROPOSAL] Add pre-registry #2339

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
52 changes: 52 additions & 0 deletions sanic/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -536,6 +537,30 @@ def blueprint(
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.

Expand Down Expand Up @@ -1677,6 +1702,32 @@ def get_app(
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
# -------------------------------------------------------------------- #
Expand All @@ -1697,6 +1748,7 @@ def signalize(self):

async def _startup(self):
self._future_registry.clear()
self._register_lazy_blueprints()
self.signalize()
self.finalize()
ErrorHandler.finalize(
Expand Down
28 changes: 28 additions & 0 deletions sanic/blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class Blueprint(BaseSanic):
"version_prefix",
"websocket_routes",
)
__pre_registry__: Dict[Union[str, Default], Any] = defaultdict(list)

def __init__(
self,
Expand Down Expand Up @@ -461,3 +462,30 @@ def register_futures(
):
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
}
)
15 changes: 14 additions & 1 deletion sanic/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -20,6 +22,7 @@ class SanicArgumentParser(ArgumentParser):


class SanicCLI:
DEFAULT_APP_NAME = "SANIC"
DESCRIPTION = indent(
f"""
{get_logo(True)}
Expand Down Expand Up @@ -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?"
Expand Down