Параметры пути и их валидация
Path-параметры — это переменные части URL вроде /users/42, которые FastAPI извлекает по типу аннотации, конвертирует и валидирует.
Объявив в пути
/items/{item_id}и параметрitem_id: int, вы получаете автоматическое преобразование"42"в число и ошибку422, если в URL пришло не число.
Путь запроса часто несёт идентификатор ресурса: номер пользователя, slug статьи, код товара. В FastAPI вы помечаете изменяемую часть фигурными скобками в строке маршрута и принимаете её одноимённым параметром функции. Тип параметра определяет, как значение будет разобрано. Это снова та же идея фреймворка: тип — спецификация.
from fastapi import FastAPI, Path
from typing import Annotated
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: Annotated[int, Path(ge=1)]):
return {"item_id": item_id}
Здесь item_id обязан быть целым и не меньше 1 (ge=1 — «greater or equal»). Запрос /items/5 вернёт {"item_id": 5}, а /items/abc или /items/0 — структурированную ошибку валидации. Обратите внимание на современный стиль: ограничения задаются через Annotated[int, Path(...)], а не через значение по умолчанию.
Как работает под капотом
Starlette компилирует строку маршрута в регулярное выражение, где {item_id} превращается в группу захвата. При запросе путь матчится этим регулярным выражением, захваченная подстрока — всегда текст. Затем FastAPI смотрит на аннотацию int и прогоняет текст через Pydantic-конвертацию. Смоделируем извлечение и конвертацию вручную на stdlib:
import re
# имитация компиляции "/items/{item_id}" в регулярку
pattern = re.compile(r"^/items/(?P<item_id>[^/]+)$")
def handle_path(url):
m = pattern.match(url)
if not m:
return "404 Not Found"
raw = m.group("item_id") # всегда строка
try:
item_id = int(raw) # конвертация по аннотации int
except ValueError:
return f"422: '{raw}' не является целым числом"
if item_id < 1: # проверка ge=1
return f"422: {item_id} меньше 1"
return {"item_id": item_id}
for url in ["/items/5", "/items/abc", "/items/0", "/other"]:
print(url, "->", handle_path(url))
Попробуй сам ▶ Видно три исхода: успех, ошибка типа, ошибка ограничения — ровно то, что делает FastAPI.
Важный нюанс — порядок маршрутов. Маршруты проверяются сверху вниз, поэтому фиксированный путь должен идти раньше параметризованного. Если объявить /users/{user_id} раньше /users/me, то запрос /users/me попадёт в первый и попытается превратить me в число.
Частые ошибки
Первая — ставить специфичные маршруты после общих параметризованных, из-за чего они становятся недостижимы. Вторая — забывать, что часть пути не может содержать / по умолчанию (для путей-с-слешами нужен конвертер {full_path:path}). Третья — полагаться на path-параметр там, где логичнее query: путь идентифицирует ресурс, а фильтры и опции — это query-строка.
Best practices
- Размещайте конкретные маршруты (
/users/me) выше параметризованных (/users/{id}). - Добавляйте ограничения через
Annotated[int, Path(ge=1)]— это и валидация, и документация. - Используйте path для идентификации ресурса, а не для фильтров и флагов.
- Для путей с произвольными слешами применяйте конвертер
:path.
Перечисления и предопределённые значения
Иногда path-параметр должен принимать не любое значение, а одно из фиксированного набора: тип сортировки, категория, версия API. Грубое решение — принять строку и проверять её в коде, но тогда вы теряете валидацию и документацию. Правильнее объявить перечисление через Enum и указать его как тип параметра: category: ModelCategory. Тогда FastAPI сам отвергнет значения вне набора с ошибкой 422, а в документации Swagger покажет выпадающий список допустимых вариантов. Это ещё одна грань главной идеи фреймворка: чем точнее тип, тем больше работы он делает за вас. Enum в роли типа path-параметра превращает «магическую строку» в самодокументируемый контракт, который невозможно нарушить незаметно.
Итог: path-параметр объявляется фигурными скобками и одноимённым типизированным параметром; FastAPI извлекает текст из URL, конвертирует по типу и валидирует. Следите за порядком маршрутов.