FastAPI 快速开发 Web API 项目: 响应模型与错误处理

1,362 阅读8分钟

FastAPI 快速开发 Web API 项目学习笔记:

介绍

在前面的文章中,我们可以通过注释路径操作函数返回类型来声明用于响应的类型。

因此,我们可以像在函数参数中输入数据一样使用类型注释,使用 Pydantic 模型、列表、字典、标量值(如整数、布尔值等)。

响应模型

响应模型作为模板,用于从 API 路由的路径返回数据。它们建立在 Pydantic 上,以正确解析从客户端到服务器的请求响应。

Responses 是 API 生命周期的一个组成部分。响应是通过任何标准的 HTTP 方法与 API 路由交互而收到的反馈。一个 API 响应通常是 JSON 或 XML 格式,但它也可以是文档形式。响应一般由一个头和一个响应体组成。

响应头

响应头由请求的状态和附加信息组成,以指导响应体的交付。响应头中包含的信息的一个例子是 头中包含的信息的一个例子是 Content-Type,它告诉客户端返回的内容类型。

响应体

另一方面,响应体是客户端向服务器请求的数据。

响应体是由 Content-Type 头变量决定的,最常用的是 application/json 。

建立 FastAPI 响应模型

FastAPI 可以为 response_model 定义 API 输出的数据格式:

  1. 对 API 响应返回的数据进行验证
  2. 转化输出数据对应 response_model 所定义的数据模型,它将限制和过滤输出数据,使之符合返回类型中的定义。这对于 API 安全性尤为重要
  3. 可以根据 response_model 指定的数据格式,限制可以输出的数据:如果数据是无效的(例如,你缺少一个字段),这意味着你的应用程序代码被破坏了,没有返回它应该返回的东西,它将返回一个服务器错误,而不是返回错误的数据
  4. 为响应添加一个 JSON schema,在 OpenAPI 的路由操作中上体现,方便开发者可以直接从文件上看到 API 需要的文件格式,它也将被自动客户端代码生成工具所使用

之前的文章中学习了如何使用 Pydantic 来构建模型。在 FastAPI 中,响应模型也是建立在 Pydantic 上的,但其目的不同。例如,在路由路径的定义中,我们有以下内容:

@todo_router.get("/todo")
async def retrieve_todos() -> dict:
    return {
        "todos": todo_list
    }

该路由返回数据库中存在的待办事项的列表。下面是一些输出的例子:

{
  "todos": [
    {
      "id": 1,
      "name": "write a todo",
      "item": "write a todo project"
    },
    {
      "id": 2,
      "name": "learning path",
      "item": "learning path parameters"
    },
    {
      "id": 3,
      "name": "learning query",
      "item": "learning query parameters"
    }
  ]
}

该路由返回存储在 todos 数组中的所有内容。为了指定要返回的信息,我们必须把要显示的数据分开,或者引入额外的逻辑。幸运的是,我们可以创建一个包含我们想要返回的字段的模型,并使用 response_model 参数将其添加到我们的路由定义中。

先定义一个新的模型类来返回待办事项的列表,在 model.py 中进行代码变更:

from pydantic import BaseModel, StrictInt
from typing import List


class Todo(BaseModel):
    id: StrictInt
    name: str
    item: str


class TodoItem(BaseModel):
    item: str
    class Config:
        schema_extra = {
            "example": {
                "item": "test"
            }
        }
        

class TodoItems(BaseModel):
    todos: List[TodoItem]
    class Config:
        schema_extra = {
            "example": {
                "todos": [
                    {
                        "item": "Example schema 1!"
                    },
                    {
                        "item": "Example schema 2!"
                    }
                ]
            }
        }
        
$ curl -X 'POST' 'http://127.0.0.1:8888/todo' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"id": 1,"name": "test", "item": "This todo will be retrieved without exposing my ID!"}'
{"message":"Todo added successfully"}
$ curl -X 'GET' 'http://127.0.0.1:8888/todo' -H 'accept: applicatiion/json'
{"todos":[{"item":"This todo will be retrieved without exposing my ID!"}]}

状态码

