Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 102 additions & 76 deletions src/flask/sansio/blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,43 +275,13 @@ def register(self, app: App, options: dict[str, t.Any]) -> None:
views and callbacks registered on the blueprint with the
application. Creates a :class:`.BlueprintSetupState` and calls
each :meth:`record` callback with it.

:param app: The application this blueprint is being registered
with.
:param options: Keyword arguments forwarded from
:meth:`~Flask.register_blueprint`.

.. versionchanged:: 2.3
Nested blueprints now correctly apply subdomains.

.. versionchanged:: 2.1
Registering the same blueprint with the same name multiple
times is an error.

.. versionchanged:: 2.0.1
Nested blueprints are registered with their dotted name.
This allows different blueprints with the same name to be
nested at different locations.

.. versionchanged:: 2.0.1
The ``name`` option can be used to change the (pre-dotted)
name the blueprint is registered with. This allows the same
blueprint to be registered multiple times with unique names
for ``url_for``.
...
"""
name_prefix = options.get("name_prefix", "")
self_name = options.get("name", self.name)
name = f"{name_prefix}.{self_name}".lstrip(".")

if name in app.blueprints:
bp_desc = "this" if app.blueprints[name] is self else "a different"
existing_at = f" '{name}'" if self_name != name else ""

raise ValueError(
f"The name '{self_name}' is already registered for"
f" {bp_desc} blueprint{existing_at}. Use 'name=' to"
f" provide a unique name."
)
self._ensure_unique_name(app, self_name, name)

first_bp_registration = not any(bp is self for bp in app.blueprints.values())
first_name_registration = name not in app.blueprints
Expand All @@ -320,61 +290,117 @@ def register(self, app: App, options: dict[str, t.Any]) -> None:
self._got_registered_once = True
state = self.make_setup_state(app, options, first_bp_registration)

if self.has_static_folder:
state.add_url_rule(
f"{self.static_url_path}/<path:filename>",
view_func=self.send_static_file, # type: ignore[attr-defined]
endpoint="static",
)
self._register_static(state)
self._merge_blueprint_funcs_if_needed(
app, name, first_bp_registration, first_name_registration
)
self._run_deferred_functions(state)
self._register_cli(app, name, options)
self._register_nested_blueprints(app, state, name)

# Merge blueprint data into parent.
def _ensure_unique_name(self, app: App, self_name: str, name: str) -> None:
if name not in app.blueprints:
return

bp_desc = "this" if app.blueprints[name] is self else "a different"
existing_at = f" '{name}'" if self_name != name else ""

raise ValueError(
f"The name '{self_name}' is already registered for"
f" {bp_desc} blueprint{existing_at}. Use 'name=' to"
f" provide a unique name."
)

def _register_static(self, state: BlueprintSetupState) -> None:
if not self.has_static_folder:
return

state.add_url_rule(
f"{self.static_url_path}/<path:filename>",
view_func=self.send_static_file, # type: ignore[attr-defined]
endpoint="static",
)

def _merge_blueprint_funcs_if_needed(
self,
app: App,
name: str,
first_bp_registration: bool,
first_name_registration: bool,
) -> None:
if first_bp_registration or first_name_registration:
self._merge_blueprint_funcs(app, name)

def _run_deferred_functions(self, state: BlueprintSetupState) -> None:
for deferred in self.deferred_functions:
deferred(state)

def _register_cli(self, app: App, name: str, options: dict[str, t.Any]) -> None:
if not self.cli.commands:
return

cli_resolved_group = options.get("cli_group", self.cli_group)

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 = name
app.cli.add_command(self.cli)
else:
self.cli.name = cli_resolved_group
app.cli.add_command(self.cli)
if cli_resolved_group is None:
app.cli.commands.update(self.cli.commands)
elif cli_resolved_group is _sentinel:
self.cli.name = name
app.cli.add_command(self.cli)
else:
self.cli.name = cli_resolved_group
app.cli.add_command(self.cli)

def _register_nested_blueprints(
self,
app: App,
state: BlueprintSetupState,
name: str,
) -> None:
for blueprint, bp_options in self._blueprints:
bp_options = bp_options.copy()
bp_url_prefix = bp_options.get("url_prefix")
bp_subdomain = bp_options.get("subdomain")

if bp_subdomain is None:
bp_subdomain = blueprint.subdomain

if state.subdomain is not None and bp_subdomain is not None:
bp_options["subdomain"] = bp_subdomain + "." + state.subdomain
elif bp_subdomain is not None:
bp_options["subdomain"] = bp_subdomain
elif state.subdomain is not None:
bp_options["subdomain"] = state.subdomain

if bp_url_prefix is None:
bp_url_prefix = blueprint.url_prefix

if state.url_prefix is not None and bp_url_prefix is not None:
bp_options["url_prefix"] = (
state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
)
elif bp_url_prefix is not None:
bp_options["url_prefix"] = bp_url_prefix
elif state.url_prefix is not None:
bp_options["url_prefix"] = state.url_prefix

bp_options["name_prefix"] = name
blueprint.register(app, bp_options)
merged_options = self._compute_nested_blueprint_options(
blueprint, state, bp_options, name
)
blueprint.register(app, merged_options)

def _compute_nested_blueprint_options(
self,
blueprint: Blueprint,
state: BlueprintSetupState,
bp_options: dict[str, t.Any],
name: str,
) -> dict[str, t.Any]:
bp_options = bp_options.copy()
bp_options["name_prefix"] = name

# --- subdomain ---
bp_subdomain = bp_options.get("subdomain")

if bp_subdomain is None:
bp_subdomain = blueprint.subdomain

if state.subdomain is not None and bp_subdomain is not None:
bp_options["subdomain"] = bp_subdomain + "." + state.subdomain
elif bp_subdomain is not None:
bp_options["subdomain"] = bp_subdomain
elif state.subdomain is not None:
bp_options["subdomain"] = state.subdomain

# --- url_prefix ---
bp_url_prefix = bp_options.get("url_prefix")

if bp_url_prefix is None:
bp_url_prefix = blueprint.url_prefix

if state.url_prefix is not None and bp_url_prefix is not None:
bp_options["url_prefix"] = (
state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
)
elif bp_url_prefix is not None:
bp_options["url_prefix"] = bp_url_prefix
elif state.url_prefix is not None:
bp_options["url_prefix"] = state.url_prefix

return bp_options

def _merge_blueprint_funcs(self, app: App, name: str) -> None:
def extend(
Expand Down
Loading