跳转至

是否为输入和输出使用独立的 OpenAPI 模式

使用 Pydantic v2 时,生成的 OpenAPI 比以前更精确和正确。😎

实际上,在某些情况下,对于同一个 Pydantic 模型,根据是否具有默认值,OpenAPI 甚至会为输入和输出生成两个 JSON 模式

让我们看看这是如何工作的,以及如果需要如何更改它。

用于输入和输出的 Pydantic 模型

假设你有一个带有默认值的 Pydantic 模型,如下所示:

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None

# Code below omitted 👇
👀 Full file preview
from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None


app = FastAPI()


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
🤓 Other versions and variants
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Optional[str] = None


app = FastAPI()


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Union[str, None] = None


app = FastAPI()


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> List[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

用于输入的模型

如果你像这样将此模型用作输入:

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None


app = FastAPI()


@app.post("/items/")
def create_item(item: Item):
    return item

# Code below omitted 👇
👀 Full file preview
from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None


app = FastAPI()


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
🤓 Other versions and variants
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Optional[str] = None


app = FastAPI()


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Union[str, None] = None


app = FastAPI()


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> List[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

...那么 description 字段将不是必需的。因为它有一个默认值 None

文档中的输入模型

你可以在文档中确认,description 字段没有红色星号,它没有被标记为必需:

用于输出的模型

但如果你将相同的模型用作输出,像这样:

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None


app = FastAPI()


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
🤓 Other versions and variants
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Optional[str] = None


app = FastAPI()


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Union[str, None] = None


app = FastAPI()


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> List[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

...那么因为 description 有默认值,如果你没有返回任何内容给该字段,它仍然会具有该默认值

用于输出的响应数据模型

如果你与文档交互并检查响应,即使代码没有在某个 description 字段中添加任何内容,JSON 响应也会包含默认值(null):

这意味着它将始终有一个值,只是有时该值可能是 None(或在 JSON 中为 null)。

这意味着,使用你 API 的客户端不必检查该值是否存在,他们可以假定该字段将始终存在,只是在某些情况下它将具有 None 的默认值。

在 OpenAPI 中描述这一点的方式是将该字段标记为必需,因为它将始终存在。

因此,根据用于输入还是输出,模型的 JSON 模式可能会有所不同:

  • 对于输入description不是必需的
  • 对于输出,它将是必需的(并且可能是 None,或者在 JSON 术语中为 null

文档中的输出模型

你也可以在文档中检查输出模型,两个 namedescription 都被标记为必需,并带有红色星号

文档中的输入和输出模型

如果你检查 OpenAPI 中所有可用的模式(JSON 模式),你会看到有两个,一个是 Item-Input,一个是 Item-Output

对于 Item-Inputdescription 不是必需的,它没有红色星号。

但对于 Item-Outputdescription必需的,它有红色星号。

借助 Pydantic v2 的这一特性,你的 API 文档更加精确,如果你有自动生成的客户端和 SDK,它们也会更加精确,具有更好的开发人员体验和一致性。🎉

不分离模式

现在,在某些情况下,你可能希望输入和输出使用相同的模式

可能的主要用例是,如果你已经有一些自动生成的客户端代码/SDK,并且你不想更新所有自动生成的客户端代码/SDK,你可能希望在某个时候进行更新,但不是现在。

在这种情况下,你可以在 FastAPI 中禁用此功能,使用参数 separate_input_output_schemas=False

Info

separate_input_output_schemas 的支持是在 FastAPI 0.102.0 中添加的。🤓

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None


app = FastAPI(separate_input_output_schemas=False)


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
🤓 Other versions and variants
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Optional[str] = None


app = FastAPI(separate_input_output_schemas=False)


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Union[str, None] = None


app = FastAPI(separate_input_output_schemas=False)


@app.post("/items/")
def create_item(item: Item):
    return item


@app.get("/items/")
def read_items() -> List[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

文档中输入和输出模型的相同模式

现在,模型的输入和输出将有一个单一的模式,只有 Item,并且 description不是必需的

这与 Pydantic v1 中的行为相同。🤓