前言

Flask 时代写 API 要自己造的轮子——参数校验、文档、类型提示——FastAPI 一次全给:写函数签名,送你文档和校验。这篇从零到部署走一遍。

一、五分钟起步

pip install "fastapi[standard]" -i https://mirrors.aliyun.com/pypi/simple/
# main.py
from fastapi import FastAPI

app = FastAPI(title="运维平台 API", version="1.0.0")

@app.get("/healthz")
def healthz():
    return {"status": "ok"}

@app.get("/hello/{name}")
def hello(name: str):
    return {"message": f"hello, {name}"}
fastapi dev main.py              # 开发模式, 改代码热重载
# 打开 http://localhost:8000/docs        ← 自动交互式文档(Swagger UI)
# 打开 http://localhost:8000/redoc        ← 另一套文档样式

类型注解就是功能:name: str 声明后,框架自动解析路径参数、校验类型、生成文档。

二、Pydantic:请求与响应模型

from pydantic import BaseModel, Field, EmailStr
from typing import Optional

class HostCreate(BaseModel):
    name: str = Field(..., min_length=2, max_length=30, examples=["web01"])
    ip: str = Field(..., pattern=r"^(\d{1,3}\.){3}\d{1,3}$")
    port: int = Field(22, ge=1, le=65535)
    tags: list[str] = []
    owner: Optional[EmailStr] = None

class HostOut(HostCreate):
    id: int
    status: str

@app.post("/api/hosts", response_model=HostOut, status_code=201)
def create_host(host: HostCreate):
    # 走到这里, 数据一定合法(不合法根本进不来)
    saved = save_to_db(host)          # 假装入库
    return saved

非法请求的自动处理:

curl -X POST http://localhost:8000/api/hosts -H "Content-Type: application/json" \
  -d '{"name":"w","ip":"999.1.1.1","port":70000}'
# 422 Unprocessable Entity
# {"detail":[
#   {"loc":["body","name"],"msg":"String should have at least 2 characters"},
#   {"loc":["body","ip"],"msg":"String should match pattern..."},
#   {"loc":["body","port"],"msg":"Input should be at most 65535"}]}

response_model 的妙处:输出自动过滤(比如过滤密码字段)、自动转成声明的形状——文档永远和代码一致。

三、查询参数、过滤与分页

from fastapi import Query

@app.get("/api/hosts")
def list_hosts(
    page: int = Query(1, ge=1),
    size: int = Query(20, ge=1, le=100),
    tag: Optional[str] = None,
):
    return {"page": page, "size": size, "tag": tag}
# /api/hosts?page=2&size=50&tag=prod

四、依赖注入:中间逻辑的优雅归宿

鉴权、取 DB 会话、分页参数——不用每个函数复制粘贴:

from fastapi import Depends, Header, HTTPException

API_TOKEN = "secret-token"

def verify_token(authorization: str = Header(...)):
    if authorization != f"Bearer {API_TOKEN}":
        raise HTTPException(status_code=401, detail="invalid token")

def get_db():
    db = SessionLocal()
    try:
        yield db            # 用完自动走 finally 关闭
    finally:
        db.close()

@app.get("/api/hosts", dependencies=[Depends(verify_token)])
def list_hosts(db=Depends(get_db)):
    return db.query(Host).all()

依赖可以嵌套依赖,测试时用 app.dependency_overrides 替换假实现——解耦利器。

五、异步端点

import httpx

@app.get("/api/check")
async def check(url: str):
    async with httpx.AsyncClient() as client:
        resp = await client.get(url, timeout=5)
    return {"url": url, "status": resp.status_code}

# 注意: async 端点里绝不能有阻塞调用(time.sleep/同步IO)
# 要么用 await 异步库, 要么用普通 def(FastAPI 自动丢线程池)

六、后台任务与定时活

from fastapi import BackgroundTasks

def write_audit(action: str):
    with open("audit.log", "a") as f:
        f.write(f"{action} at {time.time()}\n")

@app.post("/api/hosts/{hid}/restart")
def restart_host(hid: int, bg: BackgroundTasks):
    bg.add_task(write_audit, f"restart host {hid}")   # 响应后异步执行
    return {"result": "restarting"}

# 更重的任务用 Celery/ARQ 队列, BackgroundTasks 适合轻量收尾

七、错误处理与中间件

from fastapi import Request
from fastapi.responses import JSONResponse

class BizError(Exception):
    def __init__(self, code: str, msg: str):
        self.code, self.msg = code, msg

@app.exception_handler(BizError)
async def biz_handler(request: Request, exc: BizError):
    return JSONResponse(status_code=400,
        content={"code": exc.code, "message": exc.msg})

@app.middleware("http")
async def add_timing(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    response.headers["X-Process-Time"] = f"{time.time()-start:.3f}"
    return response

八、项目结构与部署

app/
├── main.py            # FastAPI 实例与路由注册
├── deps.py            # 公共依赖
├── models.py          # Pydantic 模型
└── routers/
    ├── hosts.py
    └── users.py
# routers/hosts.py —— APIRouter 拆分路由
from fastapi import APIRouter
router = APIRouter(prefix="/api/hosts", tags=["hosts"])

@router.get("/{hid}")
def get_host(hid: int): ...

生产部署:

# 多 worker 跑生产
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000

# 或纯 uvicorn
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4

前置 Nginx(反向代理篇的配置直接复用)+ systemd 拉起:

[Service]
ExecStart=/opt/app/.venv/bin/gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 127.0.0.1:8000

九、测试

from fastapi.testclient import TestClient

client = TestClient(app)

def test_create_host():
    resp = client.post("/api/hosts", json={"name": "web01", "ip": "10.0.0.1"})
    assert resp.status_code == 201
    assert resp.json()["name"] == "web01"

def test_invalid_ip():
    resp = client.post("/api/hosts", json={"name": "web01", "ip": "bad"})
    assert resp.status_code == 422

同步 TestClient + pytest,无起服务的开销,CI 里跑飞快。

十、Flask 还是 FastAPI?

维度 FastAPI Flask
类型/校验 原生 Pydantic ✅ 手写或插件
异步 原生 ✅ 需要 async 视图改造
文档 自动 ✅ flask-restx 等
生态/历史 较新 极其成熟
适合 新 API 项目 传统 Web 页面/老项目

我的结论:2026 年起新 API 项目默认 FastAPI,除非团队有厚重 Flask 资产。

小结

能力 写法
校验 Pydantic 模型 + Field 约束
文档 自动 /docs,response_model 保持一致
复用逻辑 Depends 依赖注入
异步 async def + await
收尾任务 BackgroundTasks
部署 gunicorn -k UvicornWorker

本文是「Python」系列第 9 篇。