Advanced Route Customization
Advanced route customization in this project is primarily achieved through the APIRoute class, which serves as the fundamental building block for path operations. While standard decorators like @app.get() or @app.post() handle most use cases, APIRoute provides hooks for deep integration into the request-response lifecycle, OpenAPI schema generation, and protocol-level validation.
Customizing Request Handling
The most powerful way to customize how requests are processed is by subclassing APIRoute and overriding the get_route_handler method. This method is responsible for returning the actual ASGI application (a callable) that handles the route.
By overriding this, you can wrap the original handler to inject custom logic, such as decompressing request bodies or logging specific metadata before the dependency injection system even starts.
As seen in docs_src/custom_request_and_route/tutorial001_py310.py, a common pattern is to create a custom Request subclass and then use a custom APIRoute to ensure that this subclass is used:
class GzipRequest(Request):
async def body(self) -> bytes:
if not hasattr(self, "_body"):
body = await super().body()
if "gzip" in self.headers.getlist("Content-Encoding"):
body = gzip.decompress(body)
self._body = body
return self._body
class GzipRoute(APIRoute):
def get_route_handler(self) -> Callable:
original_route_handler = super().get_route_handler()
async def custom_route_handler(request: Request) -> Response:
# Wrap the standard request with our custom GzipRequest
request = GzipRequest(request.scope, request.receive)
return await original_route_handler(request)
return custom_route_handler
app = FastAPI()
# Apply the custom route class to all routes in the router
app.router.route_class = GzipRoute
This design choice allows developers to modify the request object globally or per-router without changing the individual path operation functions. The tradeoff is that you must ensure the custom_route_handler correctly handles the ASGI scope and receive/send channels, typically by calling the original_route_handler.
Unique ID Generation
The unique_id of an APIRoute is a critical internal identifier. It is used for:
- The
operationIdin the generated OpenAPI schema. - Naming internal Pydantic models (e.g.,
Body_unique_idorResponse_unique_id).
By default, FastAPI uses the function name as the basis for the unique ID. However, in large projects with multiple routers, name collisions can occur. To solve this, you can provide a generate_unique_id_function to the FastAPI app, an APIRouter, or an individual APIRoute.
In tests/test_generate_unique_id_function.py, the implementation demonstrates how to prefix operation IDs to ensure uniqueness across different modules:
def custom_generate_unique_id(route: APIRoute):
return f"foo_{route.name}"
app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
When this function is set, the APIRoute.__init__ method (found in fastapi/routing.py) uses it to populate self.unique_id:
self.unique_id = self.operation_id or current_generate_unique_id(self)
If an operation_id is explicitly provided in the decorator, it takes precedence over the generated ID.
Strict Content-Type Checking
By default, this implementation enforces strict content-type checking for JSON requests. If a client sends a request body intended for a JSON-compatible endpoint but fails to include the Content-Type: application/json header, the application will return a 422 Unprocessable Entity error.
This behavior is controlled by the strict_content_type parameter in APIRoute. While the default is True, it can be disabled at the application level to support legacy clients or simplified testing environments.
As demonstrated in tests/test_strict_content_type_app_level.py, disabling this allows the server to attempt parsing the body as JSON even if the header is missing:
# Disabling strict checking at the app level
app_lax = FastAPI(strict_content_type=False)
@app_lax.post("/items/")
async def app_lax_post(data: dict):
return data
In fastapi/routing.py, this flag is passed into the get_request_handler function within get_route_handler. When strict_content_type is False, the internal logic becomes more permissive, which can be useful for interoperability but may lead to unexpected behavior if non-JSON data is sent to an endpoint expecting a structured body.