Skip to content

输入和输出的独立OpenAPI模式或不独立

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

事实上,在某些情况下,它甚至会为同一个Pydantic模型生成**两个JSON模式**,一个用于输入,一个用于输出,这取决于它们是否具有**默认值**。

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

输入和输出的Pydantic模型

假设你有一个带有默认值的Pydantic模型,像这样:

from fastapi import FastAPI
from pydantic import BaseModel


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

# 下面的代码省略了 👇
👀 完整文件预览
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

# 下面的代码省略了 👇
👀 完整文件预览
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

# 下面的代码省略了 👇
👀 完整文件预览
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

# 下面的代码省略了 👇
👀 完整文件预览
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

# 下面的代码省略了 👇
👀 完整文件预览
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

# 下面的代码省略了 👇
👀 完整文件预览
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 禁用此功能。

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