Декларування прикладів вхідних даних¶
Ви можете задати приклади даних, які Ваш застосунок може отримувати.
Ось кілька способів, як це зробити.
Додаткові дані 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
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-схеми, і вона буде використовуватися в документації до API.
У версії Pydantic 2 використовується атрибут model_config
, який приймає dict
, як описано в документації Pydantic: Конфігурація.
Ви можете встановити "json_schema_extra"
як dict
, що містить будь-які додаткові дані, які Ви хочете відобразити у згенерованій JSON-схемі, включаючи examples
.
У версії Pydantic 1 використовується внутрішній клас Config
і параметр schema_extra
, як описано в документації Pydantic: Налаштування схеми.
Ви можете задати 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
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-схеми для цих даних.
Втім, на момент написання цього (26 серпня 2023), Swagger UI — інструмент, який відповідає за відображення UI документації — не підтримує показ кількох прикладів у JSON-схеми. Але нижче можна прочитати про обхідний шлях.
Специфічні для OpenAPI examples
¶
Ще до того, як JSON-схема почала підтримувати examples
, OpenAPI вже мала підтримку поля з такою ж назвою — examples
.
Це специфічне для OpenAPI поле examples
розміщується в іншій частині специфікації OpenAPI — у деталях кожної операції шляху, а не всередині самої JSON-схеми.
Swagger UI вже давно підтримує це поле examples
. Тому Ви можете використовувати його, щоб відображати кілька прикладів у документації.
Це поле examples
у специфікації OpenAPI — це dict
(словник) з кількома прикладами (а не список list
), кожен із яких може містити додаткову інформацію, що буде додана до OpenAPI.
Воно не включається до JSON Schema кожного параметра, а розміщується зовні, безпосередньо в операції шляху.
Використання параметра 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 специфікація використовувала стару та модифіковану версію JSON Schema.
Оскільки JSON Schema раніше не підтримувала examples
, OpenAPI додала власне поле examples
.
OpenAPI також додала example
і examples
до інших частин специфікації:
Parameter Object
(в специфікації) використовується FastAPI для:Path()
Query()
Header()
Cookie()
Request Body Object
, в поліcontent
, вMedia Type Object
(в специфікації) використовується FastAPI для:Body()
File()
Form()
Інформація
Цей старий параметр examples
, специфічний для OpenAPI, тепер називається 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
) прикладів, без додаткових метаданих (на відміну від OpenAPI).
Інформація
Навіть після того, як з'явився OpenAPI 3.1.0, який підтримував examples у JSON Schema, інструмент Swagger UI ще деякий час не підтримував цю версію (підтримка з’явилась з версії 5.0.0 🎉).
Через це версії FastAPI до 0.99.0 все ще використовували версії OpenAPI нижчі за 3.1.0.
Examples
в Pydantic і FastAPI¶
Коли Ви додаєте examples
у модель Pydantic через schema_extra
або Field(examples=["something"])
, ці приклади додаються до JSON Schema цієї моделі.
І ця JSON Schema Pydantic-моделі включається до OpenAPI Вашого API, а потім використовується в UI документації (docs UI).
У версіях FastAPI до 0.99.0 (починаючи з 0.99.0 використовується новіший OpenAPI 3.1.0), коли Ви використовували example
або examples
з іншими утилітами (Query()
, Body()
тощо), ці приклади не додавалися до JSON Schema, який описує ці дані (навіть не до власної версії JSON Schema у OpenAPI). Натомість вони додавалися безпосередньо до опису обробника шляху (path operation) в OpenAPI (тобто поза межами частин, які використовують JSON Schema).
Але тепер, коли FastAPI 0.99.0 і вище використовують OpenAPI 3.1.0, а той — JSON Schema 2020-12, разом із Swagger UI 5.0.0 і вище — все стало більш узгодженим, і examples тепер включаються до JSON Schema.
Swagger UI та специфічні для OpenAPI examples
¶
Раніше (станом на 26 серпня 2023 року) Swagger UI не підтримував кілька прикладів у JSON Schema, тому користувачі не мали можливості показати декілька прикладів у документації.
Щоб вирішити це, FastAPI починаючи з версії 0.103.0 додав підтримку старого OpenAPI-специфічного поля examples
через новий параметр openapi_examples
. 🤓
Підсумок¶
Раніше я казав, що не люблю історію... а тепер ось я — розповідаю "технічні історичні" лекції. 😅
Коротко: оновіться до FastAPI 0.99.0 або вище — і все стане значно простішим, узгодженим та інтуїтивно зрозумілим, і Вам не доведеться знати всі ці історичні деталі. 😎