Blueprint-Specific vs. Application-Wide Hooks
In this codebase, the Blueprint class (found in src/flask/sansio/blueprints.py) provides a dual-scope system for request processing and error handling. This allows developers to choose whether a hook should be local to the blueprint's routes or affect the entire application.
Blueprint-Scoped Hooks
Blueprint-scoped hooks are inherited from the Scaffold base class. These include methods like @bp.before_request, @bp.after_request, and @bp.errorhandler. When you use these decorators on a blueprint, the functions are initially stored within the blueprint's own data structures.
The scoping is enforced during the registration process. In src/flask/sansio/blueprints.py, the _merge_blueprint_funcs method is responsible for moving these hooks to the application object. It prefixes the keys in the application's hook dictionaries with the blueprint's name:
def _merge_blueprint_funcs(self, app: App, name: str) -> None:
def extend(
bp_dict: dict[ft.AppOrBlueprintKey, list[t.Any]],
parent_dict: dict[ft.AppOrBlueprintKey, list[t.Any]],
) -> None:
for key, values in bp_dict.items():
# If key is None (local to BP), it becomes 'blueprint_name' in the app
key = name if key is None else f"{name}.{key}"
parent_dict[key].extend(values)
# ... merges before_request_funcs, after_request_funcs, etc.
extend(self.before_request_funcs, app.before_request_funcs)
Because these hooks are registered under the blueprint's name, the application only executes them when a request matches a route defined within that specific blueprint.
Application-Wide Hooks (The app_ Prefix)
There are scenarios where a blueprint needs to register logic that applies to every request in the application, regardless of which blueprint handles the route. For this, the Blueprint class provides app_ prefixed decorators:
@bp.before_app_request@bp.after_app_request@bp.teardown_app_request@bp.app_errorhandler@bp.app_context_processor
Unlike the standard hooks, these methods use record_once to register the function directly onto the application's global (None) key. For example, the implementation of before_app_request looks like this:
@setupmethod
def before_app_request(self, f: T_before_request) -> T_before_request:
self.record_once(
lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
)
return f
Practical Use Case: Authentication
A common pattern is using before_app_request in an authentication blueprint to load the current user for every page of the site. This is demonstrated in examples/tutorial/flaskr/auth.py:
bp = Blueprint("auth", __name__, url_prefix="/auth")
@bp.before_app_request
def load_logged_in_user():
user_id = session.get("user_id")
if user_id is None:
g.user = None
else:
g.user = get_db().execute(
"SELECT * FROM user WHERE id = ?", (user_id,)
).fetchone()
Even though this function is defined inside the auth blueprint, it will run before every request to the application, ensuring g.user is always available.
Precedence and Execution Order
When a request is handled, Flask must decide which hooks to run. The execution order generally follows a "specific to general" priority for error handlers, but a "global then specific" order for request processors.
Error Handlers
If an error occurs within a blueprint route, Flask first looks for a handler registered specifically for that blueprint. If none is found, it falls back to the application-wide error handlers. This allows blueprints to provide specialized error pages (e.g., an API blueprint returning JSON errors while the frontend blueprint returns HTML).
Context Processors
Blueprint-specific context processors (registered via @bp.context_processor) only run when a template is rendered from a view within that blueprint. Application-wide processors (registered via @bp.app_context_processor) run for every template rendering in the app.
Implementation via Deferred Functions
Blueprints do not have immediate access to the Flask application object when they are defined. Instead, they use a "recording" mechanism. Methods like record and record_once add functions to a deferred_functions list.
When app.register_blueprint(bp) is called, the application iterates through this list and executes each function, passing it a BlueprintSetupState object which contains a reference to the actual app.
Registration Constraints
Because of this deferred execution, all hooks must be defined before the blueprint is registered. The Blueprint class enforces this with the _check_setup_finished method:
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..."
)
Attempting to add a hook (like @bp.before_request) after app.register_blueprint(bp) has already occurred will result in an AssertionError. This ensures that the application's internal hook dictionaries remain consistent and predictable.