FastAPI 框架入门

先看框架承担哪部分职责

FastAPI 入门:函数参数与类型注解被框架读取,用于路由绑定和校验。Pydantic 在边界处理数据,普通 Python 函数不会因此自动变成强制类型系统。

最小练习与预期结果:分别提交合法、缺字段与错类型数据;观察校验响应,再测试纯业务函数的错误处理。

具体 API 与版本细节可随时查阅 FastAPI 官方文档。建议顺序是:先用 Pydantic 模型接住一组请求,观察类型注解如何决定校验行为,再回头看依赖注入与路由组织。FastAPI 的自动机制多来自类型注解而非配置约定,因此注解写在哪一行,就决定了哪一层承担校验——这比记住某个装饰器的参数更值得先想清楚。

1. 项目初始化

# 创建应用骨架并安装依赖(--no-package 生成 main.py 的扁平布局,
1. 与下文 uvicorn main:app 匹配;不加该参数新版 uv 默认 src 布局,模块在 src/ 下会找不到)
uv init --no-package todo-api && cd todo-api
uv add fastapi "uvicorn[standard]"

1. 先保存第 2 节 main.py,再启动开发服务器(热重载)
uv run uvicorn main:app --reload

2. 第一个路由:路径参数

# main.py
from fastapi import FastAPI, HTTPException, Query, status

app = FastAPI(title="待办服务", version="0.1.0")

1. 演示用内存存储,后续项目会换成数据库
_TODOS: dict[int, dict] = {1: {"id": 1, "title": "学 FastAPI", "done": False}}

@app.get("/health")
def health() -> dict[str, str]:
    """健康检查端点,文档字符串会显示在 /docs 中。"""
    return {"status": "ok"}

@app.get("/todos/{todo_id}")
async def read_todo(todo_id: int) -> dict:
    1. 注解 int 让 FastAPI 自动做类型转换;请求 /todos/abc 会得到 422
    if todo_id not in _TODOS:
        1. 规范错误出口:状态码 + 人类可读的 detail
        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="待办不存在")
    return _TODOS[todo_id]

要点:

  • 函数签名即校验规则——路径参数注解为 int,非数字请求直接返回 422
  • HTTPException 是 FastAPI 推荐的业务错误出口(异常机制基础见 Python 异常处理)
  • 所有语法细节可随时查 FastAPI 官方文档

3. Pydantic 模型:请求体与响应体

from pydantic import BaseModel, ConfigDict, Field

class TodoCreate(BaseModel):
    """创建待办的入参——自动完成校验、序列化与文档生成。"""
    model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")
    title: str = Field(min_length=1, max_length=100, examples=["学 FastAPI"])
    done: bool = False

class TodoRead(TodoCreate):
    """对外输出的完整模型:继承入参字段并补充 id。"""
    id: int

@app.post("/todos", response_model=TodoRead, status_code=status.HTTP_201_CREATED)
async def create_todo(payload: TodoCreate) -> TodoRead:
    1. payload 已经过 Pydantic 校验:title 保证是 1-100 字符
    new_id = max(_TODOS, default=0) + 1
    todo = TodoRead(id=new_id, **payload.model_dump())
    _TODOS[new_id] = todo.model_dump()
    return todo

要点:

  • 请求体模型通过参数注解声明,无需手动解析 JSON
  • response_model 起到”出口过滤”作用:多余字段不会泄露给客户端
  • Pydantic v2 使用 model_dump(),替代 v1 的 dict()

第 3、4 节代码按顺序追加到同一个 main.py。演示存储仅属于一个进程,重启即丢失;内存读写端点使用不含 await 的短 async def,避免线程池中的多个请求交错分配 ID。这不替代数据库事务:接入阻塞 I/O 后须改为线程池或异步驱动,并由数据库保障 ID 与并发更新。response_model 还会校验输出;若服务端产生错误类型,应修正实现,不能把响应校验失败包装成客户端错误。

4. 查询参数:列表与过滤

