Python 现代开发学习笔记

(十五)完成新增、修改与删除

1. 先把变化写进同一事务

查询已经能得到当前用户的记录,现在把模型中的变化应用到对象。下面接在 service.py 后面:

# 续写同一文件:api_project/src/bookapi/service.py
def create_book(session: Session, owner_id: int, data: BookCreate) -> BookOut:
    with commit_changes(session):
        book = Book(owner_id=owner_id, **data.model_dump())
        session.add(book)
        session.flush()  # 发出 INSERT,取回 id,此时事务还没提交。
        result = BookOut.model_validate(book)
    return result


def patch_book(
    session: Session, owner_id: int, book_id: int, data: BookPatch
) -> BookOut:
    book = owned_book(session, book_id, owner_id)
    with commit_changes(session):
        # 只取本次实际提交的字段,False 和 [] 都会被保留。
        for name, value in data.model_dump(exclude_unset=True).items():
            setattr(book, name, value)
        session.flush()
        result = BookOut.model_validate(book)
    return result


def delete_book(session: Session, owner_id: int, book_id: int):
    book = owned_book(session, book_id, owner_id)
    with commit_changes(session):
        session.delete(book)

新增时,data.model_dump() 给出书籍字段,** 把这些键值拆成构造函数参数;归属单独由服务端传入。flush() 后数据库编号可用,先用 BookOut 构造返回结果,再离开 commit_changes 提交。提交失败时抛出异常,函数不会返回成功结果。

部分修改先取到有权限的书,再遍历 exclude_unset=True 得到的变化。setattr(book, name, value) 相当于按字段名执行属性赋值。输入模型已经拒绝额外字段,所以这里处理的是 title、finished、tags 三个已知名称。False 会真正写入,空列表会真正清空标签,没出现的字段保留。

删除也先走归属检查,session.delete(book) 标记删除,提交时执行对应的 SQL。每个写入动作都有自己的事务;同一事务中的字段变化一起成功或回滚。

用书籍二改名为已存在书名展示更新失败,唯一键冲突触发回滚,下一请求重新读取仍是原书名;另一路成功修改在提交后返回

2. 把写入动作接到 HTTP

继续写入 routes/books.py:

# 续写同一文件:api_project/src/bookapi/routes/books.py
# 新增响应给出 Location,局部修改交给服务层,删除成功后返回不带正文的 204。
@router.post("", response_model=BookOut, status_code=201)
def add_book(
    data: BookCreate, session: DbSession, identity: CurrentIdentity, response: Response
):
    result = service.create_book(session, identity.user.id, data)
    response.headers["Location"] = f"/books/{result.id}"
    return result


@router.patch("/{book_id}", response_model=BookOut)
def update_book(
    book_id: BookId, data: BookPatch, session: DbSession, identity: CurrentIdentity
):
    return service.patch_book(session, identity.user.id, book_id, data)


@router.delete("/{book_id}", status_code=204)
def remove_book(book_id: BookId, session: DbSession, identity: CurrentIdentity):
    service.delete_book(session, identity.user.id, book_id)
    return Response(status_code=204)

POST /books 新建资源,成功返回 201,Location 指向新书路径。PATCH /books/{book_id} 只应用提交的变化,返回修改后的书。DELETE 成功交回 204,没有 JSON 正文,代码显式返回空 Response。

这里的 response: Response 由 FastAPI 提供,用来设置响应头;函数返回值仍交给响应模型序列化。Location 采用相对路径,调用者可以直接用同一 API 客户端继续读取它。

笔记正文与全部题解 · Ctrl / ⌘ K