fastapi教程(四):做出响应

请求体现的是后端的数据服务能力,而响应体现的是后端向前端的数据展示能力。

一,一个完整的web响应应该包含哪些东西

一个完整的 Web 响应通常包含以下几个主要部分:

1. 状态行
   - HTTP 版本
   - 状态码
   - 状态消息

例如:`HTTP/1.1 200 OK`

2. 响应头
常见的响应头包括:

   a. 通用头部:
      - Date: 响应生成的日期和时间
      - Connection: 连接状态(如 keep-alive 或 close)

   b. 响应特定头部:
      - Server: 服务器软件名称和版本
      - Content-Type: 响应体的 MIME 类型
      - Content-Length: 响应体的长度(以字节为单位)
      - Content-Encoding: 响应体的编码方式(如 gzip)

   c. 实体头部:
      - Last-Modified: 资源的最后修改日期
      - ETag: 资源的唯一标识符
      - Expires: 资源的过期时间
      - Cache-Control: 缓存控制指令

   d. 安全相关头部:
      - Set-Cookie: 设置 HTTP cookie
      - X-XSS-Protection: 控制浏览器的 XSS 筛选器
      - X-Frame-Options: 控制页面是否可以被嵌入框架
      - Content-Security-Policy: 内容安全策略
      - Strict-Transport-Security: 强制使用 HTTPS

   e. 跨域相关头部:
      - Access-Control-Allow-Origin: 指定允许跨域请求的源
      - Access-Control-Allow-Methods: 允许的 HTTP 方法
      - Access-Control-Allow-Headers: 允许的请求头

3. 空行
   用于分隔头部和响应体

4. 响应体
   - 包含请求的资源或处理结果
   - 格式取决于 Content-Type(如 HTML、JSON、XML、图片等)

示例:

HTTP/1.1 200 OK
Date: Mon, 23 May 2023 12:28:53 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: application/json; charset=utf-8
Content-Length: 234
Cache-Control: max-age=3600
ETag: "686897696a7c876b7e"
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains

{
  "id": 12345,
  "name": "Example Product",
  "description": "This is an example product description.",
  "price": 99.99,
  "inStock": true
}

注意事项:

  1. 并非所有响应都需要包含响应体(如 204 No Content)。
  2. 响应头的具体内容会根据请求和应用需求而变化。
  3. 在实际应用中,应当根据安全需求和性能考虑来选择合适的响应头。
  4. 对于流式响应或大文件传输,可能会使用分块传输编码(Transfer-Encoding: chunked)。
  5. 在设计 API 时,应考虑响应的一致性,包括错误处理和状态码的使用。

二,响应

(一)简单响应

就像前面我们定义的路由处理函数一样,可以通过返回一个字典来返回一个简单的响应内容:

import uvicorn
from fastapi import FastAPI, Header

app = FastAPI()


@app.get("/test/")
def test():
    response = {"message": "Hello, World!"}
    print(type(response))   # -> <class 'dict'>
    return response


if '__main__' == __name__:
    uvicorn.run(app, host='127.0.0.1', port=8088)

通过打印的内容我们可以看到,返回的是一个 Python 字典,来看看 FastAPI 是怎么处理它的:
在这里插入图片描述

具体到不同的客户端情况:
1,使用浏览器直接访问:

  • 你会看到 JSON 格式的文本

2,使用 JavaScript/Ajax:

fetch('http://127.0.0.1:8088/test/')
  .then(response => response.json())
  .then(data => console.log(typeof data, data));
// 输出:object {message: "Hello, World!"}
  • 接收到的是 JavaScript 对象

3,使用 Python requests 库:

import requests
response = requests.get('http://127.0.0.1:8088/test/')
data = response.json()
print(type(data), data)
# 输出:<class 'dict'> {'message': 'Hello, World!'}
  • 解析后得到的是 Python 字典

4,使用 curl 命令行工具:

curl http://127.0.0.1:8088/test/
# 输出:{"message": "Hello, World!"}
  • 接收到的是 JSON 格式的字符串