@app.get("/todos", response_model=list[TodoRead])
async def list_todos(
    done: bool | None = None,   # 可选过滤条件:?done=true
    limit: int = Query(10, ge=1, le=100),
    offset: int = Query(0, ge=0),
) -> list[TodoRead]:
    items = [TodoRead(**t) for t in _TODOS.values()]
    if done is not None:                          # 仅在显式传入时过滤
        items = [t for t in items if t.done == done]
    return items[offset : offset + limit]

要点:不属于路径的标量参数自动成为查询参数;bool | None 表达”可传可不传”。

用 /docs 验收:创建 {"title":"学路由"} 返回 201 与 ID;查询该 ID 返回相同标题;{"title":" "}、?limit=0、?offset=-1 返回 422;未知 ID 返回 404。int 只检查类型,Query 中的上下界才阻止负索引和无界分页。把上述用例整理成 pytest(用 fastapi.testclient.TestClient 请求路由)即可随时回归验证。

参数声明与校验的官方说明见 FastAPI Query 校验。

5. 依赖注入:Depends 最小示例

导语提到依赖注入,这里补上最小示例。FastAPI 用 Depends 声明依赖:框架在请求进入前执行依赖函数,把返回值注入参数。常见的两类用法是共享资源(如存储)与请求级策略(如限流)。下面把第 3 节的 create_todo 升级——替换原路由即可,同一路径只注册一次:

from fastapi import Depends

1. 用途一:共享资源——请求处理用的存储由依赖统一提供,测试时可换成假实现
_memory: dict[int, dict] = {}

def get_store() -> dict[int, dict]:
    return _memory

1. 用途二:请求级策略——最简单的固定窗口限流
_calls: dict[str, int] = {}

def rate_limit(key: str = "global") -> None:/n    _calls[key] = _calls.get(key, 0) + 1
    if _calls[key] > 100:
        raise HTTPException(status.HTTP_429_TOO_MANY_REQUESTS, detail="请求过于频繁")

@app.post("/todos", response_model=TodoRead, status_code=status.HTTP_201_CREATED,
          dependencies=[Depends(rate_limit)])
async def create_todo(payload: TodoCreate, store: dict[int, dict] = Depends(get_store)) -> TodoRead:/n    new_id = max(store, default=0) + 1
    todo = TodoRead(id=new_id, **payload.model_dump())
    store[new_id] = todo.model_dump()
    return todo

要点:处理函数只声明「我需要一个什么样的依赖」,不关心它如何构造——这让存储、限流策略都可以在测试中替换。依赖还可以嵌套(依赖依赖),官方说明见 FastAPI Dependencies。

6. 自动文档:/docs 与 /redoc

地址用途
http://127.0.0.1:8000/docsSwagger UI,可在页面里直接发请求调试
http://127.0.0.1:8000/redocReDoc 风格阅读版
http://127.0.0.1:8000/openapi.json机器可读的 OpenAPI 3 规范
  • 文档由类型注解 + Pydantic 模型 + 文档字符串自动生成,无需手写
  • 在 FastAPI(title=..., version=...) 中维护元信息;示例值用 Field(examples=[...])
  • 前后端协作时可直接把 openapi.json 交给客户端代码生成器

最佳实践

同步端点可通过框架的线程池运行阻塞代码,async 端点则应等待可异步的操作;随意把同步库调用放进 async 函数并不会自动变非阻塞。线程池同样有容量与排队成本,应在实际负载下观察。

输入模型只允许客户端应填写的字段,输出模型只暴露需要返回的字段,防止数据库内部属性意外泄漏。校验成功还不代表有权操作资源,用跨用户访问和非法输入验证服务端边界。

常见问题

Q1: 请求一直返回 422? 请求体/参数与模型声明不符。查看 /docs 中自动生成的 Schema,与实际发送的 JSON 对照排查。

Q2: def 和 async def 端点怎么选? 用同步库(多数传统数据库驱动)就用 def;全异步栈(asyncpg、redis.asyncio、httpx.AsyncClient)才用 async def。


延伸阅读

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