跳至內容

是否要為輸入和輸出使用單獨的 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,它們也會更加精確,提供更好的開發者體驗和一致性。🎉

不要區分 Schema

現在,在某些情況下,您可能希望輸入和輸出使用相同的 Schema

主要的應用場景可能是您已經有一些自動生成的客戶端程式碼/SDK,並且您還不想更新所有自動生成的客戶端程式碼/SDK,您可能最終會想要更新,但或許不是現在。

在這種情況下,您可以使用參數 separate_input_output_schemas=FalseFastAPI 中停用此功能。

資訊

FastAPI 0.102.0 版本開始支援 separate_input_output_schemas。🤓

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"),
    ]

文件中輸入和輸出模型使用相同的 Schema

現在模型的輸入和輸出將只有一個 Schema,即 Item,並且 description 將被設定為非必要

這與 Pydantic v1 的行為相同。🤓