(二)响应数据模型

我们可以定义请求体的数据模型来接收请求体的数据,同样也可以定义响应数据模型来规范响应数据。

  • 响应数据模型在 API 文档页中为 JSON 格式。

只需要在任意的路由处理函数中使用 response_model 参数来声明用于响应的模型。
FastAPI 将使用此 response_model 来:

  • 将输出数据转换为其声明的类型。
  • 校验数据。
  • 在 OpenAPI 的路径操作中为响应添加一个 JSON Schema。
  • 并在自动生成文档系统中使用。

1,定义与使用响应数据模型

首先同样使用 pydantic 定义响应数据模型:

import uvicorn

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


# 1,定义请求提数据模型
class User(BaseModel):
    first_name: str
    last_name: str


# 2,定义响应数据模型
class UserExtra(BaseModel):
    first_name: str
    last_name: str
    full_name: str | None = None


# 处理请求
@app.post("/create-user/", response_model=UserExtra)
async def create_user(user: User) -> Any:
    # 4,处理请求数据
    full_name = user.first_name + ' ' + user.last_name

    # 5,返回数据
    return {
        "first_name": user.first_name,
        "last_name": user.last_name,
        "full_name": full_name
    }


if '__main__' == __name__:
    uvicorn.run(app, host='127.0.0.1', port=8088)

之所以要将响应模型放在参数中声明,而不是放在函数返回值中使用,是因为路由处理函数可能不会真正返回响应模型(可能是一个 dict、数据库对象或其他模型),这是就可以使用 response_model 来执行字段约束和序列化。

查看 API:
在这里插入图片描述
在这里插入图片描述
当路由处理函数的返回值无法被 response_model 处理成满足响应模型的数据的时候,就会报错:

import uvicorn

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


# 1,定义请求提数据模型
class User(BaseModel):
    first_name: str
    last_name: str


# 2,定义响应数据模型
class UserExtra(BaseModel):
    first_name: str
    last_name: str
    full_name: str | None = None


# 处理请求
@app.post("/create-user/", response_model=UserExtra)
async def create_user(user: User):
    # 4,处理请求数据
    full_name = user.first_name + ' ' + user.last_name

    # 5,返回数据
    return {
        "full_name": full_name
    }


if '__main__' == __name__:
    uvicorn.run(app, host='127.0.0.1', port=8088)

在这里插入图片描述
在这里插入图片描述
当路由处理函数的返回值“太丰富”时,response_model 还会自动过滤掉无关内容:

import uvicorn

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


# 1,定义请求提数据模型
class User(BaseModel):
    first_name: str
    last_name: str


# 2,定义响应数据模型
class UserExtra(BaseModel):
    first_name: str
    last_name: str
    full_name: str | None = None


# 处理请求
@app.post("/create-user/", response_model=UserExtra)
async def create_user(user: User):
    # 4,处理请求数据
    full_name = user.first_name + ' ' + user.last_name

    # 5,返回数据
    return {
        "first_name": user.first_name,
        "last_name": user.last_name,
        "full_name": full_name,
        "extra": "extra"
    }


if '__main__' == __name__:
    uvicorn.run(app, host='127.0.0.1', port=8088)

在这里插入图片描述

2,对响应数据模型的控制

如果我们在定义响应数据模型时使用了默认值,通常情况下,会在请求响应中补上这些字段并使用其默认值。

如果组要排除这些字段的出现,可以使用 response_model_exclude_unset 参数:

from typing import Any
import uvicorn

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


# 1,响应数据模型
class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = 1111
    tags: list[str] = ['tag1', 'tag2']


@app.get("/items/", response_model=list[Item], response_model_exclude_unset=True)
async def read_items() -> Any:
    return [
        {"name": "Portal Gun", "price": 42.0},
        {"name": "Plumbus", "price": 32.0, "tax": 3.2},
    ]


if __name__ == '__main__':
    uvicorn.run(app)

