Перейти к содержанию

Path-параметры и валидация числовых данных

Так же, как с помощью Query вы можете добавлять валидацию и метаданные для query-параметров, так и с помощью Path вы можете добавлять такую же валидацию и метаданные для path-параметров.

Импорт Path

Сначала импортируйте Path из fastapi, а также импортируйте Annotated:

from typing import Annotated

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get")],
    q: Annotated[str | None, Query(alias="item-query")] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results
from typing import Annotated, Union

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get")],
    q: Annotated[Union[str, None], Query(alias="item-query")] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Path, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get")],
    q: Annotated[Union[str, None], Query(alias="item-query")] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: int = Path(title="The ID of the item to get"),
    q: str | None = Query(default=None, alias="item-query"),
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import Union

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: int = Path(title="The ID of the item to get"),
    q: Union[str, None] = Query(default=None, alias="item-query"),
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Информация

Поддержка Annotated была добавлена в FastAPI начиная с версии 0.95.0 (и с этой версии рекомендуется использовать этот подход).

Если вы используете более старую версию, вы столкнётесь с ошибками при попытке использовать Annotated.

Убедитесь, что вы обновили версию FastAPI как минимум до 0.95.1 перед тем, как использовать Annotated.

Определите метаданные

Вы можете указать все те же параметры, что и для Query.

Например, чтобы указать значение метаданных title для path-параметра item_id, вы можете написать:

from typing import Annotated

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get")],
    q: Annotated[str | None, Query(alias="item-query")] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results
from typing import Annotated, Union

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get")],
    q: Annotated[Union[str, None], Query(alias="item-query")] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Path, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get")],
    q: Annotated[Union[str, None], Query(alias="item-query")] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: int = Path(title="The ID of the item to get"),
    q: str | None = Query(default=None, alias="item-query"),
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import Union

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: int = Path(title="The ID of the item to get"),
    q: Union[str, None] = Query(default=None, alias="item-query"),
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Примечание

Path-параметр всегда является обязательным, поскольку он составляет часть пути.

Поэтому следует объявить его с помощью ..., чтобы обозначить, что этот параметр обязательный.

Тем не менее, даже если вы объявите его как None или установите для него значение по умолчанию, это ни на что не повлияет и параметр останется обязательным.

Задайте нужный вам порядок параметров

Подсказка

Это не имеет большого значения, если вы используете Annotated.

Допустим, вы хотите объявить query-параметр q как обязательный параметр типа str.

И если вам больше ничего не нужно указывать для этого параметра, то нет необходимости использовать Query.

Но вам по-прежнему нужно использовать Path для path-параметра item_id. И если по какой-либо причине вы не хотите использовать Annotated, то могут возникнуть небольшие сложности.

Если вы поместите параметр со значением по умолчанию перед другим параметром, у которого нет значения по умолчанию, то Python укажет на ошибку.

Но вы можете изменить порядок параметров, чтобы параметр без значения по умолчанию (query-параметр q) шёл первым.

Это не имеет значения для FastAPI. Он распознает параметры по их названиям, типам и значениям по умолчанию (Query, Path, и т.д.), ему не важен их порядок.

Поэтому вы можете определить функцию так:

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(q: str, item_id: int = Path(title="The ID of the item to get")):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Но имейте в виду, что если вы используете Annotated, вы не столкнётесь с этой проблемой, так как вы не используете Query() или Path() в качестве значения по умолчанию для параметра функции.

from typing import Annotated

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    q: str, item_id: Annotated[int, Path(title="The ID of the item to get")]
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results
from fastapi import FastAPI, Path
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    q: str, item_id: Annotated[int, Path(title="The ID of the item to get")]
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Задайте нужный вам порядок параметров, полезные приёмы

Подсказка

Это не имеет большого значения, если вы используете Annotated.

Здесь описан небольшой приём, который может оказаться удобным, хотя часто он вам не понадобится.

Если вы хотите:

  • объявить query-параметр q без Query и без значения по умолчанию
  • объявить path-параметр item_id с помощью Path
  • указать их в другом порядке
  • не использовать Annotated

...то вы можете использовать специальную возможность синтаксиса Python.

Передайте * в качестве первого параметра функции.

Python не будет ничего делать с *, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как kwargs, даже если у них нет значений по умолчанию.

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Лучше с Annotated

Имейте в виду, что если вы используете Annotated, то, поскольку вы не используете значений по умолчанию для параметров функции, то у вас не возникнет подобной проблемы и вам не придётся использовать *.

from typing import Annotated

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get")], q: str
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results
from fastapi import FastAPI, Path
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get")], q: str
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Валидация числовых данных: больше или равно

С помощью Query и Path (и других классов, которые мы разберём позже) вы можете добавлять ограничения для числовых данных.

В этом примере при указании ge=1, параметр item_id должен быть больше или равен 1 ("greater than or equal").

from typing import Annotated

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)], q: str
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results
from fastapi import FastAPI, Path
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)], q: str
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    *, item_id: int = Path(title="The ID of the item to get", ge=1), q: str
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Валидация числовых данных: больше и меньше или равно

То же самое применимо к:

  • gt: больше (greater than)
  • le: меньше или равно (less than or equal)
from typing import Annotated

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get", gt=0, le=1000)],
    q: str,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results
from fastapi import FastAPI, Path
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get", gt=0, le=1000)],
    q: str,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    *,
    item_id: int = Path(title="The ID of the item to get", gt=0, le=1000),
    q: str,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Валидация числовых данных: числа с плавающей точкой, больше и меньше

Валидация также применима к значениям типа float.

В этом случае становится важной возможность добавить ограничение gt, вместо ge, поскольку в таком случае вы можете, например, создать ограничение, чтобы значение было больше 0, даже если оно меньше 1.

Таким образом, 0.5 будет корректным значением. А 0.0 или 0 — нет.

То же самое справедливо и для lt.

from typing import Annotated

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    *,
    item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
    q: str,
    size: Annotated[float, Query(gt=0, lt=10.5)],
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results
from fastapi import FastAPI, Path, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    *,
    item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
    q: str,
    size: Annotated[float, Query(gt=0, lt=10.5)],
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    *,
    item_id: int = Path(title="The ID of the item to get", ge=0, le=1000),
    q: str,
    size: float = Query(gt=0, lt=10.5),
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Резюме

С помощью Query, Path (и других классов, которые мы пока не затронули) вы можете добавлять метаданные и строковую валидацию тем же способом, как и в главе Query-параметры и валидация строк.

А также вы можете добавить валидацию числовых данных:

  • gt: больше (greater than)
  • ge: больше или равно (greater than or equal)
  • lt: меньше (less than)
  • le: меньше или равно (less than or equal)

Информация

Query, Path и другие классы, которые мы разберём позже, являются наследниками общего класса Param.

Все они используют те же параметры для дополнительной валидации и метаданных, которые вы видели ранее.

Технические детали

Query, Path и другие "классы", которые вы импортируете из fastapi, на самом деле являются функциями, которые при вызове возвращают экземпляры одноимённых классов.

Объект Query, который вы импортируете, является функцией. И при вызове она возвращает экземпляр одноимённого класса Query.

Использование функций (вместо использования классов напрямую) нужно для того, чтобы ваш редактор не подсвечивал ошибки, связанные с их типами.

Таким образом вы можете использовать привычный вам редактор и инструменты разработки, не добавляя дополнительных конфигураций для игнорирования подобных ошибок.