Skip to main content

Request Lifecycle and Middleware Hooks

The request lifecycle in this codebase is managed through the Scaffold class, which serves as the base for both the Flask application and Blueprint objects. Scaffold provides a standardized set of decorators to hook into various stages of request processing, from initial URL parsing to final resource cleanup.

Hook Registration and Storage

The Scaffold class (found in src/flask/sansio/scaffold.py) initializes several internal data structures to store middleware functions. These are organized by "scope," where a scope of None represents application-wide hooks, and a string scope represents hooks specific to a named Blueprint.

# From src/flask/sansio/scaffold.py
self.before_request_funcs: dict[
ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable]
] = defaultdict(list)

self.after_request_funcs: dict[
ft.AppOrBlueprintKey, list[ft.AfterRequestCallable[t.Any]]
] = defaultdict(list)

self.teardown_request_funcs: dict[
ft.AppOrBlueprintKey, list[ft.TeardownCallable]
] = defaultdict(list)

When you use a decorator like @app.before_request, the function is appended to the list associated with the None key. When used on a blueprint, it is associated with that blueprint's name.

Pre-processing Phase

Before a view function is executed, the application performs two pre-processing steps defined in Flask.preprocess_request (in src/flask/app.py).

URL Value Preprocessors

Registered via @Scaffold.url_value_preprocessor, these functions run first. they are typically used to extract common URL parameters (like a language code) and store them in the g object or modify request.view_args before they are passed to the view.

Before Request Hooks

Registered via @Scaffold.before_request, these functions run after URL pre-processing.

Execution Order:

  1. Application-level hooks (scope None).
  2. Blueprint-level hooks (in the order of the blueprint stack).

Early Return Behavior: If any before_request function returns a non-None value, the execution chain stops immediately. The returned value is treated as the response, and the actual view function is never called.

# Example of early return from tests/test_basic.py
@app.before_request
def before_request1():
evts.append(1)

@app.before_request
def before_request2():
evts.append(2)
return "hello" # Further processing stops here

@app.before_request
def before_request3():
evts.append(3) # This will not run

Post-processing Phase

After the view function returns a response, the application enters the post-processing phase via Flask.process_response.

Dynamic Hooks: after_this_request

The after_this_request function (defined in src/flask/ctx.py) allows for registering a one-off hook during the execution of a view. These are stored on the AppContext and are the first post-processing hooks to execute.

# Example from tests/test_basic.py
@app.route("/")
def index():
@flask.after_this_request
def add_custom_header(response):
response.headers["X-Foo"] = "bar"
return response
return "Hello"

After Request Hooks

Registered via @Scaffold.after_request, these functions modify the response object.

Execution Order:

  1. after_this_request hooks.
  2. Blueprint-level hooks (in reverse order of registration).
  3. Application-level hooks (in reverse order of registration).

Unlike before_request, these functions must accept a response object and must return a response object (either the same one or a new one).

Teardown Phase

The teardown phase is the final stage of the lifecycle, triggered by Flask.do_teardown_request when the request context is popped.

Teardown Request Hooks

Registered via @Scaffold.teardown_request, these functions are designed for resource cleanup (e.g., closing database connections).

Key Characteristics:

  • Reliability: They are called even if an unhandled exception occurs during the request or if a before_request hook returned early.
  • Exception Handling: If an exception occurred, it is passed to the teardown function as an argument.
  • Reverse Order: They execute from the blueprint level up to the application level, in reverse order of registration.
# From src/flask/app.py: do_teardown_request
for name in chain(ctx.request.blueprints, (None,)):
if name in self.teardown_request_funcs:
for func in reversed(self.teardown_request_funcs[name]):
with collect_errors:
self.ensure_sync(func)(exc)

Execution Flow Summary

The following sequence represents the standard execution flow for a request handled by a blueprint:

  1. url_value_preprocessor (App)
  2. url_value_preprocessor (Blueprint)
  3. before_request (App)
  4. before_request (Blueprint)
  5. View Function
  6. after_this_request (Dynamic)
  7. after_request (Blueprint, reverse registration order)
  8. after_request (App, reverse registration order)
  9. teardown_request (Blueprint, reverse registration order)
  10. teardown_request (App, reverse registration order)