这些控制字段来自 pydantic,还有:

  • response_model_exclude_unset=True:排除未设置的字段;
  • response_model_exclude_defaults=True:排除默认值的字段;
  • response_model_exclude_none=True:排除值为 None 的字段;
  • response_model_exclude={}:排除指定的字段,{}是一个set
  • response_model_include={}:包含指定的字段,{}是一个set

fastapi 官方更建议使用多个响应数据模型而不是疯狂使用上面这些个参数:因为即使使用 response_model_includeresponse_model_exclude 来省略某些属性,在应用程序的 OpenAPI 定义(和文档)中生成的 JSON Schema 仍将是完整的模型。

3,业务数据模型

在实际场景中,获得请求数据后会经过一系列业务处理后才能生成响应数据,这个过程中往往还需要使用到其他数据模型。

例如,在处理用户注册业务的路由处理函数中:

  • 接受请求数据的请求数据模型包含用户名和密码
  • 业务模型包含用户名、密码的哈希值
  • 响应数据模型包含用户名

通常来说,我们应该将各个数据模型独立出来,形成请求数据模型、业务数据模型和响应数据模型。

from datetime import datetime

import uvicorn

from typing import Any, Optional
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class UserIn(BaseModel):
    """
    请求数据模型
    """
    username: str  # 必填
    password: str  # 必填
    # 以下字段非必填
    email: Optional[str] = None
    phone: Optional[str] = None
    address: Optional[str] = None


class UserDb(BaseModel):
    """
    数据库业务模型
    """
    username: str
    password_hash: str
    email: Optional[str] = None
    phone: Optional[str] = None
    address: Optional[str] = None
    update_time: datetime = datetime.now()


class UserOut(BaseModel):
    """
    响应数据模型
    """
    username: str
    email: Optional[str] = None
    phone: Optional[str] = None
    address: Optional[str] = None
    update_time: datetime


def get_password_hash(password: str) -> str:
    """
    假设这是一个加密函数
    """
    return password + 'hash'


def save_user_login(user: UserIn) -> UserDb:
    """
    假设这是一个保存用户登录的函数
    """
    password_md5 = get_password_hash(user.password)
    user_db = UserDb(**user.model_dump(), password_hash=password_md5)
    print(user_db)
    return user_db


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
    """
    创建用户
    """
    user_saved = save_user_login(user)
    return user_saved


if '__main__' == __name__:
    uvicorn.run(app, host='127.0.0.1', port=8088)

在这里插入图片描述
在这里插入图片描述

4,简化数据模型

在上面的代码中,我们定义了三个数据模型,它们有许多相似的字段,我们可以使用继承来简化这些数据模型。

import uvicorn

from datetime import datetime
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class UserBase(BaseModel):
    username: str
    email: Optional[str] = None
    phone: Optional[str] = None
    address: Optional[str] = None


class UserIn(UserBase):
    password: str


class UserDb(UserBase):
    password_hash: str
    update_time: datetime = Field(default_factory=datetime.now)


class UserOut(UserBase):
    update_time: datetime


# 其余的函数和路由保持不变
def get_password_hash(password: str) -> str:
    return password + 'hash'


def save_user_login(user: UserIn) -> UserDb:
    password_hash = get_password_hash(user.password)
    return UserDb(**user.model_dump(exclude={'password'}), password_hash=password_hash)


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
    return save_user_login(user)


if '__main__' == __name__:
    uvicorn.run(app, host='127.0.0.1', port=8088)

5,使用多个响应模型

在以下情况下,我们可能需要在响应中返回多个数据模型:

  • 错误处理:当API可能返回成功响应或错误响应时。
  • 条件响应:基于某些条件(如查询参数)返回不同的响应模型。
  • 版本控制:API的不同版本可能返回不同的响应结构。
  • 多态响应:根据请求的资源类型返回不同的响应模型。

这时可以使用 Union 类型将不同的响应模型组合在一起。

