1. FastAPI 入门第二天:构建你的第一个完整 API
作为一名从 Flask 转战 FastAPI 的后端开发者,我清楚地记得第二天学习时的兴奋感——这个框架的现代特性让我眼前一亮。FastAPI 不仅仅是一个 Python Web 框架,它更像是为 API 开发量身定制的瑞士军刀,结合了 Python 类型提示的威力和自动化的 OpenAPI 文档生成。
为什么选择 FastAPI 作为你的 API 开发工具? 当我第一次看到一行简单的 Python 类型注解就能自动生成 API 文档时,就决定深入研究它。与传统的 Flask 或 Django REST Framework 相比,FastAPI 提供了更快的性能(基于 Starlette 和 Pydantic)、更简洁的代码和更强大的开发体验。
2. 核心功能快速实现
2.1 基础路由与请求处理
让我们从一个基础的 GET 请求开始,这是所有 Web 开发的起点:
python复制from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
这个简单的例子展示了 FastAPI 的几个关键优势:
- 使用
@app.get装饰器明确声明 HTTP 方法 - 异步支持(async/await)开箱即用
- 自动将 Python 字典转换为 JSON 响应
提示:虽然这个示例很简单,但已经是一个完全符合 OpenAPI 标准的 API 端点。启动服务后访问
/docs就能看到自动生成的交互式文档。
2.2 路径参数与类型校验
FastAPI 最强大的特性之一是内置的类型校验系统:
python复制@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
这里 item_id: int 不仅是一个类型提示,FastAPI 会自动:
- 将 URL 中的字符串参数转换为整数
- 如果无法转换(如传入 "foo"),自动返回 422 错误和详细的错误信息
- 在交互式文档中显示参数类型要求
2.3 查询参数与默认值
处理查询参数同样直观:
python复制from typing import Optional
@app.get("/items/")
async def list_items(skip: int = 0, limit: Optional[int] = 10):
return {"skip": skip, "limit": limit}
这个接口可以这样访问:
/items/→ 使用默认值 skip=0, limit=10/items/?skip=20&limit=5→ skip=20, limit=5
注意:Optional[int] 表示这个参数可以省略,当省略时使用默认值 10。这与直接使用
limit: int = 10的区别在于后者不允许显式传递 None 值。
3. 请求体与 Pydantic 模型
3.1 定义数据模型
FastAPI 使用 Pydantic 模型来处理请求体,这是它的一大亮点:
python复制from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
这个模型定义了:
- 必需的 name 字段(字符串)
- 可选的 description 字段(默认为 None)
- 必需的 price 字段(浮点数)
- 可选的 tax 字段(默认为 None)
3.2 创建 POST 接口
使用定义好的模型处理 POST 请求:
python复制@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
这个接口会自动:
- 验证请求体是否符合 Item 模型定义
- 将 JSON 请求体转换为 Item 实例
- 在文档中展示请求体示例和结构
4. 响应模型与数据转换
4.1 控制响应数据结构
你可以指定响应模型,确保返回的数据结构一致:
python复制class UserOut(BaseModel):
username: str
email: str
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
return user
即使 UserIn 包含密码字段,响应中也只会包含 UserOut 定义的字段。
4.2 响应状态码
设置正确的 HTTP 状态码很重要:
python复制from fastapi import status
@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(item: Item):
return {"item": item}
FastAPI 提供了所有标准 HTTP 状态码的便捷引用。
5. 错误处理与自定义异常
5.1 内置错误处理
FastAPI 自动处理许多常见错误:
- 422:请求数据验证失败
- 404:路径不存在
- 405:方法不允许
5.2 自定义异常处理器
python复制from fastapi import HTTPException
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id not in items:
raise HTTPException(
status_code=404,
detail="Item not found",
headers={"X-Error": "Item missing"}
)
return {"item": items[item_id]}
6. 依赖注入系统
6.1 创建依赖项
python复制from fastapi import Depends
async def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
6.2 依赖项的组合
python复制def query_extractor(q: Optional[str] = None):
return q
def query_or_body_extractor(
q: str = Depends(query_extractor),
last_query: Optional[str] = Cookie(None)
):
if not q:
return last_query
return q
7. 数据库集成
7.1 SQLAlchemy 配置
python复制from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
7.2 创建模型
python复制from sqlalchemy import Column, Integer, String
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
hashed_password = Column(String)
7.3 数据库依赖
python复制def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/")
def create_user(user: UserCreate, db: Session = Depends(get_db)):
db_user = User(username=user.username, hashed_password=fake_hash_password(user.password))
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
8. 认证与安全
8.1 OAuth2 密码流
python复制from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}
8.2 密码哈希
python复制from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password: str, hashed_password: str):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str):
return pwd_context.hash(password)
9. 中间件与 CORS
9.1 添加中间件
python复制from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
9.2 自定义中间件
python复制@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
10. 测试与部署
10.1 编写测试
python复制from fastapi.testclient import TestClient
client = TestClient(app)
def test_read_item():
response = client.get("/items/42")
assert response.status_code == 200
assert response.json() == {"item_id": 42}
10.2 部署准备
python复制# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
使用 Uvicorn 运行:
bash复制uvicorn main:app --reload
11. 性能优化技巧
11.1 异步数据库访问
python复制async def get_db():
async with async_session() as session:
yield session
11.2 响应缓存
python复制from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from fastapi_cache.decorator import cache
@app.get("/")
@cache(expire=60)
async def index():
return {"message": "Hello World"}
12. 实际项目结构
推荐的项目结构:
code复制.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── endpoints
│ │ │ ├── items.py
│ │ │ └── users.py
│ ├── core
│ │ ├── config.py
│ │ └── security.py
│ ├── db
│ │ ├── models.py
│ │ └── session.py
│ └── schemas
│ ├── items.py
│ └── users.py
13. 常见问题解决
13.1 循环导入问题
解决方案:
- 使用字符串形式的类型提示
python复制def get_user(db: "Session"):
...
- 将公共依赖放在单独的模块中
13.2 大型应用组织
使用 APIRouter:
python复制from fastapi import APIRouter
router = APIRouter()
@router.get("/items/")
async def read_items():
return [{"name": "Item 1"}]
app.include_router(router, prefix="/api/v1")
14. 进阶特性探索
14.1 后台任务
python复制from fastapi import BackgroundTasks
def write_notification(email: str, message=""):
with open("log.txt", mode="w") as email_file:
content = f"notification for {email}: {message}"
email_file.write(content)
@app.post("/send-notification/{email}")
async def send_notification(
email: str, background_tasks: BackgroundTasks
):
background_tasks.add_task(write_notification, email, message="some notification")
return {"message": "Notification sent in the background"}
14.2 WebSocket 支持
python复制from fastapi import WebSocket
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message text was: {data}")
15. 监控与日志
15.1 结构化日志
python复制import logging
from fastapi.logger import logger
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
@app.get("/")
async def root():
logger.info("Root endpoint accessed")
return {"message": "Hello World"}
15.2 性能监控
python复制from prometheus_fastapi_instrumentator import Instrumentator
Instrumentator().instrument(app).expose(app)
16. 实际开发经验分享
在真实项目中使用 FastAPI 一年多后,我总结了几个关键经验:
-
类型提示是朋友:虽然一开始可能觉得繁琐,但完善的类型提示能显著减少运行时错误,并提升开发效率。
-
合理组织项目结构:从一开始就采用模块化设计,避免将所有代码堆在一个文件中。
-
充分利用依赖注入:这是 FastAPI 最强大的特性之一,可以极大简化代码并提高可测试性。
-
异步不是银弹:虽然 FastAPI 支持异步,但只有 I/O 密集型操作才能真正受益。对于 CPU 密集型任务,考虑使用后台任务或单独的 worker 进程。
-
文档即代码:自动生成的 OpenAPI 文档是 FastAPI 的杀手锏,确保你的端点描述清晰准确,这将成为前端团队的最爱。