Query-параметры, значения по умолчанию и Annotated
Query-параметры — это часть URL после ?, которую FastAPI распознаёт по параметрам функции, не вошедшим в путь, и валидирует так же, как всё остальное.
Правило простое: параметр функции, имя которого не встречается в пути, FastAPI считает query-параметром. Есть значение по умолчанию — он опциональный, нет — обязательный.
Query-строка нужна для фильтрации, пагинации, сортировки, поиска — всего, что уточняет запрос, но не идентифицирует ресурс. В URL /items?skip=0&limit=20&q=phone три query-параметра. FastAPI различает path и query по простому признаку: если имя параметра функции есть в шаблоне пути — это path, если нет — query.
from fastapi import FastAPI, Query
from typing import Annotated
app = FastAPI()
@app.get("/items")
async def list_items(
skip: int = 0,
limit: Annotated[int, Query(le=100)] = 20,
q: str | None = None,
):
return {"skip": skip, "limit": limit, "q": q}
Здесь skip имеет дефолт 0 (опциональный), limit по умолчанию 20 и не больше 100, а q может отсутствовать (str | None = None). Современный синтаксис ограничений — снова Annotated[int, Query(...)]; он отделяет тип от метаданных и при этом сохраняет настоящее значение по умолчанию справа от =.
Как работает под капотом
Браузер и клиенты передают query как плоскую строку key=value&key2=value2, где всё — текст и всё URL-кодировано. FastAPI разбирает её, сопоставляет с параметрами функции, конвертирует типы и подставляет дефолты. Разберём query-строку вручную на stdlib — это ровно та работа, которую делает фреймворк до вызова обработчика:
from urllib.parse import parse_qs
raw = "skip=10&limit=50&q=phone%20case"
parsed = parse_qs(raw) # {'skip': ['10'], 'limit': ['50'], 'q': ['phone case']}
print("сырой разбор:", parsed)
def get(name, default=None, cast=str):
if name not in parsed:
return default # подстановка значения по умолчанию
return cast(parsed[name][0]) # берём первое значение и приводим тип
skip = get("skip", 0, int)
limit = get("limit", 20, int)
q = get("q", None, str)
print("skip =", skip, "| limit =", limit, "| q =", repr(q))
# проверка ограничения le=100
if limit > 100:
print("422: limit превышает 100")
else:
print("итог:", {"skip": skip, "limit": limit, "q": q})
Попробуй сам ▶ Поменяй raw: убери q — увидишь, как срабатывает значение по умолчанию None.
Заметьте, что %20 декодировалось в пробел: query всегда URL-кодирована, и декодирование — тоже забота FastAPI.
Частые ошибки
Первая — путать «нет дефолта» и «дефолт None». Без значения по умолчанию параметр обязателен, и его отсутствие даст 422. Вторая — ожидать, что булев query-параметр придёт как Python-bool сам по себе: FastAPI понимает true/false/1/0/yes/no, но только потому, что вы аннотировали тип bool. Третья — складывать сложные структуры в один query-параметр строкой вместо нормальной модели или повторяющихся ключей (списков).
Best practices
- Опциональность выражайте значением по умолчанию; обязательность — его отсутствием.
- Ограничения (
ge,le,max_length,pattern) задавайте черезAnnotated[..., Query(...)]. - Для пагинации используйте
skip/limitилиpage/sizeс разумными дефолтами и верхней границей. - Списочные query-параметры объявляйте как
list[str]— FastAPI соберёт повторяющиеся ключи.
Списки и сложные query-параметры
Query-строка умеет больше, чем плоские пары. Если объявить параметр как tags: list[str], FastAPI соберёт все повторяющиеся ключи: запрос ?tags=a&tags=b&tags=c даст список из трёх элементов. Это стандартный способ передавать множественные фильтры. Когда же параметров много и они логически связаны, их группируют в Pydantic-модель и помечают как query-зависимость — тогда вместо десятка отдельных аргументов в сигнатуре появляется один типизированный объект фильтра. Важно не злоупотреблять: складывать в один строковый query-параметр сериализованный JSON — антипаттерн, который ломает кэширование, логи и документацию. Если структура сложная, ей место в теле запроса (для операций, где тело уместно) или в нескольких явных параметрах, а не в одной перегруженной строке.
Итог: query-параметр — это параметр функции вне пути; дефолт делает его опциональным. FastAPI декодирует, конвертирует и валидирует query-строку до вызова обработчика.