跳至内容

是否为输入和输出分别创建 OpenAPI 架构

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

事实上,在某些情况下,它甚至会在 OpenAPI 中为同一个 Pydantic 模型创建 **两个 JSON 架构**,分别用于输入和输出,具体取决于它们是否有 **默认值**。

让我们看看它是如何工作的,以及如何在需要时进行更改。

用于输入和输出的 Pydantic 模型

假设您有一个带有默认值的 Pydantic 模型,例如:

from fastapi import FastAPI
from pydantic import BaseModel


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

# Code below omitted 👇
👀 查看完整文件预览
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"),
    ]
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


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

# Code below omitted 👇
👀 查看完整文件预览
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

# Code below omitted 👇
👀 查看完整文件预览
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 👇
👀 查看完整文件预览
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"),
    ]
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

# Code below omitted 👇
👀 查看完整文件预览
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

# Code below omitted 👇
👀 查看完整文件预览
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"),
    ]
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来禁用此功能。

信息

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"),
    ]
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 中的行为相同。🤓