Defining Path Operations
In this codebase, path operations are the core building blocks of an API. They define how the application responds to specific HTTP methods at specific URL paths. This is primarily achieved using the APIRouter class and its associated decorators.
The Role of APIRouter
The APIRouter class (found in fastapi/routing.py) is used to group path operations. It allows you to organize your application into multiple files and modules, which can then be included in the main FastAPI application.
When you define a path operation on a router, you are essentially registering a function (the "endpoint") to be called when a request matches a specific path and HTTP method.
from fastapi import APIRouter
router = APIRouter()
@router.get("/items/")
async def read_items():
return [{"name": "Item 1"}, {"name": "Item 2"}]
HTTP Method Decorators
APIRouter provides several decorator methods that correspond to standard HTTP methods. Each of these methods internally calls self.api_route, which eventually creates an instance of APIRoute.
Available decorators include:
@router.get()@router.post()@router.put()@router.delete()@router.patch()@router.options()@router.head()@router.trace()
Configuration Parameters
These decorators accept a wide range of parameters to configure the behavior and documentation of the path operation:
- path: The URL path (e.g.,
"/items/{item_id}"). - status_code: The default HTTP status code for the response (e.g.,
201for creation). - response_model: A Pydantic model or type used for response serialization, validation, and OpenAPI schema generation.
- tags: A list of strings used to group operations in the generated OpenAPI UI.
- dependencies: A list of dependencies (using
Depends()) that must be executed before the endpoint.
Configuring Responses
The way an endpoint returns data is controlled by response_model, status_code, and response_class.
Status Codes
You can define the default status code for a successful response. If the status code is one that should not have a body (like 204 No Content), APIRoute ensures this is enforced.
@router.post("/items/", status_code=201)
async def create_item(name: str):
return {"name": name}
Response Models
The response_model parameter is critical for data integrity. When provided, APIRoute creates a response_field (a Pydantic ModelField) to:
- Validate the data returned by your function.
- Serialize the data into JSON.
- Filter the data based on the model definition (e.g., excluding internal fields).
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
@router.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
# Even if this dict has extra fields, only 'name' and 'description'
# will be returned to the client based on the Item model.
return {"name": "Portal Gun", "id": item_id, "internal_id": "secret"}
Modular Routing with APIRouter
For larger applications, APIRouter supports shared configuration that applies to all routes defined within it. This is demonstrated in docs_src/bigger_applications/app_an_py310/routers/items.py:
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
Path Prefixes
The prefix parameter automatically prepends a string to all paths in the router.
- Requirement: Prefixes must start with
/and must not end with/. - Internal Logic: In
APIRouter.add_api_route, the final path is calculated asself.prefix + path.
Shared Dependencies and Tags
Dependencies and tags defined at the APIRouter level are merged with those defined at the individual path operation level. In APIRouter.add_api_route, the router's tags and dependencies are copied and extended with the specific route's configuration before being passed to the APIRoute constructor.
Internal Implementation: APIRoute
While APIRouter is the interface for developers, APIRoute is the internal class that manages the execution logic.
When an APIRoute is initialized:
- It compiles the path into a regex for matching.
- It extracts the endpoint's docstring to use as a default
description. - It creates a
dependantobject (viaget_dependant) to manage dependency injection for that specific endpoint. - It determines the
response_class. If not explicitly provided, it defaults toJSONResponse.
This separation allows APIRouter to act as a factory, while APIRoute handles the complexities of request/response processing for a single path.