状态码是服务器为响应客户的请求而发出的独特的短代码。 在 HTTP 中,您发送一个 3 位数的数字状态代码作为响应的一部分。这些状态代码有一个关联的名称来识别它们,但重要的部分是数字。 响应状态代码被分为五类,每一类表示不同的响应:

  • 100 及以上为“信息”。你很少直接使用它们。具有这些状态代码的响应不能有正文。
  • 200 及以上表示“成功”响应。这些是您最常使用的。
    • 200 是默认状态代码,表示一切正常。
    • 另一个例子是 201,“已创建”。它通常在数据库中创建新记录后使用。
    • 一个特例是 204,“无内容”。当没有内容返回给客户端时使用此响应,因此响应不能有正文。
  • 300 及以上用于“重定向”。具有这些状态代码的响应可能有也可能没有正文,但 304“未修改”除外,它不能有正文。
  • 400 及以上用于“客户端错误”响应。这些是您可能最常使用的第二种类型。
    • 一个示例是 404,表示“未找到”响应。
    • 对于来自客户端的一般错误,您可以只使用 400。
  • 500 及以上是服务器错误。你几乎从不直接使用它们。当您的应用程序代码或服务器的某个部分出现问题时,它会自动返回这些状态代码之一。

总结就是:

  • 1XX: 请求被接收
  • 2XX: 请求成功
  • 3XX: 请求重定向
  • 4XX: 客户端错误
  • 5XX: 服务端错误

完整的状态码列表可以点此处

Snipaste_2023-04-10_11-40-11.png

在构建 Web 应用程序时,不管是什么框架,所遵循的标准做法是为各个事件返回适当的状态代码。400 状态码不应该被返回给服务器错误。同样地,200 状态码也不应该在请求操作失败时返回。

定义HTTP状态码

除了定义 response_model 之外,也可以在路由操作中确定 API 预期返回的 HTTP 状态码:

@app.post( '/user/login' , response_model=UserBase, status_code= 201 ) 
def  login ( user_in: UserIn ):返回user_in    
    

如果 HTTP 状态码记不过来,但是知道状态消息类别,也可以使用 FastAPI 提供的状态:

from fastapi import FastAPI, status 
@app.post( '/user/login' , response_model=UserBase, status_code=status.HTTP_201_CREATED ) 
def  login ( user_in: UserIn ):返回user_in
    

FastAPI 将使用该时间响应来提取状态代码(还有 cookie 和标头),并将它们放入包含您返回的值的最终响应中,由任何 response_model 过滤。 您也可以在依赖项中声明 Response 参数,并在其中设置状态码。但请记住,最后设置的将获胜。

错误处理

错误处理包括在处理应用程序的错误时涉及的实践和活动。这些做法包括返回适当的错误状态代码和错误信息。

请求可能会返回错误的响应,这些响应可能是意料之外的,或者对请求失败原因的信息不足。请求的错误可能是由于试图访问不存在的资源、没有足够权限的受保护页面,甚至是服务器错误。FastAPI 中的错误是通过使用 FastAPI 的 HTTPException 类引发一个异常来处理的:

  • 状态码:返回失败的状态码
  • 描述:将向客户发送的附带信息
  • 头:一个可选的参数,用于需要头信息的响应

image.png

在我们的待办事项路由定义中,当一个待办事项找不到时,我们会返回一个消息。我们将更新它以引发 HTTPException 。HTTPException 允许我们返回一个适当的错误响应代码。

如果根据上一节路由中的方法,如果访问一个 id 不存在的待办事项: 1111,我们虽然会返回一个错误信息,但是状态码是返回 200,这与状态码的规则是不相符的,如图所示:

HTTP/1.1 200 OK
content-length: 50
content-type: application/json
date: Mon, 10 Apr 2023 06:42:15 GMT
server: uvicorn

{"message":"Todo with supplied ID doesn't exist."}

image.png

因此,我们需要修改一下代码,使用 HTTPException,如下:

@todo_router.get("/todo/{todo_id}")
async def get_single_todo(todo_id: int = Path(...,
                                              title="The ID of the todo to be retrieved.")) -> dict:
    for todo in todo_list:
        # if todo["id"] == todo_id:
        if todo.id == todo_id:
            return {
                "todo": todo
            }
    # return {
    #     "message": "Todo with supplied ID doesn't exist."
    # }
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail= "Todo with supplied ID doesn't exist",
    )

此时,我们返回的状态码就是 404 Not Found,

image.png

总结

本文学习了什么是响应和响应模型,以及错误处理的含义,并了解了 HTTP 状态码以及它的含义。并利用 FastAPI 创建了一个响应模型,只返回待办事项列表中的 item ,而不返回其 id。最后,我们学习了错误和错误处理,让 Todo 应用在发生查不到的情况返回 404 错误,而不是 200 状态码。

希望本文能对你有所帮助,如果喜欢本文,可以点个关注.

下一篇文章见!宇宙古今无有穷期,一生不过须臾,当思奋争。

本文正在参加「金石计划」

参考链接: