forked from orbit-oss/flask
Nested blueprints
This allows blueprints to be nested within blueprints via a new Blueprint.register_blueprint method. This should provide a use case that has been desired for the past ~10 years. This works by setting the endpoint name to be the blueprint names, from parent to child delimeted by "." and then iterating over the blueprint names in reverse order in the app (from most specific to most general). This means that the expectation of nesting a blueprint within a nested blueprint is met.
This commit is contained in:
parent
85dce2c836
commit
f92e820b4b
5 changed files with 154 additions and 56 deletions
|
|
@ -723,9 +723,9 @@ class Flask(Scaffold):
|
|||
funcs = self.template_context_processors[None]
|
||||
reqctx = _request_ctx_stack.top
|
||||
if reqctx is not None:
|
||||
bp = reqctx.request.blueprint
|
||||
if bp is not None and bp in self.template_context_processors:
|
||||
funcs = chain(funcs, self.template_context_processors[bp])
|
||||
for bp in self._request_blueprints():
|
||||
if bp in self.template_context_processors:
|
||||
funcs = chain(funcs, self.template_context_processors[bp])
|
||||
orig_ctx = context.copy()
|
||||
for func in funcs:
|
||||
context.update(func())
|
||||
|
|
@ -987,21 +987,7 @@ class Flask(Scaffold):
|
|||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
first_registration = False
|
||||
|
||||
if blueprint.name in self.blueprints:
|
||||
assert self.blueprints[blueprint.name] is blueprint, (
|
||||
"A name collision occurred between blueprints"
|
||||
f" {blueprint!r} and {self.blueprints[blueprint.name]!r}."
|
||||
f" Both share the same name {blueprint.name!r}."
|
||||
f" Blueprints that are created on the fly need unique"
|
||||
f" names."
|
||||
)
|
||||
else:
|
||||
self.blueprints[blueprint.name] = blueprint
|
||||
first_registration = True
|
||||
|
||||
blueprint.register(self, options, first_registration)
|
||||
blueprint.register(self, options)
|
||||
|
||||
def iter_blueprints(self):
|
||||
"""Iterates over all blueprints by the order they were registered.
|
||||
|
|
@ -1235,22 +1221,18 @@ class Flask(Scaffold):
|
|||
"""
|
||||
exc_class, code = self._get_exc_class_and_code(type(e))
|
||||
|
||||
for name, c in (
|
||||
(request.blueprint, code),
|
||||
(None, code),
|
||||
(request.blueprint, None),
|
||||
(None, None),
|
||||
):
|
||||
handler_map = self.error_handler_spec[name][c]
|
||||
for c in [code, None]:
|
||||
for name in chain(self._request_blueprints(), [None]):
|
||||
handler_map = self.error_handler_spec[name][c]
|
||||
|
||||
if not handler_map:
|
||||
continue
|
||||
if not handler_map:
|
||||
continue
|
||||
|
||||
for cls in exc_class.__mro__:
|
||||
handler = handler_map.get(cls)
|
||||
for cls in exc_class.__mro__:
|
||||
handler = handler_map.get(cls)
|
||||
|
||||
if handler is not None:
|
||||
return handler
|
||||
if handler is not None:
|
||||
return handler
|
||||
|
||||
def handle_http_exception(self, e):
|
||||
"""Handles an HTTP exception. By default this will invoke the
|
||||
|
|
@ -1749,17 +1731,17 @@ class Flask(Scaffold):
|
|||
further request handling is stopped.
|
||||
"""
|
||||
|
||||
bp = _request_ctx_stack.top.request.blueprint
|
||||
|
||||
funcs = self.url_value_preprocessors[None]
|
||||
if bp is not None and bp in self.url_value_preprocessors:
|
||||
funcs = chain(funcs, self.url_value_preprocessors[bp])
|
||||
for bp in self._request_blueprints():
|
||||
if bp in self.url_value_preprocessors:
|
||||
funcs = chain(funcs, self.url_value_preprocessors[bp])
|
||||
for func in funcs:
|
||||
func(request.endpoint, request.view_args)
|
||||
|
||||
funcs = self.before_request_funcs[None]
|
||||
if bp is not None and bp in self.before_request_funcs:
|
||||
funcs = chain(funcs, self.before_request_funcs[bp])
|
||||
for bp in self._request_blueprints():
|
||||
if bp in self.before_request_funcs:
|
||||
funcs = chain(funcs, self.before_request_funcs[bp])
|
||||
for func in funcs:
|
||||
rv = func()
|
||||
if rv is not None:
|
||||
|
|
@ -1779,10 +1761,10 @@ class Flask(Scaffold):
|
|||
instance of :attr:`response_class`.
|
||||
"""
|
||||
ctx = _request_ctx_stack.top
|
||||
bp = ctx.request.blueprint
|
||||
funcs = ctx._after_request_functions
|
||||
if bp is not None and bp in self.after_request_funcs:
|
||||
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
|
||||
for bp in self._request_blueprints():
|
||||
if bp in self.after_request_funcs:
|
||||
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
|
||||
if None in self.after_request_funcs:
|
||||
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
|
||||
for handler in funcs:
|
||||
|
|
@ -1815,9 +1797,9 @@ class Flask(Scaffold):
|
|||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
funcs = reversed(self.teardown_request_funcs[None])
|
||||
bp = _request_ctx_stack.top.request.blueprint
|
||||
if bp is not None and bp in self.teardown_request_funcs:
|
||||
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
||||
for bp in self._request_blueprints():
|
||||
if bp in self.teardown_request_funcs:
|
||||
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
||||
for func in funcs:
|
||||
func(exc)
|
||||
request_tearing_down.send(self, exc=exc)
|
||||
|
|
@ -1985,3 +1967,9 @@ class Flask(Scaffold):
|
|||
wrapped to apply middleware.
|
||||
"""
|
||||
return self.wsgi_app(environ, start_response)
|
||||
|
||||
def _request_blueprints(self):
|
||||
if _request_ctx_stack.top.request.blueprint is None:
|
||||
return []
|
||||
else:
|
||||
return reversed(_request_ctx_stack.top.request.blueprint.split("."))
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ class BlueprintSetupState:
|
|||
#: blueprint.
|
||||
self.url_prefix = url_prefix
|
||||
|
||||
self.name_prefix = self.options.get("name_prefix", "")
|
||||
|
||||
#: A dictionary with URL defaults that is added to each and every
|
||||
#: URL that was defined with the blueprint.
|
||||
self.url_defaults = dict(self.blueprint.url_values_defaults)
|
||||
|
|
@ -68,7 +70,7 @@ class BlueprintSetupState:
|
|||
defaults = dict(defaults, **options.pop("defaults"))
|
||||
self.app.add_url_rule(
|
||||
rule,
|
||||
f"{self.blueprint.name}.{endpoint}",
|
||||
f"{self.name_prefix}{self.blueprint.name}.{endpoint}",
|
||||
view_func,
|
||||
defaults=defaults,
|
||||
**options,
|
||||
|
|
@ -168,6 +170,7 @@ class Blueprint(Scaffold):
|
|||
|
||||
self.url_values_defaults = url_defaults
|
||||
self.cli_group = cli_group
|
||||
self._blueprints = []
|
||||
|
||||
def _is_setup_finished(self):
|
||||
return self.warn_on_modifications and self._got_registered_once
|
||||
|
|
@ -210,7 +213,16 @@ class Blueprint(Scaffold):
|
|||
"""
|
||||
return BlueprintSetupState(self, app, options, first_registration)
|
||||
|
||||
def register(self, app, options, first_registration=False):
|
||||
def register_blueprint(self, blueprint, **options):
|
||||
"""Register a :class:`~flask.Blueprint` on this blueprint. Keyword
|
||||
arguments passed to this method will override the defaults set
|
||||
on the blueprint.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
self._blueprints.append((blueprint, options))
|
||||
|
||||
def register(self, app, options):
|
||||
"""Called by :meth:`Flask.register_blueprint` to register all
|
||||
views and callbacks registered on the blueprint with the
|
||||
application. Creates a :class:`.BlueprintSetupState` and calls
|
||||
|
|
@ -223,6 +235,20 @@ class Blueprint(Scaffold):
|
|||
:param first_registration: Whether this is the first time this
|
||||
blueprint has been registered on the application.
|
||||
"""
|
||||
first_registration = False
|
||||
|
||||
if self.name in app.blueprints:
|
||||
assert app.blueprints[self.name] is self, (
|
||||
"A name collision occurred between blueprints"
|
||||
f" {self!r} and {app.blueprints[self.name]!r}."
|
||||
f" Both share the same name {self.name!r}."
|
||||
f" Blueprints that are created on the fly need unique"
|
||||
f" names."
|
||||
)
|
||||
else:
|
||||
app.blueprints[self.name] = self
|
||||
first_registration = True
|
||||
|
||||
self._got_registered_once = True
|
||||
state = self.make_setup_state(app, options, first_registration)
|
||||
|
||||
|
|
@ -278,19 +304,28 @@ class Blueprint(Scaffold):
|
|||
for deferred in self.deferred_functions:
|
||||
deferred(state)
|
||||
|
||||
if not self.cli.commands:
|
||||
return
|
||||
|
||||
cli_resolved_group = options.get("cli_group", self.cli_group)
|
||||
|
||||
if cli_resolved_group is None:
|
||||
app.cli.commands.update(self.cli.commands)
|
||||
elif cli_resolved_group is _sentinel:
|
||||
self.cli.name = self.name
|
||||
app.cli.add_command(self.cli)
|
||||
else:
|
||||
self.cli.name = cli_resolved_group
|
||||
app.cli.add_command(self.cli)
|
||||
if self.cli.commands:
|
||||
if cli_resolved_group is None:
|
||||
app.cli.commands.update(self.cli.commands)
|
||||
elif cli_resolved_group is _sentinel:
|
||||
self.cli.name = self.name
|
||||
app.cli.add_command(self.cli)
|
||||
else:
|
||||
self.cli.name = cli_resolved_group
|
||||
app.cli.add_command(self.cli)
|
||||
|
||||
for blueprint, bp_options in self._blueprints:
|
||||
url_prefix = options.get("url_prefix", "")
|
||||
if "url_prefix" in bp_options:
|
||||
url_prefix = (
|
||||
url_prefix.rstrip("/") + "/" + bp_options["url_prefix"].lstrip("/")
|
||||
)
|
||||
|
||||
bp_options["url_prefix"] = url_prefix
|
||||
bp_options["name_prefix"] = options.get("name_prefix", "") + self.name + "."
|
||||
blueprint.register(app, bp_options)
|
||||
|
||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
||||
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue