title: FastAPI与Tortoise-ORM开发的神奇之旅

date: 2025/05/05 00:15:48

updated: 2025/05/05 00:15:48

author: cmdragon

excerpt:

FastAPI与Tortoise-ORM结合实现全链路开发,涵盖环境配置、数据模型定义、Pydantic模式设计及API端点实现。文章模型包含软删除功能,通过is_deleted字段实现逻辑删除。API支持创建、分页查询、条件更新和软删除操作,确保数据安全与完整性。分页机制通过offsetlimit实现,条件更新使用exclude_unset=True避免未传字段被覆盖。软删除通过filter().update()实现原子操作。系统具备用户认证、输入验证、防注入和细粒度权限控制等安全特性。

categories:

  • 后端开发
  • FastAPI

tags:

  • FastAPI
  • TortoiseORM
  • 全链路开发
  • 数据模型
  • API端点
  • 分页机制
  • 软删除


扫描二维码

关注或者微信搜一搜:编程智域 前端至全栈交流与成长

探索数千个预构建的 AI 应用,开启你的下一个伟大创意https://tools.cmdragon.cn/

从API到数据库:FastAPI + Tortoise-ORM全链路开发指南

1. 环境准备与包安装

首先创建Python虚拟环境并安装必要依赖:

python -m venv venv
source venc/bin/activate # Linux/Mac
venv\Scripts\activate # Windows pip install fastapi uvicorn tortoise-orm pydantic python-multipart

2. 数据模型定义

创建包含软删除功能的文章模型:

# models.py
from tortoise.models import Model
from tortoise import fields class Article(Model):
id = fields.IntField(pk=True)
title = fields.CharField(max_length=255)
content = fields.TextField()
author_id = fields.IntField()
created_at = fields.DatetimeField(auto_now_add=True)
updated_at = fields.DatetimeField(auto_now=True)
is_deleted = fields.BooleanField(default=False) class Meta:
table = "articles" class PydanticMeta:
exclude = ["is_deleted"]

3. Pydantic模式定义

定义请求响应数据结构:

# schemas.py
from pydantic import BaseModel
from datetime import datetime class ArticleCreate(BaseModel):
title: str
content: str class ArticleUpdate(BaseModel):
title: str | None = None
content: str | None = None class ArticleResponse(BaseModel):
id: int
title: str
content: str
author_id: int
created_at: datetime
updated_at: datetime class Config:
orm_mode = True

4. FastAPI应用配置

配置数据库连接和路由:

# main.py
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from tortoise.contrib.fastapi import register_tortoise app = FastAPI() # 数据库配置
DATABASE_URL = "sqlite://./db.sqlite3" register_tortoise(
app,
db_url=DATABASE_URL,
modules={"models": ["models"]},
generate_schemas=True,
add_exception_handlers=True,
) # 模拟用户认证
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") async def get_current_user(token: str = Depends(oauth2_scheme)):
return {"id": 1} # 模拟返回用户信息

5. 完整API端点实现

5.1 创建文章

@app.post("/articles", response_model=ArticleResponse)
async def create_article(
article: ArticleCreate,
user: dict = Depends(get_current_user)
):
db_article = await Article.create(
**article.dict(),
author_id=user["id"]
)
return await ArticleResponse.from_tortoise_orm(db_article)

5.2 分页查询

@app.get("/articles", response_model=list[ArticleResponse])
async def list_articles(
page: int = 1,
per_page: int = 10
):
skip = (page - 1) * per_page
query = Article.all().offset(skip).limit(per_page)
return await ArticleResponse.from_queryset(query)

5.3 条件更新

@app.patch("/articles/{article_id}", response_model=ArticleResponse)
async def update_article(
article_id: int,
update_data: ArticleUpdate,
user: dict = Depends(get_current_user)
):
# 只允许作者修改自己的文章
db_article = await Article.get_or_none(id=article_id, author_id=user["id"])
if not db_article:
raise HTTPException(status_code=404, detail="Article not found") await db_article.update_from_dict(update_data.dict(exclude_unset=True))
await db_article.save()
return await ArticleResponse.from_tortoise_orm(db_article)

5.4 软删除

@app.delete("/articles/{article_id}")
async def delete_article(
article_id: int,
user: dict = Depends(get_current_user)
):
updated_count = await Article.filter(
id=article_id,
author_id=user["id"]
).update(is_deleted=True) if not updated_count:
raise HTTPException(status_code=404, detail="Article not found")
return {"message": "Article deleted"}

6. 关键实现解析

6.1 分页机制原理

  • 计算skip值:skip = (page - 1) * per_page
  • 数据库查询使用offset和limit实现分页
  • 实际生产环境建议使用游标分页(Cursor Pagination)

6.2 条件更新安全

  • 通过get_or_none确保记录存在
  • 使用filter().update()实现原子操作
  • exclude_unset=True忽略未传字段

6.3 软删除实现

  1. 添加is_deleted字段
  2. 重写默认查询过滤器:
class SoftDeleteManager(fields.Model):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)

7. 课后Quiz

Q1:如何防止SQL注入攻击?

A:使用ORM的参数化查询,避免拼接SQL语句。Tortoise-ORM会自动处理查询参数,例如:

await Article.filter(title="Safe' OR 1=1--")

实际生成的SQL会是参数化查询,特殊字符会被正确转义

Q2:为什么在更新时使用exclude_unset=True?

A:该参数会忽略未提供的字段,实现部分更新。例如用户只修改标题时,不会影响content字段

Q3:如何实现真正的物理删除?

A:直接使用delete()方法:

await Article.filter(id=1).delete()

8. 常见报错解决方案

错误1:422 Validation Error

  • 原因:请求体不符合Pydantic模型要求
  • 解决:检查请求头Content-Type是否正确(应为application/json),确认字段类型和必填项

错误2:404 Not Found

  • 原因:尝试操作不存在的记录
  • 解决:在更新/删除前先使用get_or_none检查记录是否存在

错误3:RuntimeError - Event loop closed

  • 原因:在同步代码中调用异步方法
  • 解决:确保所有数据库操作都在async函数中执行,使用await调用

启动服务命令:

uvicorn main:app --reload

访问 http://localhost:8000/docs 即可测试API接口。本文实现的系统具备以下安全特性:

  1. 用户认证隔离数据访问
  2. 所有输入参数自动验证
  3. 数据库操作防注入
  4. 细粒度的权限控制

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:FastAPI与Tortoise-ORM开发的神奇之旅 | cmdragon's Blog

往期文章归档:

