Объявление примеров данных запроса¶
Вы можете объявлять примеры данных, которые ваше приложение может получать.
Вот несколько способов, как это сделать.
Дополнительные данные JSON Schema в моделях Pydantic¶
Вы можете объявить examples
для модели Pydantic, которые будут добавлены в сгенерированную JSON Schema.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
model_config = {
"json_schema_extra": {
"examples": [
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
]
}
}
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
model_config = {
"json_schema_extra": {
"examples": [
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
]
}
}
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class Config:
schema_extra = {
"examples": [
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
]
}
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
class Config:
schema_extra = {
"examples": [
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
]
}
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Эта дополнительная информация будет добавлена как есть в выходную JSON Schema этой модели и будет использоваться в документации API.
В Pydantic версии 2 вы будете использовать атрибут model_config
, который принимает dict
, как описано в Документации Pydantic: Конфигурация.
Вы можете задать "json_schema_extra"
с dict
, содержащим любые дополнительные данные, которые вы хотите видеть в сгенерированной JSON Schema, включая examples
.
В Pydantic версии 1 вы будете использовать внутренний класс Config
и schema_extra
, как описано в Документации Pydantic: Настройка схемы.
Вы можете задать schema_extra
со dict
, содержащим любые дополнительные данные, которые вы хотите видеть в сгенерированной JSON Schema, включая examples
.
Подсказка
Вы можете использовать тот же приём, чтобы расширить JSON Schema и добавить свою собственную дополнительную информацию.
Например, вы можете использовать это, чтобы добавить метаданные для фронтенд‑пользовательского интерфейса и т.д.
Информация
OpenAPI 3.1.0 (используется начиная с FastAPI 0.99.0) добавил поддержку examples
, который является частью стандарта JSON Schema.
До этого поддерживалось только ключевое слово example
с одним примером. Оно всё ещё поддерживается в OpenAPI 3.1.0, но помечено как устаревшее и не является частью стандарта JSON Schema. Поэтому рекомендуется мигрировать example
на examples
. 🤓
Подробнее — в конце этой страницы.
Дополнительные аргументы Field
¶
При использовании Field()
с моделями Pydantic вы также можете объявлять дополнительные examples
:
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str = Field(examples=["Foo"])
description: str | None = Field(default=None, examples=["A very nice Item"])
price: float = Field(examples=[35.4])
tax: float | None = Field(default=None, examples=[3.2])
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str = Field(examples=["Foo"])
description: Union[str, None] = Field(default=None, examples=["A very nice Item"])
price: float = Field(examples=[35.4])
tax: Union[float, None] = Field(default=None, examples=[3.2])
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
examples
в JSON Schema — OpenAPI¶
При использовании любой из функций:
Path()
Query()
Header()
Cookie()
Body()
Form()
File()
вы также можете объявить набор examples
с дополнительной информацией, которая будет добавлена в их JSON Schema внутри OpenAPI.
Body
с examples
¶
Здесь мы передаём examples
, содержащий один пример данных, ожидаемых в Body()
:
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Annotated[
Item,
Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
],
),
],
):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Annotated, Union
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Annotated[
Item,
Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
],
),
],
):
results = {"item_id": item_id, "item": item}
return results
from typing import Union
from fastapi import Body, FastAPI
from pydantic import BaseModel
from typing_extensions import Annotated
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Annotated[
Item,
Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
],
),
],
):
results = {"item_id": item_id, "item": item}
return results
Tip
Prefer to use the Annotated
version if possible.
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Item = Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
],
),
):
results = {"item_id": item_id, "item": item}
return results
Tip
Prefer to use the Annotated
version if possible.
from typing import Union
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Item = Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
],
),
):
results = {"item_id": item_id, "item": item}
return results
Пример в UI документации¶
С любым из перечисленных выше методов это будет выглядеть так в /docs
:
Body
с несколькими examples
¶
Конечно, вы можете передать и несколько examples
:
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Annotated[
Item,
Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
{
"name": "Bar",
"price": "35.4",
},
{
"name": "Baz",
"price": "thirty five point four",
},
],
),
],
):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Annotated, Union
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Annotated[
Item,
Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
{
"name": "Bar",
"price": "35.4",
},
{
"name": "Baz",
"price": "thirty five point four",
},
],
),
],
):
results = {"item_id": item_id, "item": item}
return results
from typing import Union
from fastapi import Body, FastAPI
from pydantic import BaseModel
from typing_extensions import Annotated
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Annotated[
Item,
Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
{
"name": "Bar",
"price": "35.4",
},
{
"name": "Baz",
"price": "thirty five point four",
},
],
),
],
):
results = {"item_id": item_id, "item": item}
return results
Tip
Prefer to use the Annotated
version if possible.
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Item = Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
{
"name": "Bar",
"price": "35.4",
},
{
"name": "Baz",
"price": "thirty five point four",
},
],
),
):
results = {"item_id": item_id, "item": item}
return results
Tip
Prefer to use the Annotated
version if possible.
from typing import Union
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Item = Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
{
"name": "Bar",
"price": "35.4",
},
{
"name": "Baz",
"price": "thirty five point four",
},
],
),
):
results = {"item_id": item_id, "item": item}
return results
Когда вы делаете это, примеры становятся частью внутренней JSON Schema для данных тела запроса.
Тем не менее, на момент написания этого Swagger UI, инструмент, отвечающий за отображение UI документации, не поддерживает показ нескольких примеров для данных в JSON Schema. Но ниже есть обходной путь.
Специфические для OpenAPI examples
¶
Ещё до того как JSON Schema поддержала examples
, в OpenAPI была поддержка другого поля, также называемого examples
.
Эти специфические для OpenAPI examples
находятся в другой секции спецификации OpenAPI. Они находятся в подробностях для каждой операции пути (обработчика пути), а не внутри каждого объекта Schema.
И Swagger UI уже какое‑то время поддерживает именно это поле examples
. Поэтому вы можете использовать его, чтобы отобразить разные примеры в UI документации.
Структура этого специфичного для OpenAPI поля examples
— это dict
с несколькими примерами (вместо list
), каждый с дополнительной информацией, которая также будет добавлена в OpenAPI.
Это не помещается внутрь каждого объекта Schema в OpenAPI, это находится снаружи, непосредственно на уровне самой операции пути.
Использование параметра openapi_examples
¶
Вы можете объявлять специфические для OpenAPI examples
в FastAPI с помощью параметра openapi_examples
для:
Path()
Query()
Header()
Cookie()
Body()
Form()
File()
Ключи dict
идентифицируют каждый пример, а каждое значение — это ещё один dict
.
Каждый конкретный пример‑dict
в examples
может содержать:
summary
: Краткое описание примера.description
: Подробное описание, которое может содержать текст в Markdown.value
: Это фактический пример, который отображается, например,dict
.externalValue
: альтернативаvalue
, URL, указывающий на пример. Хотя это может поддерживаться не так многими инструментами, какvalue
.
Использовать это можно так:
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Annotated[
Item,
Body(
openapi_examples={
"normal": {
"summary": "A normal example",
"description": "A **normal** item works correctly.",
"value": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
},
"converted": {
"summary": "An example with converted data",
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
"value": {
"name": "Bar",
"price": "35.4",
},
},
"invalid": {
"summary": "Invalid data is rejected with an error",
"value": {
"name": "Baz",
"price": "thirty five point four",
},
},
},
),
],
):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Annotated, Union
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Annotated[
Item,
Body(
openapi_examples={
"normal": {
"summary": "A normal example",
"description": "A **normal** item works correctly.",
"value": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
},
"converted": {
"summary": "An example with converted data",
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
"value": {
"name": "Bar",
"price": "35.4",
},
},
"invalid": {
"summary": "Invalid data is rejected with an error",
"value": {
"name": "Baz",
"price": "thirty five point four",
},
},
},
),
],
):
results = {"item_id": item_id, "item": item}
return results
from typing import Union
from fastapi import Body, FastAPI
from pydantic import BaseModel
from typing_extensions import Annotated
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Annotated[
Item,
Body(
openapi_examples={
"normal": {
"summary": "A normal example",
"description": "A **normal** item works correctly.",
"value": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
},
"converted": {
"summary": "An example with converted data",
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
"value": {
"name": "Bar",
"price": "35.4",
},
},
"invalid": {
"summary": "Invalid data is rejected with an error",
"value": {
"name": "Baz",
"price": "thirty five point four",
},
},
},
),
],
):
results = {"item_id": item_id, "item": item}
return results
Tip
Prefer to use the Annotated
version if possible.
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Item = Body(
openapi_examples={
"normal": {
"summary": "A normal example",
"description": "A **normal** item works correctly.",
"value": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
},
"converted": {
"summary": "An example with converted data",
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
"value": {
"name": "Bar",
"price": "35.4",
},
},
"invalid": {
"summary": "Invalid data is rejected with an error",
"value": {
"name": "Baz",
"price": "thirty five point four",
},
},
},
),
):
results = {"item_id": item_id, "item": item}
return results
Tip
Prefer to use the Annotated
version if possible.
from typing import Union
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Item = Body(
openapi_examples={
"normal": {
"summary": "A normal example",
"description": "A **normal** item works correctly.",
"value": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
},
"converted": {
"summary": "An example with converted data",
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
"value": {
"name": "Bar",
"price": "35.4",
},
},
"invalid": {
"summary": "Invalid data is rejected with an error",
"value": {
"name": "Baz",
"price": "thirty five point four",
},
},
},
),
):
results = {"item_id": item_id, "item": item}
return results
OpenAPI-примеры в UI документации¶
С openapi_examples
, добавленным в Body()
, страница /docs
будет выглядеть так:
Технические детали¶
Подсказка
Если вы уже используете FastAPI версии 0.99.0 или выше, вы, вероятно, можете пропустить эти подробности.
Они более актуальны для старых версий, до того как стала доступна OpenAPI 3.1.0.
Считайте это кратким уроком истории про OpenAPI и JSON Schema. 🤓
Внимание
Далее идут очень технические подробности о стандартах JSON Schema и OpenAPI.
Если идеи выше уже работают для вас, этого может быть достаточно, и, вероятно, вам не нужны эти детали — смело пропускайте их.
До OpenAPI 3.1.0 OpenAPI использовала более старую и модифицированную версию JSON Schema.
В JSON Schema не было examples
, поэтому OpenAPI добавила собственное поле example
в свою модифицированную версию.
OpenAPI также добавила поля example
и examples
в другие части спецификации:
Parameter Object
(в спецификации), которое использовалось в FastAPI:Path()
Query()
Header()
Cookie()
Request Body Object
, в полеcontent
, вMedia Type Object
(в спецификации), которое использовалось в FastAPI:Body()
File()
Form()
Информация
Этот старый специфичный для OpenAPI параметр examples
теперь называется openapi_examples
, начиная с FastAPI 0.103.0
.
Поле examples
в JSON Schema¶
Позже в новой версии спецификации JSON Schema было добавлено поле examples
.
А затем новый OpenAPI 3.1.0 был основан на последней версии (JSON Schema 2020-12), которая включала это новое поле examples
.
И теперь это новое поле examples
имеет приоритет над старым одиночным (и кастомным) полем example
, которое теперь устарело.
Это новое поле examples
в JSON Schema — это просто list
примеров, а не dict с дополнительными метаданными, как в других местах OpenAPI (описанных выше).
Информация
Даже после того как OpenAPI 3.1.0 была выпущена с этой новой, более простой интеграцией с JSON Schema, какое‑то время Swagger UI, инструмент, предоставляющий автоматическую документацию, не поддерживал OpenAPI 3.1.0 (поддержка появилась начиная с версии 5.0.0 🎉).
Из‑за этого версии FastAPI до 0.99.0 всё ещё использовали версии OpenAPI ниже 3.1.0.
examples
в Pydantic и FastAPI¶
Когда вы добавляете examples
внутри модели Pydantic, используя schema_extra
или Field(examples=["something"])
, этот пример добавляется в JSON Schema для этой модели Pydantic.
И эта JSON Schema модели Pydantic включается в OpenAPI вашего API, а затем используется в UI документации.
В версиях FastAPI до 0.99.0 (0.99.0 и выше используют новый OpenAPI 3.1.0), когда вы использовали example
или examples
с любыми другими утилитами (Query()
, Body()
, и т.д.), эти примеры не добавлялись в JSON Schema, описывающую эти данные (даже в собственную версию JSON Schema OpenAPI), они добавлялись непосредственно в объявление операции пути в OpenAPI (вне частей OpenAPI, использующих JSON Schema).
Но теперь, когда FastAPI 0.99.0 и выше используют OpenAPI 3.1.0, который использует JSON Schema 2020-12, а также Swagger UI 5.0.0 и выше, всё стало более последовательным, и примеры включаются в JSON Schema.
Swagger UI и специфичные для OpenAPI examples
¶
Раньше, поскольку Swagger UI не поддерживал несколько примеров JSON Schema (по состоянию на 2023-08-26), у пользователей не было способа показать несколько примеров в документации.
Чтобы решить это, FastAPI 0.103.0
добавил поддержку объявления того же старого, специфичного для OpenAPI, поля examples
с новым параметром openapi_examples
. 🤓
Итог¶
Раньше я говорил, что не очень люблю историю... а теперь вот рассказываю «уроки технической истории». 😅
Коротко: обновитесь до FastAPI 0.99.0 или выше — так всё будет значительно проще, последовательнее и интуитивнее, и вам не придётся знать все эти исторические подробности. 😎