from typing import Union

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class UserProfile(BaseModel):
    username: str
    email: str
    is_active: bool


class ErrorMessage(BaseModel):
    error_code: int
    message: str


@app.get("/user/{user_id}", response_model=Union[UserProfile, ErrorMessage])
async def get_user(user_id: int):
    if user_id == 1:
        return UserProfile(username="john_doe", email="john@example.com", is_active=True)
    else:
        return ErrorMessage(error_code=404, message="User not found")


# 另一个例子:根据查询参数返回不同的响应
class BasicUserInfo(BaseModel):
    username: str
    email: str


class DetailedUserInfo(BaseModel):
    username: str
    email: str
    phone: str
    address: str


@app.get("/user_info", response_model=Union[BasicUserInfo, DetailedUserInfo])
async def get_user_info(user_id: int, detailed: bool = False):
    if detailed:
        return DetailedUserInfo(username="jane_doe", email="jane@example.com",
                                phone="123-456-7890", address="123 Main St")
    else:
        return BasicUserInfo(username="jane_doe", email="jane@example.com")


if '__main__' == __name__:
    uvicorn.run(app, host='127.0.0.1', port=8088)

在上面的示例中,我展示了两种使用 Union 类型的情况:

  1. 错误处理:get_user 函数可能返回 UserProfile 或 ErrorMessage。
  2. 条件响应:get_user_info 函数根据 detailed 参数返回 BasicUserInfo 或 DetailedUserInfo。

使用 Union 类型的好处包括:

  • 类型安全:它明确定义了可能的响应类型,有助于静态类型检查。
  • 文档清晰:FastAPI 会自动生成包含所有可能响应的 API 文档。
  • 灵活性:允许在不同情况下返回不同的响应结构,而不需要创建一个包含所有可能字段的大型模型。

需要注意的是,当使用 Union 类型时,客户端需要能够处理不同的响应结构。

查看 API:
在这里插入图片描述
在这里插入图片描述

(三)内置的响应类

在路由处理函数中,我们可以直接返回基础数据类型、用 Union 构造的泛型,或者使用 Pydantic 定义的响应数据模型。
fastapi 会将这些数据转换为 JSON 格式的响应数据,然后通过响应类返回给客户端。

除此之外,FastAPI还提供了一些内置的响应类,用来返回更多类型的响应。

1,纯文本响应——Plain Text Response

fastapi 使用 PlainTextResponse 类来返回纯文本响应,不会对纯文本的内容进行校验与转换。

import uvicorn

from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI()


@app.get("/", response_class=PlainTextResponse)
async def read_root():
    return "Hello, world!"


if __name__ == '__main__':
    uvicorn.run(app)

2,HTML 响应——HTML Response

fastapi 使用 HTMLResponse 类来返回 HTML 响应,不会对 HTML 的内容进行校验与转换。

import uvicorn

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

html_content = """
<html>
    <head>
        <title>FastAPI</title>
    </head>
    <body>
        <h1>Hello, FastAPI!</h1>
    </body>
</html>
"""


@app.get("/type1", response_class=HTMLResponse)
async def read_root():
    """
    响应数据的 media type 为 text/html
    """
    return html_content


@app.get("/type2")
async def read_root():
    """
    响应数据的 media type 为 application/json
    """
    return HTMLResponse(content=html_content)


if __name__ == '__main__':
    uvicorn.run(app)

3,重定向响应——Redirect Response

fastapi 使用 RedirectResponse 类来返回重定向响应,跳转到指定的 URL。

import uvicorn

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/")
def read_root():
    return RedirectResponse(url='/docs')


@app.get("/github")
def read_github():
    return RedirectResponse(url='https://github.com/')


if __name__ == '__main__':
    uvicorn.run(app)

4,JSON响应——JSON Response

默认情况下,fastapi 使用 json_encoder() 将模型数据转为 JSON 格式,然后使用 JSONResponse 类返回这些数据。

import uvicorn

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse


app = FastAPI()


