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

Тіло запиту - Декілька параметрів

Тепер, коли ми розглянули використання Path та Query, розгляньмо більш просунуті способи оголошення тіла запиту в ReadyAPI.

Змішування Path, Query та параметрів тіла запиту

По-перше, звісно, Ви можете вільно змішувати оголошення параметрів Path, Query та тіла запиту, і ReadyAPI правильно їх обробить.

Також Ви можете оголосити параметри тіла як необов’язкові, встановивши для них значення за замовчуванням None:

from typing import Annotated

from pydantic import BaseModel
from readyapi import Path, ReadyAPI

app = ReadyAPI()


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: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
    q: str | None = None,
    item: Item | None = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    if item:
        results.update({"item": item})
    return results
🤓 Other versions and variants
from typing import Annotated, Union

from pydantic import BaseModel
from readyapi import Path, ReadyAPI

app = ReadyAPI()


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: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
    q: Union[str, None] = None,
    item: Union[Item, None] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    if item:
        results.update({"item": item})
    return results
from typing import Union

from pydantic import BaseModel
from readyapi import Path, ReadyAPI
from typing_extensions import Annotated

app = ReadyAPI()


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: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
    q: Union[str, None] = None,
    item: Union[Item, None] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    if item:
        results.update({"item": item})
    return results

Tip

Prefer to use the Annotated version if possible.

from pydantic import BaseModel
from readyapi import Path, ReadyAPI

app = ReadyAPI()


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 = Path(title="The ID of the item to get", ge=0, le=1000),
    q: str | None = None,
    item: Item | None = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    if item:
        results.update({"item": item})
    return results

Tip

Prefer to use the Annotated version if possible.

from typing import Union

from pydantic import BaseModel
from readyapi import Path, ReadyAPI

app = ReadyAPI()


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 = Path(title="The ID of the item to get", ge=0, le=1000),
    q: Union[str, None] = None,
    item: Union[Item, None] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    if item:
        results.update({"item": item})
    return results

Примітка

Зверніть увагу, що в цьому випадку параметр item, який береться з тіла запиту, є необов'язковим, оскільки має значення за замовчуванням None.

Декілька параметрів тіла запиту

У попередньому прикладі операція шляху очікувала JSON з атрибутами Item, наприклад:

{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2
}
Але Ви також можете оголосити декілька параметрів тіла, наприклад item та user:

from pydantic import BaseModel
from readyapi import ReadyAPI

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


class User(BaseModel):
    username: str
    full_name: str | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
    results = {"item_id": item_id, "item": item, "user": user}
    return results
🤓 Other versions and variants
from typing import Union

from pydantic import BaseModel
from readyapi import ReadyAPI

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


class User(BaseModel):
    username: str
    full_name: Union[str, None] = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
    results = {"item_id": item_id, "item": item, "user": user}
    return results

У цьому випадку ReadyAPI розпізнає, що є кілька параметрів тіла (два параметри є моделями Pydantic).

Тому він використає назви параметрів як ключі (назви полів) у тілі запиту, очікуючи:

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    },
    "user": {
        "username": "dave",
        "full_name": "Dave Grohl"
    }
}

Примітка

Зверніть увагу, що хоча item оголошено, так само як і раніше, тепер він очікується в тілі під ключем item.

ReadyAPI автоматично конвертує дані із запиту таким чином, щоб параметр item отримав свій вміст, і те ж саме стосується user.

Він виконає валідацію складених даних і задокументує їх відповідним чином у схемі OpenAPI та в автоматичній документації.

Одиничні значення в тілі запиту

Так само як є Query і Path для визначення додаткових даних для параметрів запиту та шляху, ReadyAPI надає еквівалентний Body.

Наприклад, розширюючи попередню модель, Ви можете вирішити додати ще один ключ importance в те ж саме тіло запиту разом із item і user.

Якщо Ви оголосите його як є, то, оскільки це одиничне значення, ReadyAPI припускатиме, що це параметр запиту (query parameter).

Але Ви можете вказати ReadyAPI обробляти його як інший ключ тіла (body key), використовуючи Body:

from typing import Annotated

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


class User(BaseModel):
    username: str
    full_name: str | None = None


