本篇文章介绍 FastAPI 的返回类型 response model

可以在返回函数的类型注解中声明该接口的响应数据类型

类型注解的用法和输入数据参数一样, 可以使用:

PYTHON
@app.post("/items/")
async def create_item(item: Item) -> Item:
    ...

@app.get("/items/")
async def read_items() -> list[Item]:
    ...
Click to expand and view more

FastAPI 会使用返回类型完成一下事情:

response_model Parameter

有时候可能需要返回的数据和类型注解不完全一致, 例如:

如果直接用返回类型注解, 编辑器会提示类型不匹配的错误

这种情况下, 可以用路径装饰器的 response_model 参数来声明响应类型, 而不是用返回类型注解

PYTHON
class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []

@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
    return item

@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
    return [
        {"name": "Portal Gun", "price": 42.0},
        {"name": "Plumbus", "price": 32.0},
    ]
Click to expand and view more

注意:

如果使用 mypy 之类做 static type check, 可以声明函数返回类型为 Any

Return the Same Input Data 返回相同数据数据

很多情况下, 希望模型返回与输入模型相同的数据

这式, 可以在路径函数中直接声明 response_model=YourModel, FastAPI 会自动处理

PYTHON
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None

# Don't do this in production!
@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
    return user
Click to expand and view more

不要在生产环境中以明文形式存储用户密码, 也不要像这样直接返回密码

Add an Output Model 添加输出模型

我们可以改成: 输入模型包含明文密码, 输出模型不含

PYTHON
from typing import Any

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()

# Input model
class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None

# Output model
class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


@app.post("/user/", response_model=UserOut) # output
async def create_user(user: UserIn) -> Any:
    return user # like input
Click to expand and view more

这样, 即使路径操作函数返回的对象中包含该字段, FastAPI 也会按照 response_model=UserOut 来过滤密码

Return Type and Data Filtering 返回类型与数据过滤

延续上面的例子, 希望函数的类型注解和实际返回值不同:

函数返回的对象可能包含更多数据, 但响应中只保留输出模型声明的字段

之前由于类不同, 只能用 response_model, 这样就失去了编辑器和类型检查对返回值的检查

大多数情况下, 我们只是想去掉或过滤掉部分数据, 这时可以用 类继承(classes and inheritance) 来兼顾类型注解和数据过滤

PYTHON
class BaseUser(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None

class UserIn(BaseUser):
    password: str

@app.post("/user/")
async def create_user(user: UserIn) -> BaseUser:
    return user
Click to expand and view more

通过这种方式:

  1. Type Annotations and Testing 编辑器和类型检查工具支持: UserInBaseUser 的子类, 返回 UserIn 实例完全符合 BaseUser 类型要求
  2. FastAPI Data Filtering 数据过滤: 响应中会自动去掉 password 字段, 只保留 BaseUser 中声明的字段

Other Return Type Annotations 其他类型注解

有些时候, 返回的内容不是有效的 Pydantic 字段, 但在函数中添加了注解, 为了获取工具支持

Return a response directly 直接返回响应

最常见的就是直接返回一个 Resposne

PYTHON
from fastapi import FastAPI, Response
from fastapi.resposnes import JSONResponse, RedirectResponse

app = FastAPI()

@app.get("/portal")
async def get_protal(teleport: bool = False) -> Response:
    if teleport:
        return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
    return JSONResponse(content={"message": "Here's your interdimensional portal."})
Click to expand and view more

这种简单情况由 FastAPI 自动处理, 因为返回类型注解是 Response 类

开发工具也能正常工作, 因为 RedirectResponseJSONResponse 都是 Response 的子类, 所以类型注解是正确的

Invalid return type annotations 无效的类型注解

但是, 当返回一些其他任意对象(不是有效的 Pydantic 类型, 例如数据库对象)并在函数中这样注解时, FastAPI 会尝试从该类型注解创建一个 Pydantic 响应模型, 然后会失败

如果使用了联合模型, 其中有一个或多个不是有效的 Pydantic 类型, 同样会失败

PYTHON
from fastapi import FastAPI, Response
from fastapi.responses import RedirectResponse

app = FastAPI()

@app.get("/portal")
async def get_portal(teleport: bool = False) -> Response | dict:
    if teleport:
        return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
    return {"message": "Here's your interdimensional portal."}
Click to expand and view more

这会失败是因为类型注解不是单一的 Pydantic 类型, 也不是单一的 Response 类或子类, 而是 Response 和 dict 之间的联合类型

Disable response Model 禁用响应类型

如果不希望 FastAPI 执行默认的数据验证、文档生成、过滤等操作, 但是又想在函数中保留返回类型注解, 以获得编辑器和类型检查工具的支持, 这种情况下设置 response_model=None 来禁用响应生成

PYTHON
from fastapi import FastAPI, Response
from fastapi.response import RedirectResponse

app = FastAPI()

@app.get("/portal", response_model=None)
async def get_protal(teleport: bool = False) -> Response | dict:
    if teleport:
        return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
    return JSONResponse(content={"message": "Here's your interdimensional portal."})
Click to expand and view more

Response Model Encoding Parameters 响应模型编码参数

响应模型可能有默认值

PYTHON
class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5
    tags: list[str] = []
Click to expand and view more

例如, 在 NoSQL 数据库中哟许多可选属性的模型, 但不想发送默认值的很长的 JSON 响应

可以使用 path operation operator 的 response_model_exclude_mode 参数来去除默认值

PYTHON
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]
Click to expand and view more