class UserModel(BaseModel):
    name: str
    age: int
    address: str
    designation: Optional[str] = None

@app.post("/user")
async def update_user(user: UserModel):
    json_data = jsonable_encoder(user)
    return JSONResponse(content=json_data)


if __name__ == '__main__':
    uvicorn.run(app)

5,通用响应——Response

当返回的数据不是 JSON 格式时,可以使用 Response 类来返回通用响应。

import uvicorn

from fastapi import FastAPI, Response

app = FastAPI()


@app.get("/item/{item_id}")
async def get_xml_data(item_id: int):
    data = """<Document>
        <Name>John Doe</Name>
        <Age>25</Age>
        <City>San Francisco</City>
    </Document>
    """
    return Response(content=data, media_type="application/xml")


if __name__ == '__main__':
    uvicorn.run(app)

对于通用响应,我们在实例化 Response 类时需要指定一些额外的参数:

  • content:响应数据的内容;
  • status_code:响应状态码,默认为 200;
  • media_type:响应数据的 media type;自动生成一个 Content-Type 头信息;
  • headers:响应头信息;自动包含一个 Content-Length 头信息,用于指定响应数据的长度。

5,流式响应——StreamingResponse

字节流响应的数据是二进制格式的,用于传输音频、视频、图像等二进制数据。
fastapi 使用 StreamingResponse 类来返回流式响应。

import uvicorn

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


@app.get("/get_video")
async def get_file():
    file_path = "1-1 本周整体内容介绍和学习方法.mp4"
    file_like = open(file_path, mode="rb")
    return StreamingResponse(file_like, media_type="video/mp4")

if __name__ == '__main__':
    uvicorn.run(app)

在浏览器访问 http://127.0.0.1:8000/get_video 就可以浏览视频内容了。

6,文件响应——FileResponse

fastapi 使用 FileResponse 类来处理异步文件响应,用于传输文件。
相比于 StreamingResponseFileResponse 更适合传输文件,因为它可以接受更多的参数:

  • path:文件路径;
  • filename:文件名;将自动添加 Content-Disposition 头信息;
  • media_type:文件的 media type;将自动添加 Content-Type 头信息;
  • headers:响应头信息;

两者最大的区别是,FileResponse 是异步读取文件的,不会因为一次性读出文件太大而导致内存溢出。

import uvicorn

from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()


@app.get("/get_file")
async def get_file():
    file_path = "教程.md"
    return FileResponse(file_path, media_type="application/octet-stream", filename="教程.md")
    

if __name__ == '__main__':
    uvicorn.run(app)

三,响应状态码

(一)HTTP 状态码

1xx: 信息响应

状态码含义描述
100Continue(继续)服务器已收到请求的初始部分,客户端应继续请求。
101Switching Protocols(切换协议)服务器理解并同意客户端的协议切换请求。
102Processing(处理中)服务器已收到并正在处理请求,但无响应可用。
103Early Hints(预先提示)用于与Link头一起返回一些响应头,主要用于预加载。

2xx: 成功响应

状态码含义描述
200OK(成功)请求成功。GET: 资源已被提取并在消息正文中传输。
201Created(已创建)请求成功且服务器创建了新的资源。
202Accepted(已接受)服务器已接受请求,但尚未处理。
203Non-Authoritative Information(非授权信息)服务器已成功处理请求,但返回的信息可能来自另一来源。
204No Content(无内容)服务器成功处理请求,但不需要返回任何实体内容。
205Reset Content(重置内容)服务器成功处理请求,但需要重置文档视图。
206Partial Content(部分内容)服务器成功处理了部分GET请求。

3xx: 重定向消息

状态码含义描述
300Multiple Choices(多种选择)针对请求,服务器可执行多种操作。
301Moved Permanently(永久移动)请求的资源已永久移动到新位置。
302Found(临时移动)请求的资源临时从不同的URI响应请求。
303See Other(查看其他位置)对应当前请求的响应可以在另一个URI上被找到。
304Not Modified(未修改)资源未被修改,可使用缓存版本。
307Temporary Redirect(临时重定向)请求应该被重定向到另一个URI,但将来的请求仍应使用原始URI。
308Permanent Redirect(永久重定向)请求和所有将来的请求应该被重定向到给定的URI。