@app.put("/items/{item_id}")
async def update_item(
    item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    return results
🤓 Other versions and variants
from typing import Annotated, Union

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


class User(BaseModel):
    username: str
    full_name: Union[str, None] = None


@app.put("/items/{item_id}")
async def update_item(
    item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    return results
from typing import Union

from pydantic import BaseModel
from readyapi import Body, ReadyAPI
from typing_extensions import Annotated

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


class User(BaseModel):
    username: str
    full_name: Union[str, None] = None


@app.put("/items/{item_id}")
async def update_item(
    item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    return results

Tip

Prefer to use the Annotated version if possible.

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


class User(BaseModel):
    username: str
    full_name: str | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User, importance: int = Body()):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    return results

Tip

Prefer to use the Annotated version if possible.

from typing import Union

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


class User(BaseModel):
    username: str
    full_name: Union[str, None] = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User, importance: int = Body()):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    return results

У цьому випадку ReadyAPI очікуватиме тіло запиту у такому вигляді:

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    },
    "user": {
        "username": "dave",
        "full_name": "Dave Grohl"
    },
    "importance": 5
}
Знову ж таки, ReadyAPI конвертуватиме типи даних, перевірятиме їх, створюватиме документацію тощо.

Декілька body та query параметрів

Звісно, Ви можете оголошувати додаткові query параметри запиту, коли це необхідно, на додаток до будь-яких параметрів тіла запиту.

Оскільки за замовчуванням окремі значення інтерпретуються як параметри запиту, Вам не потрібно явно додавати Query, можна просто використати:

q: Union[str, None] = None

Або в Python 3.10 та вище:

q: str | None = None

Наприклад:

from typing import Annotated

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


class User(BaseModel):
    username: str
    full_name: str | None = None


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: Item,
    user: User,
    importance: Annotated[int, Body(gt=0)],
    q: str | None = None,
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    if q:
        results.update({"q": q})
    return results
🤓 Other versions and variants
from typing import Annotated, Union

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


class User(BaseModel):
    username: str
    full_name: Union[str, None] = None


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: Item,
    user: User,
    importance: Annotated[int, Body(gt=0)],
    q: Union[str, None] = None,
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from pydantic import BaseModel
from readyapi import Body, ReadyAPI
from typing_extensions import Annotated

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


class User(BaseModel):
    username: str
    full_name: Union[str, None] = None


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: Item,
    user: User,
    importance: Annotated[int, Body(gt=0)],
    q: Union[str, None] = None,
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    if q:
        results.update({"q": q})
    return results

Tip

Prefer to use the Annotated version if possible.

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


class User(BaseModel):
    username: str
    full_name: str | None = None


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: Item,
    user: User,
    importance: int = Body(gt=0),
    q: str | None = None,
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    if q:
        results.update({"q": q})
    return results

Tip

Prefer to use the Annotated version if possible.

from typing import Union

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


class User(BaseModel):
    username: str
    full_name: Union[str, None] = None


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: Item,
    user: User,
    importance: int = Body(gt=0),
    q: Union[str, None] = None,
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    if q:
        results.update({"q": q})
    return results

Інформація

Body також має ті самі додаткові параметри валідації та метаданих, що й Query, Path та інші, які Ви побачите пізніше.

Вкладений поодинокий параметр тіла запиту

Припустимо, у вас є лише один параметр тіла запиту item з моделі Pydantic Item.

За замовчуванням ReadyAPI очікуватиме, що тіло запиту міститиме вміст безпосередньо.

Але якщо Ви хочете, щоб він очікував JSON з ключем item, а всередині — вміст моделі (так, як це відбувається при оголошенні додаткових параметрів тіла), Ви можете використати спеціальний параметр Bodyembed:

item: Item = Body(embed=True)

як у прикладі:

from typing import Annotated

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


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(embed=True)]):
    results = {"item_id": item_id, "item": item}
    return results
🤓 Other versions and variants
from typing import Annotated, Union

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


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(embed=True)]):
    results = {"item_id": item_id, "item": item}
    return results
from typing import Union

from pydantic import BaseModel
from readyapi import Body, ReadyAPI
from typing_extensions import Annotated

app = ReadyAPI()


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(embed=True)]):
    results = {"item_id": item_id, "item": item}
    return results

Tip

Prefer to use the Annotated version if possible.

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


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(embed=True)):
    results = {"item_id": item_id, "item": item}
    return results

Tip

Prefer to use the Annotated version if possible.

from typing import Union

from pydantic import BaseModel
from readyapi import Body, ReadyAPI

app = ReadyAPI()


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(embed=True)):
    results = {"item_id": item_id, "item": item}
    return results

У цьому випадку ReadyAPI очікуватиме тіло запиту такого вигляду:

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    }
}

замість:

{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2
}

Підсумок

Ви можете додавати кілька параметрів тіла до Вашої функції операції шляху (path operation function), навіть якщо запит може мати лише одне тіло.

Але ReadyAPI обробить це, надасть Вам потрібні дані у функції, перевірить їх та задокументує коректну схему в операції шляху.

Також Ви можете оголошувати окремі значення, які будуть отримані як частина тіла запиту.

Крім того, Ви можете вказати ReadyAPI вбудовувати тіло в ключ, навіть якщо оголошено лише один параметр.