Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Query-параметры
query_params

Query-параметры

Фильтрация, пагинация, опциональные параметры, валидация

Открыть лабораториюv1.0Запускается локально из публичного репозитория

Query-параметры (Query Parameters)

Query-параметры передаются в строке запроса после ? и используются для фильтрации, пагинации, сортировки. В этой теме вы научитесь работать с опциональными параметрами, валидацией и преобразованием типов.

#Что такое query-параметры?

Query-параметры (параметры строки запроса) — это часть URL после знака вопроса:

GET /items?skip=0&limit=10&sort=name

Здесь:

  • skip=0 — пропустить первые 0 элементов
  • limit=10 — вернуть не более 10 элементов
  • sort=name — сортировать по имени

В отличие от параметров пути (/items/{item_id}), query-параметры:

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

#Базовый синтаксис

from fastapi import FastAPI app = FastAPI() @app.get('/items') def get_items(skip: int = 0, limit: int = 10): return {'skip': skip, 'limit': limit}

Запрос /items?skip=20&limit=5 вернёт:

{"skip": 20, "limit": 5}

#Как это работает?

  1. FastAPI видит параметры skip и limit в сигнатуре функции
  2. Ищет их в строке запроса (?skip=20&limit=5)
  3. Преобразует к указанному типу (int)
  4. Если параметр отсутствует, использует значение по умолчанию

Запрос /items (без параметров) вернёт:

{"skip": 0, "limit": 10}

#Опциональные параметры

Параметр может быть необязательным с помощью None:

@app.get('/items') def get_items(skip: int = 0, limit: int = 10, q: str | None = None): items = [{'id': i, 'name': f'Item {i}'} for i in range(skip, skip + limit)] if q: items = [item for item in items if q.lower() in item['name'].lower()] return {'items': items, 'query': q}

Запрос /items?q=laptop вернёт только элементы, содержащие "laptop" в названии.

Запрос /items (без q) вернёт все элементы без фильтрации.

#Валидация с Query()

Как и для path-параметров, можно использовать Query() для валидации:

from fastapi import FastAPI, Query app = FastAPI() @app.get('/items') def get_items( skip: int = Query(0, ge=0, description="Пропустить N элементов"), limit: int = Query(10, ge=1, le=100, description="Вернуть не более N элементов"), q: str | None = Query(None, min_length=3, max_length=50, description="Поисковый запрос") ): return {'skip': skip, 'limit': limit, 'q': q}

#Преимущества Query()

  • Валидация: ge, le, min_length, max_length, pattern
  • Документация: description отображается в Swagger UI
  • Значения по умолчанию: можно указать в Query() или в сигнатуре

#Эквивалентные записи

# Вариант 1: значение по умолчанию в сигнатуре def get_items(limit: int = Query(10, ge=1)): ... # Вариант 2: значение по умолчанию в Query() def get_items(limit: int = Query(default=10, ge=1)): ... # Вариант 3: Annotated (Python 3.10+, предпочтительно) from typing import Annotated def get_items(limit: Annotated[int, Query(ge=1, description="Лимит")]): ...

#Обязательные query-параметры

Иногда параметр должен быть обязательным:

from fastapi import FastAPI, Query app = FastAPI() @app.get('/search') def search(q: str = Query(..., min_length=3)): return {'query': q}

... (Ellipsis) означает, что параметр обязателен.

Запрос /search без q вернёт ошибку 422:

{ "detail": [ { "type": "missing", "loc": ["query", "q"], "msg": "Field required", "input": null } ] }

Запрос /search?q=ab (меньше 3 символов) также вернёт 422.

#Преобразование типов

FastAPI автоматически преобразует типы:

@app.get('/items') def get_items( skip: int = 0, limit: int = 10, price: float = 0.0, in_stock: bool = False, tags: list[str] | None = None ): return { 'skip': skip, 'limit': limit, 'price': price, 'in_stock': in_stock, 'tags': tags }

#Примеры преобразования