此时, 如果向该路径发送 ID 为 foo 的项目请求

YAML
"foo": {"name": "Foo", "price": 50.2}
Click to expand and view more

响应将是

YAML
{
    "name": "foo",
    "price": 50.2,
}
Click to expand and view more

也可以使用 path operation parameter 中的 response_model_includeresponse_model_exclude, 他们接受一个包含属性名称字符串的 set, 用于包含(省略其余部分)或排除(包含其余部分)

PYTHON
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
    "baz": {
        "name": "Baz",
        "description": "There goes my baz",
        "price": 50.2,
        "tax": 10.5,
    },
}


@app.get(
    "/items/{item_id}/name",
    response_model=Item,
    response_model_include={"name", "description"}, # 包含
)
async def read_item_name(item_id: str):
    return items[item_id]


@app.get(
    "/items/{item_id}/public",
    response_model=Item,
    response_model_exclude={"tax"}, # 排除
)
async def read_item_public_data(item_id: str):
    return items[item_id]
Click to expand and view more

虽然可以通过上述方法自定义返回参数包含哪些, 但还是建议使用多个类来实现该功能, 而不这些参数

这是因为, 即使使用 response_model_includeresponse_model_exclude 来省略某些属性, 在应用程序的 OpenAPI 中生成的 JSON Schema 仍将是完整模型的 Schema

这对于 response_model_by_alias 也是一样的

Using list instead of sets

如果忘记使用集和而使用元组或列表, FastAPI 仍会将其转换为集和, 确保正常工作

PYTHON
@app.get(
    "/items/{item_id}/name",
    response_model=Item,
    response_model_include=["name", "description"],  # 使用列表
)
async def read_item_name(item_id: str):
    return items[item_id]

@app.get(
    "/items/{item_id}/public",
    response_model=Item,
    response_model_exclude=["tax"],  # 使用列表
)
async def read_item_public_data(item_id: str):
    return items[item_id]
Click to expand and view more

Response Status Code 响应状态码

就像可以指定响应模型一样, 也可以在任何路径操作中使用 status_code 参数声明用于响应:

PYTHON
from fastapi import FastAPI

app = FastAPI()


@app.post("/items/", status_code=201)
async def create_item(name: str):
    return {"name": name}
Click to expand and view more

status_code 是"装饰器"方法的一个参数, 而不是你的路径操作函数 path operation function 的参数

status_code 参数接收一个表示 HTTP 状态码的数字, 也可以接收一个 IntEnum, 比如 Python 中的 http.HTTPStatus

About HTTP status codes

在 HTTP 协议中, 会在响应中发送一个3位数的数字状态码

这些状态码又一个相关联的名称便于识别, 但重要的是数字本身

要了解更多关于每个状态码的信息以及哪个代码用于什么目的,请查阅 MDN 关于 HTTP 状态码的文档

Shortcut to remember the names

除了直接使用数字外, 还可以使用 fastapi.status 中的便捷变量

PYTHON
from fastapi import FastAPI, status

app = FastAPI()

@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(name: str):
    return {"name": name}
Click to expand and view more

这只是一直便利, 都是一样的树枝, 但这样可以使用编辑器的自动补全功能

也可以使用 from starlette import status

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut