Разделять схемы OpenAPI для входа и выхода или нет¶
При использовании Pydantic v2 сгенерированный OpenAPI становится чуть более точным и корректным, чем раньше. 😎
На самом деле, в некоторых случаях в OpenAPI будет даже две JSON схемы для одной и той же Pydantic‑модели: для входа и для выхода — в зависимости от наличия значений по умолчанию.
Посмотрим, как это работает, и как это изменить при необходимости.
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
(или null
в JSON).
Следовательно, клиентам, использующим ваш API, не нужно проверять наличие этого значения: они могут исходить из того, что поле всегда присутствует, а в некоторых случаях имеет значение по умолчанию None
.
В OpenAPI это описывается тем, что поле помечается как обязательное, поскольку оно всегда присутствует.
Из‑за этого JSON Schema для модели может отличаться в зависимости от использования для входа или выхода:
- для входа
description
не будет обязательным - для выхода оно будет обязательным (и при этом может быть
None
, или, в терминах JSON,null
)
Выходная модель в документации¶
В документации это тоже видно, что оба: name
и description
, помечены красной звёздочкой как обязательные:

Модели для входа и выхода в документации¶
Если посмотреть все доступные схемы (JSON Schema) в OpenAPI, вы увидите две: Item-Input
и Item-Output
.
Для Item-Input
поле description
не является обязательным — красной звёздочки нет.
А для Item-Output
description
обязательно — красная звёздочка есть.

Благодаря этой возможности 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"),
]
🤓 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. 🤓