用一个小项目检验 FastAPI:从 Pydantic、异步接口到 Jinja2 模板

2026-07-21 26 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:7 分钟

FastAPI 学习路径的收尾测验覆盖 REST API、Pydantic 模型、异步端点和 Jinja2 模板。与其只记住装饰器名称,不如把这些能力放进同一个可运行的小项目:接收结构化数据、执行校验、提供异步接口,并渲染一个简单页面。

真正需要掌握的四层能力

FastAPI 的开发体验很轻快,但一个可靠接口仍然包含多个层次:

  • 路由层决定 HTTP 方法、路径参数、查询参数和状态码。
  • 模型层使用 Pydantic 校验输入,并约束输出结构。
  • 执行层区分同步与异步工作,避免在 async def 中阻塞事件循环。
  • 展示层可以返回 JSON,也可以通过 Jinja2 输出服务端渲染的 HTML。

测验题往往能检查语法,而项目更容易暴露边界问题。例如,请求体缺少字段时是否返回清晰的错误?查询不存在的资源时是否使用 404?异步端点内部是否偷偷调用了阻塞函数?

一个可运行的自测项目

可以这样实践:构建一个内存版测验服务。下面的项目假设使用 Python 3.10 或更高版本。

先创建目录并安装依赖:

mkdir -p fastapi-quiz/templates
cd fastapi-quiz
python -m venv .venv
source .venv/bin/activate
python -m pip install fastapi 'uvicorn[standard]' jinja2

创建 app.py

import asyncio
from typing import Annotated

from fastapi import FastAPI, HTTPException, Query, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel, Field

app = FastAPI(title="FastAPI Skills Check")
templates = Jinja2Templates(directory="templates")


class QuizAnswer(BaseModel):
    question_id: int = Field(gt=0)
    answer: str = Field(min_length=1, max_length=100)


class QuizResult(BaseModel):
    question_id: int
    correct: bool
    normalized_answer: str


ANSWER_KEY = {1: "pydantic", 2: "async", 3: "jinja2"}


@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
    return templates.TemplateResponse(
        request=request,
        name="index.html",
        context={"question_count": len(ANSWER_KEY)},
    )


@app.post("/answers", response_model=QuizResult, status_code=201)
async def submit_answer(payload: QuizAnswer) -> QuizResult:
    expected = ANSWER_KEY.get(payload.question_id)
    if expected is None:
        raise HTTPException(status_code=404, detail="Question not found")

    await asyncio.sleep(0.05)  # 模拟异步数据库或网络调用
    normalized = payload.answer.strip().lower()
    return QuizResult(
        question_id=payload.question_id,
        correct=normalized == expected,
        normalized_answer=normalized,
    )


@app.get("/questions")
async def list_questions(
    limit: Annotated[int, Query(ge=1, le=20)] = 10,
):
    questions = [
        {"id": 1, "prompt": "Which library validates FastAPI models?"},
        {"id": 2, "prompt": "Which keyword defines a coroutine?"},
        {"id": 3, "prompt": "Which template engine is commonly paired here?"},
    ]
    return {"items": questions[:limit], "count": min(limit, len(questions))}

再创建 templates/index.html

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <title>FastAPI Skills Check</title>
  </head>
  <body>
    <h1>FastAPI Skills Check</h1>
    <p>当前共有 {{ question_count }} 道题。</p>
    <p><a href="/docs">打开交互式 API 文档</a></p>
  </body>
</html>

启动服务:

uvicorn app:app --reload

打开 http://127.0.0.1:8000/ 可查看模板页面,打开 /docs 则可以直接试验请求模型和接口响应。

不只测试成功路径

curl 提交正确答案:

curl -i -X POST http://127.0.0.1:8000/answers \
  -H 'Content-Type: application/json' \
  -d '{"question_id":1,"answer":"Pydantic"}'

随后故意提交非法数据:

curl -i -X POST http://127.0.0.1:8000/answers \
  -H 'Content-Type: application/json' \
  -d '{"question_id":0,"answer":""}'

第二个请求应被 Pydantic 拒绝。这里值得观察的不只是状态码,还包括错误响应能否准确指出 question_idanswer 的约束问题。再把题号换成 99,则应由业务逻辑返回 404。这两类失败分别属于输入校验和资源不存在,不应混成同一种错误。

async def 不等于自动高并发

异步端点适合等待支持异步协议的数据库驱动、HTTP 客户端或消息系统。示例中的 asyncio.sleep() 只是可运行的延迟模拟。生产代码不能把 time.sleep()、同步 HTTP 请求或重型 CPU 计算直接塞进异步端点,否则事件循环仍会被阻塞。

如果依赖库只有同步 API,可以暂时使用普通 def 路由,让 FastAPI 在线程池中执行它;如果是大量 CPU 计算,则更适合任务队列或独立计算服务。选择 async def 的依据应是调用链是否真正支持异步,而不是接口看起来是否“现代”。

收尾检查清单

准备把 FastAPI 服务投入真实环境前,可以检查以下项目:

  • 请求与响应是否分别定义了明确的 Pydantic 模型。
  • 路径、HTTP 方法和状态码是否符合资源语义。
  • 404、校验失败和内部错误是否能被区分。
  • 异步端点中是否存在阻塞 I/O 或 CPU 密集任务。
  • Jinja2 模板是否只接收必要数据,并保持默认转义策略。
  • 核心接口是否覆盖成功路径、非法输入和不存在资源。

这类综合练习的价值不在于把所有 FastAPI 功能堆进一个文件,而在于确认每一层职责都清楚。掌握这些边界后,再接入数据库、认证和后台任务,系统仍然容易理解和测试。


相关推荐