ЗапросРезультат
/items?skip=5skip: 5 (int)
/items?price=19.99price: 19.99 (float)
/items?in_stock=truein_stock: True (bool)
/items?tags=python&tags=fastapitags: ['python', 'fastapi'] (list)

#Списки в query-параметрах

Для передачи нескольких значений используйте один параметр несколько раз:

/items?tags=python&tags=fastapi&tags=api

Или через запятую (нужен кастомный парсер):

from fastapi import Query @app.get('/items') def get_items(tags: str | None = Query(None)): tag_list = tags.split(',') if tags else [] return {'tags': tag_list}

Запрос /items?tags=python,fastapi,api вернёт:

{"tags": ["python", "fastapi", "api"]}

#Пагинация

Классическое применение query-параметров — пагинация:

from fastapi import FastAPI, Query from typing import Annotated app = FastAPI() ITEMS = [{'id': i, 'name': f'Item {i}'} for i in range(100)] @app.get('/items') def list_items( page: Annotated[int, Query(ge=1, description="Номер страницы")] = 1, page_size: Annotated[int, Query(ge=1, le=50, description="Элементов на странице")] = 10 ): skip = (page - 1) * page_size end = skip + page_size return { 'items': ITEMS[skip:end], 'page': page, 'page_size': page_size, 'total': len(ITEMS), 'pages': (len(ITEMS) + page_size - 1) // page_size }

Запрос /items?page=3&page_size=20 вернёт элементы с 40 по 59.

#Метаданные пагинации

Хороший API возвращает не только данные, но и метаинформацию:

{ "items": [...], "page": 3, "page_size": 20, "total": 100, "pages": 5, "has_next": true, "has_prev": true }

#Сортировка

from fastapi import FastAPI, Query from enum import Enum class SortOrder(str, Enum): asc = "asc" desc = "desc" class SortBy(str, Enum): name = "name" price = "price" created_at = "created_at" @app.get('/items') def list_items( sort_by: SortBy = SortBy.created_at, order: SortOrder = SortOrder.desc ): # Логика сортировки return { 'sort_by': sort_by, 'order': order, 'items': ITEMS # отсортированные }

Запрос /items?sort_by=price&order=asc отсортирует по цене по возрастанию.

#Фильтрация по диапазону

from datetime import datetime @app.get('/items') def list_items( min_price: float | None = Query(None, ge=0), max_price: float | None = Query(None, ge=0), created_after: datetime | None = None, created_before: datetime | None = None ): items = ITEMS if min_price is not None: items = [i for i in items if i.get('price', 0) >= min_price] if max_price is not None: items = [i for i in items if i.get('price', 0) <= max_price] return {'items': items}

Запрос /items?min_price=100&max_price=500 отфильтрует по цене.

#Формат даты

FastAPI автоматически парсит ISO 8601:

/items?created_after=2024-01-01T00:00:00
/items?created_after=2024-01-01

#Исключение None-параметров

Иногда нужно передать только заданные параметры (например, для динамического фильтра):

from fastapi import FastAPI, Query @app.get('/items') def list_items( name: str | None = Query(None), min_price: float | None = Query(None), max_price: float | None = Query(None) ): # Собираем фильтры в словарь filters = {} if name: filters['name'] = name if min_price is not None: filters['min_price'] = min_price if max_price is not None: filters['max_price'] = max_price # Применяем фильтры к запросу в БД return {'filters': filters, 'items': []}

#Пример: Полноценный endpoint с фильтрацией

from fastapi import FastAPI, Query from typing import Annotated from datetime import datetime app = FastAPI() @app.get('/products') def list_products( # Пагинация page: Annotated[int, Query(ge=1)] = 1, page_size: Annotated[int, Query(ge=1, le=100)] = 20, # Сортировка sort_by: Annotated[str, Query(pattern=r'^(name|price|rating|created_at)$')] = 'created_at', order: Annotated[str, Query(pattern=r'^(asc|desc)$')] = 'desc', # Фильтры search: Annotated[str | None, Query(min_length=2, max_length=100)] = None, min_price: Annotated[float | None, Query(ge=0)] = None, max_price: Annotated[float | None, Query(ge=0)] = None, category: Annotated[str | None, Query()] = None, in_stock: Annotated[bool | None, Query()] = None, # Дата создания created_after: Annotated[datetime | None, Query()] = None, created_before: Annotated[datetime | None, Query()] = None, # Теги (несколько значений) tags: Annotated[list[str] | None, Query()] = None ): # Построение запроса к БД query = "SELECT * FROM products WHERE 1=1" params = {} if search: query += " AND name ILIKE :search" params['search'] = f'%{search}%' if min_price is not None: query += " AND price >= :min_price" params['min_price'] = min_price if max_price is not None: query += " AND price <= :max_price" params['max_price'] = max_price if category: query += " AND category = :category" params['category'] = category if in_stock is not None: query += " AND in_stock = :in_stock" params['in_stock'] = in_stock # Пагинация offset = (page - 1) * page_size query += " LIMIT :limit OFFSET :offset" params['limit'] = page_size params['offset'] = offset return { 'query': query, 'params': params, 'page': page, 'page_size': page_size }

Запрос:

GET /products?page=2&page_size=10&search=laptop&min_price=500&max_price=2000&category=electronics&in_stock=true&tags=gaming&tags=portable

#Query-параметры с алиасами

Иногда имя параметра в API должно отличаться от имени в коде:

from fastapi import FastAPI, Query app = FastAPI() @app.get('/items') def get_items( page_num: int = Query(1, alias='page'), page_size: int = Query(10, alias='limit') ): return {'page': page_num, 'limit': page_size}

Запрос /items?page=2&limit=50 вернёт:

{"page": 2, "limit": 50}

В коде используются page_num и page_size, в URL — page и limit.

#Deprecated-параметры

Если параметр устарел, но нужно поддерживать обратную совместимость:

from fastapi import FastAPI, Query app = FastAPI() @app.get('/items') def get_items( offset: int = Query(0, ge=0, deprecated=True, description="Устарел, используйте page"), page: int = Query(1, ge=0) ): # Конвертируем offset в page для обратной совместимости if offset: page = offset // 10 + 1 return {'page': page}

В Swagger UI параметр offset будет помечен как deprecated (зачёркнут).

#Частые ошибки

#Ошибка 1: Конфликт имён

@app.get('/items/{item_id}') def get_item(item_id: int, item_id: str = None): # Ошибка: дубликат имени ...

Проблема: Параметр пути и query-параметр с одинаковым именем.

Решение: Используйте разные имена:

@app.get('/items/{item_id}') def get_item(item_id: int, fields: str | None = None): ...

#Ошибка 2: Неправильный тип для списка

@app.get('/items') def get_items(tags: str | None = None): # Ожидает одну строку ...

Запрос /items?tags=a&tags=b передаст только последнее значение.

Решение:

@app.get('/items') def get_items(tags: list[str] | None = Query(None)): ...

#Ошибка 3: Забыли значение по умолчанию

@app.get('/items') def get_items(q: str): # Обязательный параметр без значения по умолчанию ...

Проблема: Параметр обязателен, но это не явно.

Решение:

@app.get('/items') def get_items(q: str | None = None): # Явно необязательный ... # или @app.get('/items') def get_items(q: str = Query(...)): # Явно обязательный ...

#Исполняемая лаборатория

Лаборатория «API задач с пагинацией и фильтрацией» доступна в публичном GitLab. Она проверяет Query(ge=, le=), пагинацию с метаданными, сортировку через Enum, фильтрацию по диапазону и алиасы параметров.

Склонируйте репозиторий и запустите тесты из его корня:

git clone --branch v1.0 --depth 1 https://gitlab.potapov.me/courses/fastapi_pro.git cd fastapi_pro uv sync --group test uv run --group test pytest query_params/tests

Далее: Request body