Day 4 详细学习计划:完整 CRUD 操作实现

学习目标

  • 理解 RESTful API 设计原则
  • 实现完整的增删改查接口
  • 学习 FastAPI 的依赖注入系统
  • 掌握数据库会话(Session)管理

知识点详解

1. RESTful API 设计原则

核心概念:

  • 使用标准 HTTP 方法:GET(查询)、POST(创建)、PUT(更新)、DELETE(删除)
  • URI 表示资源:/articles 表示文章集合,/articles/1 表示特定文章
  • 无状态:每次请求应包含所有必要信息

常见的 HTTP 状态码:

  • 200 OK:请求成功
  • 201 Created:创建成功
  • 400 Bad Request:客户端请求错误
  • 404 Not Found:资源不存在
  • 500 Internal Server Error:服务器内部错误

2. FastAPI 依赖注入系统

概念:

  • 自动处理依赖关系
  • 减少重复代码
  • 提高代码可测试性和可维护性

常用场景:

  • 数据库会话管理
  • 认证和授权
  • 配置参数传递

3. 数据库会话管理

重要性:

  • 管理与数据库的连接
  • 控制事务边界
  • 确保数据一致性

练习代码

day4 目录中有以下文件:

database.py - 定义了数据库模型和连接配置
schemas.py - 定义了各种数据模型(基础模型、创建、更新、读取模型)
main.py - FastAPI 主应用程序,包含了所有 REST API 接口
第四天的内容.md - 学习笔记

database.py

from sqlmodel import SQLModel, create_engine, Field, Session
from typing import Optional
from datetime import datetime



# 数据库文件Url
DATABASE_URL = "sqlite:///./articles.db"

# 创建数据库引擎,echo=True表示打印SQL语句
engine = create_engine(DATABASE_URL, echo=True)

# 创建所有表
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

# 获取数据库会话
def get_session():
    with Session(engine) as session:
        yield session #yield作用是返回一个迭代器,迭代器中的数据是yield后面的数据

schemas.py

from sqlmodel import SQLModel, Field
from typing import Optional
from datetime import datetime

# 数据库模型和响应模型共用的基础模型
class ArticleBase(SQLModel):
    title: str
    content: str
    author: Optional[str] = None
    published: bool = False

# 数据库模型
class Article(ArticleBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    created_at: Optional[datetime] = None

# 创建文章时使用的模型
class ArticleCreate(ArticleBase):
    pass
# 更新文章时使用的模型
class ArticleUpdate(ArticleBase):
    title: Optional[str] = None
    content: Optional[str] = None
# 数据库返回的文章模型
class ArticleRead(ArticleBase):
    id: int
    created_at: Optional[datetime] = None

main.py

from fastapi import FastAPI, Depends, HTTPException, status
from sqlmodel import Session, select
from typing import List
from database import create_db_and_tables, get_session
from schemas import Article, ArticleCreate, ArticleUpdate, ArticleRead
from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 应用启动时创建数据库表
    create_db_and_tables()
    yield
    # 应用关闭时可以执行清理工作

app = FastAPI(title="Tutorial Site API", version="0.1.0", lifespan=lifespan)


# 创建文章接口
@app.post("/articles/", response_model=ArticleRead, status_code=status.HTTP_201_CREATED)
def create_article(*,
                   article: ArticleCreate, 
                   session: Session = Depends(get_session)):
    db_article = Article.from_orm(article)
    session.add(db_article)
    session.commit()
    session.refresh(db_article)
    return db_article

# 获取所有文章接口
@app.get("/articles/", response_model=List[ArticleRead])
def read_articles(*, session: Session = Depends(get_session)):
    articles = session.exec(select(Article)).all()
    return articles

# 根据ID获取单个文章接口
@app.get("/articles/{article_id}", response_model=ArticleRead)
def read_article(*, article_id: int, session: Session = Depends(get_session)):
    article = session.get(Article, article_id)
    if not article:
        raise HTTPException(status_code=404, detail="Article not found")
    return article

# 更新文章接口
@app.put("/articles/{article_id}", response_model=ArticleRead)
def update_article(*,
                   article_id: int,
                   article_update: ArticleUpdate,
                   session: Session = Depends(get_session)):
    article = session.get(Article, article_id)
    if not article:
        raise HTTPException(status_code=404, detail="Article not found")
    # 只更新提供了的字段
    article_data = article_update.dict(exclude_unset=True)
    for key, value in article_data.items():
        setattr(article, key, value)
    
    session.add(article)
    session.commit()
    session.refresh(article)
    return article
# 删除文章接口
@app.delete("/articles/{article_id}")
def delete_article(*,
                   article_id: int,
                   session: Session = Depends(get_session)):
    article = session.get(Article, article_id)
    if not article:
        raise HTTPException(status_code=404, detail="Article not found")
    
    session.delete(article)
    session.commit()
    return {"ok": True}

# 根目录欢迎信息
@app.get("/")
def read_root():
    return {"message": "Welcome to the tutorial site API!"}

Day3 vs Day4 对比

Day3 的结构

在 day3 中,你只有一个 database.py 文件,其中包含了 Article 模型的定义。这个模型同时承担了两个职责:

  • 作为数据库表结构定义(通过 table=True)
  • 作为 API 请求和响应的数据验证模型

Day4 的结构

在 day4 中,你引入了 schemas.py 来专门处理数据模型,这种做法有以下几个重要变化和好处:

主要变化和好处

1. 关注点分离 (Separation of Concerns)

  • Day3: 一个模型同时处理数据库操作和 API 数据验证
  • Day4: 不同用途使用不同模型,各司其职

2. 更精确的控制

schemas.py 中,你定义了多个专用模型:

  • ArticleBase: 基础字段定义
  • ArticleCreate: 创建文章时使用的模型
  • ArticleUpdate: 更新文章时使用的模型
  • ArticleRead: 返回给客户端的模型

3. 更好的 API 设计

  • Day3: 创建文章时必须提供所有字段,包括自动生成的 idcreated_at
  • Day4: ArticleCreate 只包含必要的字段,不需要提供 idcreated_at

4. 更灵活的数据验证

  • 更新操作使用 ArticleUpdate,所有字段都是可选的(通过 Optional),符合 PATCH 操作的语义
  • 读取操作使用 ArticleRead,确保返回给客户端的数据结构是明确定义的

5. 增强的安全性和数据完整性

  • 防止客户端意外或恶意地设置不应该由他们控制的字段(如 idcreated_at)
  • 明确区分哪些字段在创建时是必需的,哪些是可选的

实际效果示例

在 Day3 中

API 文档会显示创建文章需要提供所有字段,包括 idcreated_at:

{
  "id": 0,
  "title": "string",
  "content": "string",
  "author": "string",
  "published": true,
  "created_at": "2025-12-01T00:32:45.123Z"
}

在 Day4 中

API 文档只显示真正需要的字段:

{
  "title": "string",
  "content": "string",
  "author": "string",
  "published": true
}

这就是引入 schemas.py 的主要好处:

更清晰的代码结构
更好的 API 设计
更强的数据完整性和安全性

今天学习2个多小时,已经快1点了,今天的主要内容就是增加了个schemas.py就是有点好处,不过我感触不深,学的越来越浅了,烂

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