The Blueprint Setup Mechanism
The Blueprint mechanism in this codebase follows a "record-and-apply" pattern. Because a Blueprint is often defined before the Flask application object exists, it cannot register routes or error handlers immediately. Instead, it records these operations as deferred functions and applies them later during the registration process using a specialized state object.
The Record-and-Apply Pattern
The Blueprint class (found in src/flask/sansio/blueprints.py) acts as a recorder. When you use decorators like @bp.route or @bp.errorhandler, the blueprint does not modify the application's routing table. Instead, it wraps the operation in a lambda or a local function and stores it in self.deferred_functions.
The Recording Phase
The record method is the primary way operations are queued. For example, the add_url_rule implementation in Blueprint does not register the rule directly; it records a call to the setup state's version of the method:
@setupmethod
def add_url_rule(
self,
rule: str,
endpoint: str | None = None,
view_func: ft.RouteCallable | None = None,
provide_automatic_options: bool | None = None,
**options: t.Any,
) -> None:
# ... validation logic ...
self.record(
lambda s: s.add_url_rule(
rule,
endpoint,
view_func,
provide_automatic_options=provide_automatic_options,
**options,
)
)
This ensures that the final URL rule and endpoint name are only determined when the blueprint is actually attached to an application, allowing for dynamic prefixes and names.
The Registration Process
Registration occurs when app.register_blueprint(bp) is called, which triggers Blueprint.register(app, options). This process involves several distinct steps to transition the blueprint's state into the application.
- Name Resolution: The blueprint determines its unique name within the app, considering any
name_prefixfrom parent blueprints. - State Creation: It calls
make_setup_stateto create an instance ofBlueprintSetupState. - Function Merging: It calls
_merge_blueprint_funcsto copy error handlers, view functions, and request hooks (likebefore_request) directly into the application's internal dictionaries. - Deferred Execution: It iterates through
self.deferred_functions, calling each one with theBlueprintSetupStateinstance. - Nested Blueprints: It recursively calls
registeron any blueprints nested within it.
Consistency Checks
To prevent inconsistent application state, the blueprint tracks if it has been registered using _got_registered_once. Any attempt to call a @setupmethod (like route or record) after registration will raise an AssertionError.
def _check_setup_finished(self, f_name: str) -> None:
if self._got_registered_once:
raise AssertionError(
f"The setup method '{f_name}' can no longer be called on the blueprint"
f" '{self.name}'. It has already been registered at least once..."
)
The Blueprint Setup State
The BlueprintSetupState class is a temporary object that lives only during the registration process. It serves as the bridge between the generic instructions recorded on the Blueprint and the specific requirements of the App.
Its primary responsibility is resolving the final configuration for routes. When BlueprintSetupState.add_url_rule is called, it performs the following:
- URL Joining: It prepends the registration's
url_prefixto the rule. - Endpoint Prefixing: It constructs the final endpoint name by combining the
name_prefix, the blueprint'sname, and the localendpoint. - Subdomain Application: It applies the subdomain specified during registration or defined on the blueprint.
def add_url_rule(
self,
rule: str,
endpoint: str | None = None,
view_func: ft.RouteCallable | None = None,
**options: t.Any,
) -> None:
if self.url_prefix is not None:
if rule:
rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/")))
else:
rule = self.url_prefix
# ...
self.app.add_url_rule(
rule,
f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."),
view_func,
defaults=defaults,
**options,
)
Advanced Setup Mechanisms
The record_once Mechanism
Some operations, such as registering template filters or globals, should only happen once even if a blueprint is registered multiple times (e.g., with different URL prefixes). The record_once method wraps a deferred function to check the state.first_registration flag:
@setupmethod
def record_once(self, func: DeferredSetupFunction) -> None:
def wrapper(state: BlueprintSetupState) -> None:
if state.first_registration:
func(state)
self.record(update_wrapper(wrapper, func))
Nested Blueprints and Accumulation
When blueprints are nested via Blueprint.register_blueprint, the parent blueprint stores the child in self._blueprints. During the parent's registration, it calculates the cumulative url_prefix and subdomain for the child:
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
This allows for deeply nested structures where each level contributes to the final routing path and endpoint namespacing.