Polishing Inputs: Advanced Validation and Metadata
Polishing Inputs: Advanced Validation and Metadata
Section titled “Polishing Inputs: Advanced Validation and Metadata”In previous chapters, we relied on basic Python types like str and int to validate data. However, real-world applications require stricter rules. A username might need to be at least 3 characters long, or a product ID must be a positive number. In this chapter, we will unlock the power of Query, Path, and Body to enforce these constraints and enrich our API documentation with metadata.
Validation with Query Parameters
Section titled “Validation with Query Parameters”FastAPI allows you to declare additional validation for parameters using the Query function. To use it, we will leverage the modern Python Annotated syntax.
Imagine a search endpoint where the query string q is optional but, if provided, must be no longer than 50 characters and no shorter than 3 characters.
from typing import Annotatedfrom fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")async def read_items( q: Annotated[str | None, Query(min_length=3, max_length=50)] = None): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) return resultsWe can even enforce regular expressions using the pattern parameter. For example, to ensure a string matches a specific format like a fixed-size code:
# Ensures 'q' is exactly 3 digits if providedq: Annotated[str | None, Query(pattern="^\\d{3}$")] = NonePath Parameters and Numeric Validation
Section titled “Path Parameters and Numeric Validation”Just as Query handles query parameters, Path handles path parameters. Since path parameters are always required (they are part of the URL route), we often use Path to validate numeric ranges.
Common validators include:
- gt: Greater than
- ge: Greater than or equal to
- lt: Less than
- le: Less than or equal to
from fastapi import Path
@app.get("/items/{item_id}")async def read_items( item_id: Annotated[int, Path(title="The ID of the item", ge=1)], q: Annotated[str | None, Query(alias="item-query")] = None,): return {"item_id": item_id, "q": q}In this example, if a user tries to access /items/0 or /items/-5, FastAPI will automatically reject the request with a validation error because item_id must be greater than or equal to 1.
Multiple Body Parameters
Section titled “Multiple Body Parameters”Sometimes you want to pass a single value in the request body (like a simple string) rather than a full JSON object defined by a Pydantic model. By default, FastAPI assumes simple types are Query parameters. To instruct FastAPI to read them from the JSON body, we use Body.
from fastapi import Body
@app.put("/items/{item_id}")async def update_item( item_id: int, item: Item, # A Pydantic model importance: Annotated[int, Body()] # A single value from the body): return {"item_id": item_id, "item": item, "importance": importance}FastAPI will expect a JSON body that looks like this:
{ "item": { "name": "Foo", "price": 45.2 }, "importance": 5}Adding Metadata for Documentation
Section titled “Adding Metadata for Documentation”One of FastAPI’s best features is the automatic documentation. Using Query, Path, and Body, you can add metadata that appears directly in the Swagger UI and ReDoc.
- title: A concise title for the parameter.
- description: A detailed explanation of what the parameter does. Markdown is supported!
- alias: Useful if the parameter name in the URL needs to be different from your Python variable (e.g.,
item-queryvsitem_query). - deprecated: Set to
Trueto mark a parameter as deprecated in the docs without breaking the API.
q: Annotated[ str | None, Query( title="Query String", description="Query string for the items to search in the database that have a good match", min_length=3, deprecated=True )] = NoneBy adding these details, you turn your code into self-documenting specifications, making life easier for frontend developers and API consumers.