Перейти до змісту

Декларування прикладів вхідних даних

Ви можете задати приклади даних, які Ваш застосунок може отримувати.

Ось кілька способів, як це зробити.

Додаткові дані JSON-схеми в моделях Pydantic

Ви можете задати examples для моделі Pydantic, які буде додано до згенерованої JSON-схеми.

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

Ця додаткова інформація буде додана як є до JSON-схеми для цієї моделі, і вона буде використана в документації до API.

Ви можете використати атрибут model_config, який приймає dict, як описано в документації Pydantic: Configuration.

Ви можете встановити "json_schema_extra" як dict, що містить будь-які додаткові дані, які Ви хочете відобразити у згенерованій JSON-схемі, включаючи examples.

Порада

Ви можете використати ту ж техніку, щоб розширити JSON-схему і додати власну додаткову інформацію.

Наприклад, Ви можете використати її для додавання метаданих для інтерфейсу користувача на фронтенді тощо.

Інформація

OpenAPI 3.1.0 (який використовується починаючи з FastAPI 0.99.0) додав підтримку examples, що є частиною стандарту JSON-схеми.

До цього підтримувався лише ключ 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-схемі — OpenAPI

При використанні будь-кого з наступного:

  • Path()
  • Query()
  • Header()
  • Cookie()
  • Body()
  • Form()
  • File()

Ви також можете задати набір examples з додатковою інформацією, яка буде додана до їхніх JSON-схем у 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

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

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-схеми для цих даних тіла.

Втім, на момент написання цього (час написання цього), Swagger UI — інструмент, який відповідає за відображення UI документації — не підтримує показ кількох прикладів для даних у JSON-схемі. Але нижче можна прочитати про обхідний шлях.

Специфічні для OpenAPI examples

Ще до того, як JSON-схема почала підтримувати examples, OpenAPI вже мала підтримку іншого поля, яке також називається examples.

Це специфічне для OpenAPI поле examples розміщується в іншому розділі специфікації OpenAPI. Воно розміщується в деталях кожної операції шляху, а не всередині кожної JSON-схеми.

І Swagger UI вже давно підтримує це поле examples. Тому Ви можете використовувати його, щоб відображати різні приклади в UI документації.

Форма цього специфічного для OpenAPI поля examples — це dict з кількома прикладами (а не list), кожен із яких має додаткову інформацію, яка також буде додана до OpenAPI.

Воно не включається всередину кожної JSON-схеми, що міститься в 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

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 до інших частин специфікації:

Інформація

Цей старий специфічний для 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 або вище — і все стане значно простішим, узгодженим та інтуїтивно зрозумілим, і Вам не доведеться знати всі ці історичні деталі. 😎