Skip to main content

Extending Request and Response Classes

By subclassing the default request and response wrappers, you can inject custom logic and properties into every request handled by your application. This is particularly useful for adding helper methods to the request object or ensuring every response includes specific headers.

In this tutorial, you will build a Flask application that uses a custom Request class to identify internal traffic and a custom Response class that automatically attaches a version header to every outgoing response.

Prerequisites

To follow this tutorial, you need the following installed:

  • flask (the core package containing flask.wrappers)
  • werkzeug (the underlying library for request/response handling)

Step 1: Create a Custom Request Class

The Request class in flask.wrappers is the default object used for flask.request. You can extend it to add properties that simplify your view logic.

Create a file named app_wrappers.py and define your custom request:

from flask.wrappers import Request
from flask import current_app

class CustomRequest(Request):
@property
def is_internal(self) -> bool:
"""Check if the request is coming from a trusted internal IP."""
# You can access the environment via self.environ
# or use built-in Werkzeug attributes like remote_addr
trusted_ips = {"127.0.0.1", "10.0.0.1"}
return self.remote_addr in trusted_ips

@property
def max_content_length(self) -> int | None:
"""Override to provide a dynamic limit based on the endpoint."""
if self.endpoint == "upload_large_file":
return 100 * 1024 * 1024 # 100MB for specific view

# Fallback to default behavior which uses current_app.config
return super().max_content_length

By overriding max_content_length, you are tapping into the same mechanism Flask uses internally to enforce payload limits. The base Request class implementation in src/flask/wrappers.py checks current_app.config["MAX_CONTENT_LENGTH"] by default; here, you've added a conditional override.

Step 2: Create a Custom Response Class

Similarly, you can subclass Response to modify how responses are initialized or formatted.

Add the following to app_wrappers.py:

import typing as t
from flask.wrappers import Response

class CustomResponse(Response):
def __init__(
self,
response: t.Any = None,
status: int | str | None = None,
headers: t.Mapping[str, str | t.Iterable[str]] | t.Iterable[t.Tuple[str, str]] | None = None,
mimetype: str | None = None,
content_type: str | None = None,
direct_passthrough: bool = False,
) -> None:
super().__init__(
response=response,
status=status,
headers=headers,
mimetype=mimetype,
content_type=content_type,
direct_passthrough=direct_passthrough,
)
# Automatically add a custom header to every response
self.headers["X-App-Version"] = "1.0.0"

The Response class in flask.wrappers defaults to text/html. By subclassing it, you can also change defaults like default_mimetype or autocorrect_location_header.

Step 3: Register Custom Classes with the Flask App

To make Flask use your subclasses instead of the defaults, you must assign them to the request_class and response_class attributes of your Flask application object.

Create your main application file app.py:

from flask import Flask, jsonify
from app_wrappers import CustomRequest, CustomResponse

app = Flask(__name__)

# Register the custom wrappers
app.request_class = CustomRequest
app.response_class = CustomResponse

@app.route("/status")
def status():
# request is now an instance of CustomRequest
from flask import request

return jsonify({
"internal": request.is_internal,
"endpoint": request.endpoint
})

@app.route("/hello")
def hello():
# This string will be wrapped by CustomResponse
return "Hello, World!"

When the application context is created for a request, Flask looks at app.request_class to instantiate the request object. Likewise, when a view returns a value, app.make_response uses app.response_class to ensure the return value is a proper response object.

Step 4: Verify the Custom Behavior

You can verify that your custom logic is working by using the Flask test client. The test client automatically uses the response_class defined on the app.

def test_custom_wrappers():
with app.test_client() as client:
# Test CustomResponse header
resp = client.get("/hello")
assert resp.headers["X-App-Version"] == "1.0.0"
assert resp.data == b"Hello, World!"

# Test CustomRequest property
# The test client defaults to 127.0.0.1
resp = client.get("/status")
data = resp.get_json()
assert data["internal"] is True

if __name__ == "__main__":
test_custom_wrappers()
print("All tests passed!")

Summary of Implementation

  • Request: Subclassing allows you to add domain-specific properties (like is_internal) and override configuration-driven attributes (like max_content_length).
  • Response: Subclassing is the standard way to enforce application-wide response headers or change the default mimetype.
  • Integration: Set app.request_class and app.response_class immediately after instantiating the Flask object to ensure they are used for all subsequent requests.

For more advanced customizations, you can also override on_json_loading_failed in your Request subclass to change how the application handles malformed JSON payloads.