4xx: 客户端错误响应

状态码含义描述
400Bad Request(错误请求)服务器无法理解请求的语法。
401Unauthorized(未授权)请求要求身份验证。
402Payment Required(需要付款)保留以供将来使用。
403Forbidden(禁止)服务器理解请求但拒绝执行。
404Not Found(未找到)服务器找不到请求的资源。
405Method Not Allowed(方法不允许)请求方法不被允许。
406Not Acceptable(不可接受)服务器无法根据客户端请求的内容特性完成请求。
407Proxy Authentication Required(需要代理授权)请求要求代理的身份认证。
408Request Timeout(请求超时)服务器等候请求时发生超时。
409Conflict(冲突)由于和被请求的资源的当前状态之间存在冲突,请求无法完成。
410Gone(已删除)被请求的资源在服务器上已经不再可用。
411Length Required(需要有效长度)服务器拒绝在没有定义Content-Length头的情况下接受请求。
412Precondition Failed(前提条件失败)服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。
413Payload Too Large(负载过大)请求实体过大,超出服务器的处理能力。
414URI Too Long(URI过长)请求的URI过长,服务器无法处理。
415Unsupported Media Type(不支持的媒体类型)请求的格式不受请求页面的支持。
416Range Not Satisfiable(范围不符合要求)页面无法提供请求的范围。
417Expectation Failed(未满足期望)服务器无法满足Expect的请求头信息。
429Too Many Requests(请求过多)用户在给定的时间内发送了太多的请求。

5xx: 服务器错误响应

状态码含义描述
500Internal Server Error(服务器内部错误)服务器遇到了不知道如何处理的情况。
501Not Implemented(尚未实施)服务器不具备完成请求的功能。
502Bad Gateway(错误网关)作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
503Service Unavailable(服务不可用)由于超载或系统维护,服务器暂时的无法处理客户端的请求。
504Gateway Timeout(网关超时)充当网关或代理的服务器,未及时从上游服务器收到请求。
505HTTP Version Not Supported(HTTP版本不受支持)服务器不支持请求中所用的HTTP协议版本。
506Variant Also Negotiates(变元协商)服务器存在内部配置错误。
507Insufficient Storage(存储空间不足)服务器无法存储完成请求所必须的内容。
508Loop Detected(检测到循环)服务器在处理请求时检测到无限循环。
510Not Extended(未扩展)获取资源所需要的策略并没有被满足。
511Network Authentication Required(要求网络认证)客户端需要进行身份验证才能获得网络访问权限。

HTTP response status codes

(二)fastapi 中的状态码

在fastapi中,有多种方式指定返回不同的响应码。

1,使用response参数

在create_item 函数中,我们接受一个response: Response参数,并直接设 response.status_code。

class Item(BaseModel):
    name: str
    price: float


@app.post("/items/")
async def create_item(item: Item, response: Response):
    if item.price < 0:
        response.status_code = status.HTTP_400_BAD_REQUEST
        return {"error": "Price cannot be negative"}
    response.status_code = status.HTTP_201_CREATED
    return {"message": "Item created successfully", "item": item}

这种方法允许在函数的任何位置设置状态码,同时仍然返回您想要的内容。

2,抛出HTTPException

在read_item函数中,我们使用raise HTTPException来设置状态码和错误详情。

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 0:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item_id": item_id, "name": "Sample Item"}

这种方法特别适合处理错误情况,因为它会自动停止函数的执行并返回错误响应。

3,返回Response对象

在update_item函数中,我们直接返回一个Response对象,其中包含内容和状态码。

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    if item_id == 0:
        return Response(content="Item not found", status_code=status.HTTP_404_NOT_FOUND)
    if item.price > 1000:
        return Response(content="Price too high", status_code=status.HTTP_400_BAD_REQUEST)
    return Response(content=f"Item {item_id} updated", status_code=status.HTTP_200_OK)

