使用SQLModel和FastAPI构建RESTful API教程

使用SQLModel和FastAPI构建RESTful API教程

还在为构建RESTful API时需要在数据库模型和API数据模型之间反复转换而烦恼吗?SQLModel和FastAPI的完美结合将彻底改变你的开发体验!本文将带你从零开始,构建一个功能完整的英雄管理API,涵盖CRUD操作、数据验证、依赖注入等核心功能。

🎯 你将学到什么

  • SQLModel和FastAPI的集成原理
  • 完整的CRUD(增删改查)API实现
  • 数据库会话管理和依赖注入
  • 数据验证和错误处理
  • 分页查询和过滤功能
  • 自动化API文档生成

📦 环境准备和安装

首先确保你已安装Python 3.7+,然后创建虚拟环境并安装必要的依赖:

# 创建虚拟环境
python -m venv venv

# 启用虚拟环境(Windows)
venv\Scripts\activate

# 启用虚拟环境(macOS/Linux)
source venv/bin/activate

# 安装核心依赖
pip install fastapi "uvicorn[standard]" sqlmodel

🏗️ 项目结构设计

让我们先规划一个清晰的项目结构:

heroes_api/
├── main.py          # FastAPI应用入口
├── models.py        # 数据模型定义
├── database.py      # 数据库配置
├── dependencies.py  # 依赖注入
└── crud.py         # 业务逻辑

🎭 数据模型定义

SQLModel的强大之处在于它结合了SQLAlchemy的ORM功能和Pydantic的数据验证能力。让我们定义一个英雄模型:

# models.py
from sqlmodel import Field, SQLModel
from typing import Optional
from datetime import datetime

class HeroBase(SQLModel):
    name: str = Field(index=True, max_length=100)
    secret_name: str = Field(max_length=100)
    age: Optional[int] = Field(default=None, index=True)
    created_at: datetime = Field(default_factory=datetime.utcnow)

class Hero(HeroBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)

class HeroCreate(HeroBase):
    pass

class HeroRead(HeroBase):
    id: int
    created_at: datetime

class HeroUpdate(SQLModel):
    name: Optional[str] = None
    secret_name: Optional[str] = None
    age: Optional[int] = None

这个模型设计体现了SQLModel的核心优势:

  • Hero是数据库表模型,包含所有ORM功能
  • HeroCreate用于创建操作的输入验证
  • HeroRead用于读取操作的输出序列化
  • HeroUpdate用于更新操作的部分更新支持

🗄️ 数据库配置

# database.py
from sqlmodel import create_engine, SQLModel, Session
from typing import Generator

# SQLite数据库配置
sqlite_url = "sqlite:///./heroes.db"
connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)

def create_db_and_tables():
    """创建数据库和表结构"""
    SQLModel.metadata.create_all(engine)

def get_session() -> Generator[Session, None, None]:
    """获取数据库会话的依赖函数"""
    with Session(engine) as session:
        yield session

🔧 依赖注入配置

# dependencies.py
from fastapi import Depends
from sqlmodel import Session
from database import get_session

def get_db_session(session: Session = Depends(get_session)):
    """数据库会话依赖注入"""
    return session

🚀 FastAPI应用搭建

现在让我们创建主应用文件:

# main.py
from fastapi import FastAPI, Depends, HTTPException, Query
from sqlmodel import Session, select
from typing import List, Optional
import models
import database
from dependencies import get_db_session

# 创建FastAPI应用实例
app = FastAPI(
    title="Heroes API",
    description="一个基于SQLModel和FastAPI的英雄管理API",
    version="1.0.0"
)

@app.on_event("startup")
def on_startup():
    """应用启动时创建数据库表"""
    database.create_db_and_tables()

@app.post("/heroes/", response_model=models.HeroRead)
def create_hero(
    hero: models.HeroCreate,
    session: Session = Depends(get_db_session)
):
    """创建新英雄"""
    db_hero = models.Hero.model_validate(hero)
    session.add(db_hero)
    session.commit()
    session.refresh(db_hero)
    return db_hero

@app.get("/heroes/", response_model=List[models.HeroRead])
def read_heroes(
    offset: int = 0,
    limit: int = Query(default=100, le=100),
    session: Session = Depends(get_db_session)
):
    """获取英雄列表(支持分页)"""
    heroes = session.exec(
        select(models.Hero).offset(offset).limit(limit)
    ).all()
    return heroes

@app.get("/heroes/{hero_id}", response_model=models.HeroRead)
def read_hero(
    hero_id: int,
    session: Session = Depends(get_db_session)
):
    """根据ID获取单个英雄"""
    hero = session.get(models.Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    return hero

@app.patch("/heroes/{hero_id}", response_model=models.HeroRead)
def update_hero(
    hero_id: int,
    hero: models.HeroUpdate,
    session: Session = Depends(get_db_session)
):
    """更新英雄信息"""
    db_hero = session.get(models.Hero, hero_id)
    if not db_hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    
    hero_data = hero.model_dump(exclude_unset=True)
    for key, value in hero_data.items():
        setattr(db_hero, key, value)
    
    session.add(db_hero)
    session.commit()
    session.refresh(db_hero)
    return db_hero

@app.delete("/heroes/{hero_id}")
def delete_hero(
    hero_id: int,
    session: Session = Depends(get_db_session)
):
    """删除英雄"""
    hero = session.get(models.Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    
    session.delete(hero)
    session.commit()
    return {"message": "Hero deleted successfully"}

@app.get("/heroes/search/", response_model=List[models.HeroRead])
def search_heroes(
    name: Optional[str] = None,
    min_age: Optional[int] = None,
    max_age: Optional[int] = None,
    session: Session = Depends(get_db_session)
):
    """搜索英雄(支持名称和年龄范围查询)"""
    query = select(models.Hero)
    
    if name:
        query = query.where(models.Hero.name.contains(name))
    if min_age is not None:
        query = query.where(models.Hero.age >= min_age)
    if max_age is not None:
        query = query.where(models.Hero.age <= max_age)
    
    heroes = session.exec(query).all()
    return heroes

📊 API功能特性说明

让我们通过表格来详细了解这个API的功能特性:

功能HTTP方法路径描述参数
创建英雄POST/heroes/创建新英雄HeroCreate模型
获取列表GET/heroes/获取英雄列表offset, limit
获取详情GET/heroes/{id}根据ID获取英雄hero_id
更新英雄PATCH/heroes/{id}部分更新英雄hero_id, HeroUpdate
删除英雄DELETE/heroes/{id}删除英雄hero_id
搜索英雄GET/heroes/search/条件搜索英雄name, min_age, max_age

🧪 测试API接口

启动开发服务器:

fastapi dev main.py

访问 http://127.0.0.1:8000/docs 查看自动生成的交互式API文档。

创建英雄示例

curl -X POST "http://127.0.0.1:8000/heroes/" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "钢铁侠",
    "secret_name": "托尼·斯塔克",
    "age": 48
  }'

获取英雄列表示例

curl -X GET "http://127.0.0.1:8000/heroes/?offset=0&limit=10"

搜索英雄示例

curl -X GET "http://127.0.0.1:8000/heroes/search/?name=钢铁&min_age=30"

🔍 高级功能扩展

1. 关系模型支持

SQLModel支持复杂的关系模型,让我们添加团队功能:

# models.py - 添加团队模型
class Team(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str

class HeroWithTeam(Hero):
    team: Optional[Team] = None

# 在Hero模型中添加团队关系
class Hero(HeroBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    team_id: Optional[int] = Field(default=None, foreign_key="team.id")

2. 异步支持

SQLModel也支持异步操作:

# database.py - 异步数据库配置
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlmodel import create_engine
from sqlalchemy.ext.asyncio import create_async_engine

async_engine = create_async_engine(
    "sqlite+aiosqlite:///./heroes.db",
    echo=True,
    connect_args={"check_same_thread": False}
)

async def get_async_session() -> AsyncSession:
    async with AsyncSession(async_engine) as session:
        yield session

3. 错误处理中间件

# main.py - 添加全局错误处理
from fastapi import Request
from fastapi.responses import JSONResponse

@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"detail": exc.detail}
    )

@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
    return JSONResponse(
        status_code=500,
        content={"detail": "Internal server error"}
    )

📈 性能优化建议

  1. 数据库连接池:配置合适的连接池大小
  2. 索引优化:为常用查询字段添加索引
  3. 查询优化:使用selectinload避免N+1查询问题
  4. 缓存策略:对频繁读取的数据添加缓存层

🎯 总结

通过本教程,你已经学会了:

✅ 如何使用SQLModel定义兼具ORM和数据验证功能的模型 ✅ 如何配置FastAPI与SQLModel的集成 ✅ 实现完整的CRUD RESTful API ✅ 使用依赖注入管理数据库会话 ✅ 实现分页、搜索等高级功能 ✅ 生成自动化API文档

SQLModel和FastAPI的组合为Python开发者提供了极佳的开发体验,让你能够快速构建高性能、类型安全的Web API。这种组合特别适合需要快速原型开发和保持代码质量的场景。

🚀 下一步行动

  1. 尝试添加身份验证和授权功能
  2. 实现文件上传和下载功能
  3. 添加单元测试和集成测试
  4. 部署到生产环境(Docker、Kubernetes等)
  5. 实现监控和日志记录

现在就开始使用SQLModel和FastAPI构建你的下一个API项目吧!

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值