Skip to content

是否將輸入與輸出使用不同的 OpenAPI 結構描述

🌐 AI 與人類共同完成的翻譯

此翻譯由人類指導的 AI 完成。🤝

可能會有對原意的誤解,或讀起來不自然等問題。🤖

你可以透過協助我們更好地引導 AI LLM來改進此翻譯。

英文版

自從 Pydantic v2 發佈後,生成的 OpenAPI 比以往更精確也更正確。😎

實際上,在某些情況下,同一個 Pydantic 模型在 OpenAPI 中會同時有兩個 JSON Schema:分別用於輸入與輸出,這取決於它是否有預設值。

來看看它如何運作,以及若需要時該如何調整。

作為輸入與輸出的 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"),
    ]

輸入用模型

如果你把這個模型用作輸入,如下所示:

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

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

...由於 description 有預設值,就算你沒有為該欄位回傳任何內容,它仍會有那個預設值。

輸出回應資料的模型

在互動式文件中試用並檢視回應時,儘管程式碼沒有為其中一個 description 欄位加入任何內容,JSON 回應仍包含預設值(null):

這代表該欄位一定會有值,只是有時候值可能是 None(在 JSON 中為 null)。

因此,使用你 API 的用戶端不必檢查值是否存在,可以假設該欄位一定存在;只是有些情況下它的值會是預設的 None

在 OpenAPI 中,描述這種情況的方式是將該欄位標記為必填,因為它一定存在。

因此,同一個模型的 JSON Schema 會依用於輸入或輸出而不同:

  • 用於輸入時,description 不是必填
  • 用於輸出時,description 是必填(且可能為 None,在 JSON 中為 null

文件中的輸出模型

你也可以在文件中檢視輸出模型,namedescription 都以紅色星號標示為必填:

文件中的輸入與輸出模型

如果你查看 OpenAPI 中所有可用的結構描述(JSON Schema),會看到有兩個:Item-InputItem-Output

對於 Item-Inputdescription 不是必填,沒有紅色星號。

但對於 Item-Outputdescription 是必填,有紅色星號。

有了 Pydantic v2 的這個特性,你的 API 文件會更精確;若你有自動產生的用戶端與 SDK,它們也會更精確,提供更好的開發者體驗與一致性。🎉

不要分開結構描述

不過,在某些情況下,你可能會希望輸入與輸出使用相同的結構描述。

最常見的情境是:你已經有一些自動產生的用戶端程式碼/SDK,目前還不想全部更新;也許之後會做,但不是現在。

在這種情況下,你可以在 FastAPI 中透過參數 separate_input_output_schemas=False 停用這個功能。

Info

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

文件中輸入與輸出使用相同結構描述的模型

此時輸入與輸出將共用同一個模型結構描述,只有 Item,其中 description 不是必填: