FastAPIpath-query-parameters detailed explanation

📂 Daoman PythonAI · FastAPI foundation building column | Stage 1 🔗 Pre-chapters: fastapi-intro-advantages · environment-setup · 前置Pydantic入门

If you are developing a mini e-commerce API, users may access addresses like this: /categories/electronics/items/123?q=iPhone&min_price=1999
At the same time, the administrator will also send a piece of JSON via POST to update the product data.

How does FastAPI automatically capture these parameters in milliseconds, convert types accurately, and reject data immediately when it is unqualified? This article focuses on path parameters, query parameters, and request body parameters. In conjunction with the latest Pydantic V2, it explains the core knowledge of FastAPI parameter processing in one go.

Table of contents

Three core parameter types

One of the biggest charms of FastAPI is: as long as you use Python type annotation to write the parameter type clearly, FastAPI will automatically complete the following three things for you:

  1. Know where this parameter is in the request (path, query, request body...)
  2. Convert the string into the corresponding Python type (such asintfloatbool
  3. Generate beautiful automatic documentation (visit/docscan be seen)

Let’s break it down one by one.

Path parameters

Path parameters are dynamic parts embedded in the URL path, usually used to locate a unique resource. for example/items/123inside123It's the product ID.

Basic usage + automatic type conversion

from fastapi import FastAPI

app = FastAPI()

# item_id 会被自动转换成 int
@app.get("/items/{item_id}")
def get_item(item_id: int):
    return {"item_id": item_id, "item_type": type(item_id).__name__}
  • Visit/items/123item_idWhat you get is an integer123
  • Visit/items/abc→ FastAPI will directly return 422 error because it cannot be converted to int

This saves you the need to manuallyint()And the process of catching exceptions is clean and safe.

Use enumerations to limit optional values

When path parameters can only be chosen from a few fixed values, use Python'sEnummatchstrTypes can both constrain values ​​and present clear options in documents.

from enum import Enum

class Category(str, Enum):
    ELECTRONICS = "electronics"
    BOOKS = "books"
    CLOTHING = "clothing"

@app.get("/categories/{category}/items/")
def get_category_items(category: Category):
    # 传入的值必须属于枚举成员,否则返回 422
    return {"category": category.value, "msg": f"获取{category.name}类商品"}

access/categories/electronics/items/correct,/categories/cars/items/An error will be reported directly.

Special path: match content with slashes

Sometimes you need to have a path parameter contain/, such as file path/files/home/user/report.pdf, then you can use{param:path}model:

@app.get("/files/{file_path:path}")
def read_file(file_path: str):
    return {"requested_file": file_path}

This whole/home/user/report.pdfwill be regarded asfile_pathvalue.

Query parameters

Query parameters are in the URL?The bunch of key-value pairs at the back are usually used for filtering, sorting, and paging, and will not change the location of the resource.

The default value determines whether it is optional

Among the function parameters, FastAPI will automatically recognize any parameters that are not placed in path curly braces as query parameters. Parameters with default values ​​are optional, and parameters without default values ​​are required.

from typing import Optional

@app.get("/items/")
def get_items(
    q: Optional[str] = None,
    skip: int = 0,
    limit: int = 10
):
    return {"q": q, "skip": skip, "limit": limit}
  • Visit/items/?q=phone&skip=20&limit=5→ All three parameters are available
  • Visit/items/qforNoneskipfor0limitfor10, the request was successful

Tip: Starting from FastAPI 0.95, it is recommended to useAnnotatedExplicitly declare required or optional for better readability.

Advanced scenario: list parameters and aliases

Query parameters often need to pass multiple values, or the parameter name is different from the Python variable name, then you can findAnnotated + Queryconvenience.

from fastapi import Query
from typing import Annotated, List, Optional

@app.get("/items/advanced/")
def get_advanced_items(
    # 可以多次传同一个参数:/items/advanced/?tags=phone&tags=apple
    tags: Annotated[List[str], Query(description="商品标签")] = [],
    # 别名:URL 传 ?cat=electronics,Python 里用 category 接收
    category: Annotated[Optional[str], Query(alias="cat")] = None
):
    return {"tags": tags, "category": category}

tagsMultiple identical parameter values ​​received will be automatically packed into a list.aliasThis solves the problem of inconsistency between "external naming" and "internal naming".

Request body parameters

When the amount of data is large and the structure is complex, JSON is usually sent through the request body of POST and PUT requests. FastAPI requires using Pydantic V2's BaseModel to define the structure of the request body, so that parsing, verification, and type conversion can be automatically completed.

Basic model definition

from pydantic import BaseModel
from typing import Optional

class Item(BaseModel):
    name: str
    price: float
    description: Optional[str] = None
    tax: Optional[float] = None

@app.post("/items/")
def create_item(item: Item):
    # item 已经是校验后的 Pydantic 对象
    item_dict = item.model_dump()  # Pydantic V2 用 model_dump 代替 dict()
    if item.tax:
        item_dict["total"] = item.price + item.tax
    return item_dict

Once the request body does not meet the model requirements, FastAPI will immediately return a 422 error with a detailed verification failure reason.

Nested models: dealing with complex structures

Real business data is often layered within one layer, and you can directly reference one model to another.

class StockInfo(BaseModel):
    warehouse_id: int
    quantity: int

class NestedItem(Item):
    stock: StockInfo

@app.post("/items/nested/")
def create_nested_item(item: NestedItem):
    return item

This is like building blocks. You can add as many layers as you want, and FastAPI can automatically verify it.

FastAPI's verification system is 100% built on Pydantic V2. Paths and query parameters can be usedPathQueryWait for the decorator to set the rules; the request body parameters are completely constrained by the Pydantic model. The two writing methods are very similar to each other.

Parameter level verification: Path/Query/Body

RecommendedAnnotated+ The corresponding decorator writes all the rules in one line, which is clear and conducive to generating API documentation.

from fastapi import Path
from typing import Annotated

@app.get("/items/{item_id}/")
def get_validated_item(
    item_id: Annotated[
        int,
        Path(
            title="商品ID",          # 文档中的字段标题
            ge=1,                    # 大于等于 1
            le=10000,                # 小于等于 10000
            description="商品唯一标识符,范围1-10000"
        )
    ]
):
    return {"item_id": item_id}

gelegtltThese parameters are the same as Pydantic’sFieldThe usage is consistent, and once you learn one, you can draw inferences about other cases.

Model-level verification: Pydantic V2

When you need to perform complex validation on a model and combine multiple field judgments, you should write it in the Pydantic model. Pydantic V2 provides three levels of custom validation:

1. Global configuration + field rules

passmodel_configSet the behavior of the entire model and then useFieldPrecisely describe the constraints for each field.

from pydantic import BaseModel, ConfigDict, Field

class ValidatedItem(BaseModel):
    # 全局配置:自动去除首尾空格、禁止多余字段、赋值时也触发验证
    model_config = ConfigDict(
        str_strip_whitespace=True,
        extra="forbid",
        validate_assignment=True
    )

    name: str = Field(..., min_length=2, max_length=50)   # ... 表示必填
    price: float = Field(..., gt=0)                        # 价格必须大于 0
    category: str = Field(..., alias="cat")                # 请求数据里用 "cat" 字段

2. Single field custom verification:@field_validator

When additional logical judgment needs to be made on a certain field, such as checking whether the category name is in the whitelist:

from pydantic import field_validator

class ValidatedItem(ValidatedItem):
    @field_validator("category")
    @classmethod
    def validate_category(cls, v: str) -> str:
        allowed = {"electronics", "books", "clothing"}
        if v.lower() not in allowed:
            raise ValueError(f"分类必须是{allowed}之一")
        return v.lower()

Note that the validator for V2 must be a class method and use@classmethoddecorate.

3. Multi-field joint verification:@model_validator

Some rules need to rely on multiple fields at the same time to determine, such as "Tax cannot exceed 30% of the price":

from pydantic import model_validator

class ValidatedItem(ValidatedItem):
    @model_validator(mode="after")   # after 模式:先检查单个字段,再执行此方法
    def check_price_with_tax(self) -> "ValidatedItem":
        if self.tax and self.tax > self.price * 0.3:
            raise ValueError("税率不能超过价格的30%")
        return self

Through the combination of these three methods, almost all verification requirements on the business side can be covered.

Mixing parameters and basic dependencies: combination boxing

A real interface usually has path parameters, query parameters, request body parameters, and even calls to common logic (such as query string preprocessing) in one path. FastAPI's Dependency Injection (Depends) function can make these mashup scenarios organized.

Complete example of multi-parameter mashup

from fastapi import Body
from typing import Annotated

@app.put("/users/{user_id}/items/{item_id}/")
def update_user_item(
    # 路径参数
    user_id: Annotated[int, Path(ge=1)],
    item_id: Annotated[int, Path(ge=1)],
    # 请求体(整个模型)
    item: ValidatedItem,
    # 单独的请求体字段(不想建一个模型时使用)
    priority: Annotated[int, Body(ge=1, le=5)] = 3,
    # 查询参数
    notify: Annotated[bool, Query()] = False
):
    return {
        "user_id": user_id,
        "item_id": item_id,
        "item": item,
        "priority": priority,
        "notify": notify
    }

FastAPI can automatically distinguish which parameter comes from the path, which comes from the query, and which comes from the request body, without you having to write it manually at all.request.query_params.get()code like that.

Use dependency preprocessing to search for keywords

Extract reusable logic (such as string cleaning) into dependent functions so that multiple routes can share them.

from fastapi import Depends
from typing import Annotated

# 依赖函数
def preprocess_query(q: Annotated[str, Query(min_length=1, max_length=50)]) -> str:
    # 去掉多余空格,统一转小写
    return " ".join(q.split()).lower()

@app.get("/items/search/")
def search_items(processed_q: str = Depends(preprocess_query)):
    return {"processed_q": processed_q, "msg": f"搜索{processed_q}"}

In this way, any route that requires the same preprocessing logic can just addDepends(preprocess_query)That’s it, to avoid reinventing the wheel.

Error handling and debugging: novice friendly

FastAPI provides developers with very thoughtful error handling and debugging tools, which is one of the reasons why it is so popular.

1. Automatic 422 error feedback

When the parameter type is incorrect or verification fails, FastAPI will return422 Unprocessable Entity, and clearly stated in the response body:

  • Which request parameter is the problem?
  • Specific reasons for failure
  • What value did you actually pass in?

You can do this in Swagger UI (/docs) or Redoc(/redoc), you can see these error messages directly, and the debugging efficiency is extremely high.

2. Manually throw business exceptions

For business logic errors, such as resource not found, useHTTPExceptionManually return the appropriate HTTP status code.

from fastapi import HTTPException

@app.get("/items/{item_id}/detail/")
def get_item_detail(item_id: int):
    if item_id == 9999:
        raise HTTPException(status_code=404, detail=f"商品{item_id}不存在")
    return {"item_id": item_id, "name": f"商品{item_id}"}

3. Turn on debugging mode with one click

  • Visithttp://127.0.0.1:8000/docs(Swagger UI) or/redoc(Redoc), you can directly call the interface online and view parameter descriptions.
  • Add when starting the service--reload --debug, enable code hot reloading and detailed error pages, a must during the development stage.
1. Priority use **`Annotated`** Explicitly label parameter types and validation rules, which not only facilitates reading, but also makes automatic documents more detailed. 2. All request body data should use **Pydantic V2`BaseModel`** Bearing, say goodbye to the inefficient way of manually parsing JSON. 3. Make good use of **`/docs`and`/redoc`**, test while developing, what you see is what you get. 4. High-frequency verification logic (such as email format, classification verification) can be encapsulated into reusable **precompiled regular** or **tool function** to avoid code duplication.

Summary: Ending of small e-commerce example

Going back to the e-commerce API scenario at the beginning, we used the knowledge we learned today to assemble it into a complete small example. This example also includes path parameters, query parameters, request body, dependency preprocessing, and Pydantic V2’s validation functionality.

from fastapi import FastAPI, Path, Query, Depends
from pydantic import BaseModel, ConfigDict, Field, field_validator
from typing import Annotated, Optional, List
from enum import Enum

app = FastAPI()

# 商品分类枚举
class Category(str, Enum):
    ELECTRONICS = "electronics"
    BOOKS = "books"

# 查询关键词预处理依赖
def preprocess_query(q: Annotated[Optional[str], Query(min_length=1, max_length=50)] = None) -> Optional[str]:
    return " ".join(q.split()).lower() if q else None

# 商品模型(含校验)
class Item(BaseModel):
    model_config = ConfigDict(str_strip_whitespace=True)
    name: str = Field(..., min_length=2, max_length=50)
    price: float = Field(..., gt=0)

    @field_validator("name")
    @classmethod
    def name_not_empty(cls, v: str) -> str:
        if not v:
            raise ValueError("商品名不能为空")
        return v

# 组合路径参数 + 查询参数 + 依赖
@app.get("/categories/{category}/items/{item_id}/")
def get_full_item(
    category: Category,
    item_id: Annotated[int, Path(ge=1)],
    processed_q: Optional[str] = Depends(preprocess_query),
    min_price: Annotated[Optional[float], Query(ge=0)] = None
):
    return {
        "category": category.value,
        "item_id": item_id,
        "q": processed_q,
        "min_price": min_price
    }

# 接收商品创建请求
@app.post("/categories/{category}/items/")
def create_full_item(category: Category, item: Item):
    return {"category": category.value, "item": item}

As long as you master the three core concepts of path parameters, query parameters, and request body parameters, plus the verification routine of Pydantic V2, you can already develop most RESTful APIs. The subsequent cookie processing, header extraction, file upload and other knowledge are essentially extended functions grown from this basis.

Adhere to the idea of ​​​​"type hints + model verification", and your road to FastAPI will become smoother and smoother!