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_id 和 answer 的约束问题。再把题号换成 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 功能堆进一个文件,而在于确认每一层职责都清楚。掌握这些边界后,再接入数据库、认证和后台任务,系统仍然容易理解和测试。