这种方法给了开发者对响应的完全控制,包括内容和状态码。

4,使用装饰器设置默认状态码

在delete_item函数中,我们使用@app.delete(…, status_code=status.HTTP_204_NO_CONTENT)来设置默认状态码。

@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int, response: Response):
    if item_id == 0:
        response.status_code = status.HTTP_404_NOT_FOUND
        return {"error": "Item not found"}
    # 如果成功删除,不需要返回内容,状态码会是204
    return None

如果需要更改状态码,我们仍然可以使用response参数来实现。

5,一些注意事项

  • 使用status模块(如status.HTTP_400_BAD_REQUEST)可以提高代码的可读性。
  • 对于错误处理,HTTPException通常是最清晰和最符合FastAPI风格的选择。
  • 当您需要完全控制响应时,返回Response对象是一个好方法。
  • 当设置了一个非200的状态码时,通常应该返回一些解释性的内容。

四,处理异常

在处理请求出错的情况下,需要通过响应来向客户端返回错误提示。

(一)使用 HTTPException

FastAPI的HTTPException是一个用于在API中引发HTTP错误的异常类。它允许你在代码中明确地抛出HTTP错误,以便向客户端返回适当的错误响应。

from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id not in some_items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": some_items_db[item_id]}

HTTPException 是额外包含了和 API 有关数据的常规 Python 异常。因为是 Python 异常,所以不能 return,只能 raise。

如在调用路径操作函数里的工具函数时,触发了 HTTPException,FastAPI
就不再继续执行路径操作函数中的后续代码,而是立即终止请求,并把HTTPException 的 HTTP 错误发送至客户端。

主要参数:

  • status_code: HTTP状态码(如404, 400, 500等)
  • detail: 错误的详细描述
  • headers: 可选的额外响应头

(二)全局异常

代码中通常要处理大量异常,如果所有异常都 raise HTTPException,那么代码将更加复杂,因此 fastapi 提供了全局异常处理器来自定义不同类型的异常,实现逻辑代码与异常处理代码的分离。

import uvicorn
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse

app = FastAPI()


# 自定义异常类
class CustomException(Exception):
    def __init__(self, name: str):
        self.name = name


# 为HTTPException定义异常处理器
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"message": f"HTTP error occurred: {exc.detail}"}
    )


# 为CustomException定义异常处理器
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."}
    )


# 路由处理函数,可能会抛出HTTPException
@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item_id": item_id}


# 路由处理函数,可能会抛出CustomException
@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise CustomException(name=name)
    return {"unicorn_name": name}


# 全局异常处理器
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    return JSONResponse(
        status_code=500,
        content={"message": f"An unexpected error occurred: {str(exc)}"}
    )


if '__main__' == __name__:
    uvicorn.run(app, host='127.0.0.1', port=8088)

解释一下这个代码示例的主要部分:

  1. 异常处理器装饰器:
    @app.exception_handler(ExceptionType) 用来定义一个异常处理器。ExceptionType 可以是任何异常类,如 HTTPException, CustomException, 或者甚至是基础的 Exception 类。

  2. 异常处理器函数:
    这些函数接收两个参数: request (当前的请求对象) 和 exc (捕获到的异常实例)。它们返回一个响应对象,通常是 JSONResponse。

  3. HTTPException 处理器:
    这个处理器捕获所有的 HTTPException,并返回一个包含状态码和错误消息的 JSON 响应。

  4. 自定义异常处理器:
    我们定义了一个 CustomException 类和相应的处理器。这展示了如何处理应用特定的异常。

  5. 路由处理函数:
    示例中包含了两个路由处理函数,分别可能抛出 HTTPException 和 CustomException。

  6. 全局异常处理器:
    最后,我们定义了一个处理所有未被其他处理器捕获的异常的全局处理器。

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值