FastAPI与Tortoise-ORM开发的神奇之旅的更多相关文章

  1. ORM开发之解析lambda实现完整查询(附测试例子)

    上次讲解了怎么解析匿名对象(ORM开发之解析lambda实现group查询),这次来实现解析二元运算,完成基本条件语法 先看一个表达式 query.Where(b => b.Number == ...

  2. c++::Mysql::ORM 开发环境搭建

    官网地址:https://www.codesynthesis.com/products/odb/ 环境搭建:ubuntu16.04-64 1.安装mysqlClient sudo apt-get in ...

  3. ORM开发之解析lambda实现group查询(附测试例子)

    目的:以编程方式实现group查询,在开发ORM时,需要达到这样的效果 先看一个简单的group语句 select BarCode,ProductName,COUNT(BarCode) as tota ...

  4. ORM 开发环境之利器:MVC 中间件 FreeSql.AdminLTE

    前言 这是一篇纯技术干货的分享文章,FreeSql 已经基本完成 .NETCore 最方便的 ORM 使命,我们正在筹备生态的建立,比如 ABP 中如何使用 FreeSql 的实现,需要各种各样的扩展 ...

  5. 使用 kbmmw 的ORM开发纯REST数据库访问服务

    运行环境: WIN 10  X64 delphi 10.2.2 kbmmw 5.05.11 Firefox 58.0.2 今天使用最新的kbmmw 版本做一个基于ORM的纯数据库访问的REST 服务器 ...

  6. FastAPI:一个测试人员视角的教程

    前言 教程肯定谈不上了,主要还是就自己的理解分享内容而已 内容是连官方文档的基础教程都没涵盖起的 建议直接看官方文档 以个人视角来分享,希望各位通过这个可以写接口了 需要自取 完整视频链接:https ...

  7. FastAPI小项目实战:电影列表(Vue3 + FastAPI)

    假期过半, FastAPI + Vue3项目实战 视频也算录完了,尽管项目简单(2张表 共7个接口 4个页面) 起因 在6月底的时候开始录制了FastAPI官方文档中的新手教程部分(实际还没有官网文档 ...

  8. 自己开发轻量级ORM(二)

    上一篇简单的对轻量级ORM开发开了个头.这篇主要聊下ORM框架的设计思路. ORM本质上是对数据库操作的抽象.大体上我将其分为对数据结构的抽象和对执行方法的抽象. 我的ORM设计图: ORM框架需要完 ...

  9. FastAPI项目实战:"异步"接口测试"平台"

    apiAutoTestWeb 是什么? apiAutoTest接口自动化测试工具的可视化版本,将原本对用例的操作转移到Web页面之上 用什么实现? 接口自动化测试:大体上测试逻辑将采用apiAutoT ...

  10. FastAPI + Vue 前后端分离 接口自动化测试工具 apiAutoTestWeb

    apiAutoTestWeb使用说明 apiAutoTestWeb是为apiAutoTest的可视化版本,其采用前后端分离(FastAPI + Vue2)方式实现 具体使用: Python3 + Fa ...

随机推荐

  1. OpenLayers 绘制带箭头的LineString

    <!--******************************************************************** * Copyright 2000 - 2022 ...

  2. Ansible - [04] 关于sudo的一些配置

    sudo sudo,以超级管理员或其他人的身份执行命令 基本流程 管理员需要先授权(修改/etc/sudoers文件) 普通用户以sudo的形式执行命令 可以通过sudo -l查看授权情况 配置sud ...

  3. CF1793E题解

    \(\text{Problem - 1793E - Codeforces}\) \(\text{*2600}\) 备注 2024.10.19 考试 T2.考场未能想出正解,找到性质但没有根据性质往 d ...

  4. PHP 命名空间与spl_autoload_register() 自动加载机制

    转:https://www.cnblogs.com/chihuobao/p/9895202.html include 和 require 是PHP中引入文件的两个基本方法.在小规模开发中直接使用 in ...

  5. Ubuntu 22.04 添加 AppImage 到应用程序

    前言 AppImage 逐渐成为 Linux 常用的一种软件包格式,本文将介绍如何将 AppImage 文件添加到 Ubuntu 的应用程序中. 如下图中的 CAJViewer : 操作过程 设置相关 ...

  6. 记录-C#给图片增加文字

    业务需要动态给图片增加文字(书本的封面图片),修改字体大小.字体.颜色.控制位置 测试代码: 1 string path = @"E:\cover.png"; 2 3 Bitmap ...

  7. 安卓线性布局LinearLayout

    1.weight权重解读 用法归纳: 按比例划分水平方向:将涉及到的View的android:width属性设置为0dp,然后设置为android weight属性设置比例即可. ` <Line ...

  8. 【moectf】wp

    http 用wsrx打开靶机 根据任务描述 得到moectf{basic_http_knowledge_oen0w64iYZqSeT6BT8c9OgFTV-KNMT6g} Web入门指北 打开pdf文 ...

  9. AI 智能体引爆开源社区「GitHub 热点速览」

    最近很火的 Manus 智能体是一款将你的想法转化为行动的工具,能够处理生活中的各种任务.一经发布便迅速走红,并间接引爆了开源社区. 这也导致上榜的全是 AI 智能体开源项目,比如无需邀请码的开源版 ...

  10. css3 渐变边框如何实现圆角效果

    常规的 border-image 属性如果直接使用 border-radius 会无效,关于如何实现渐变边框圆角,网上流传着大概这么几种办法: 渐变背景方式(仅适用于纯底色背景) 借助 after 伪 ...