Урок курса
Query-параметры и фильтрация каталога
FastAPI для начинающих: API с базой данных и тестамиPath-параметры брали значения прямо из сегментов пути — /api/rooms/2 давал room_id = 2. Query-параметры устроены иначе: они находятся в части URL после ?. Именно с ними мы будем фильтровать каталог переговорных.
Как аргумент попадает из query-строки в функцию
Для простых аргументов int, str и bool FastAPI по умолчанию применяет такое правило: если имя аргумента функции совпадает с именем сегмента пути в фигурных скобках — значение берётся из пути. Если не совпадает — FastAPI ищет его в query-строке.
Вот конкретный пример. Маршрут объявлен как @app.get("/api/rooms") — без переменных сегментов. Тогда аргумент min_capacity: int = 0 получит значение из ?min_capacity=10, а не из пути, которого там попросту нет.
Имя в функции должно совпадать с именем параметра в URL. Если написать ?min_capacity=10, а в функции принять capacity, FastAPI не свяжет их: capacity будет искаться как отдельный query-параметр.
Здесь кроется частая ошибка при работе с path-параметрами. Возьмём маршрут:
@app.get("/api/rooms/{room_id}")
def get_room(id: int):
...
Аргумент называется id, а в шаблоне пути стоит {room_id}. FastAPI не свяжет id с сегментом пути — он будет ожидать ?id=... в query-строке. Запрос GET /api/rooms/1 без этого параметра получит ответ 422.
Исправление простое — согласовать имена:
@app.get("/api/rooms/{room_id}")
def get_room(room_id: int):
...
Теперь room_id найдётся в пути, и запрос GET /api/rooms/1 отработает корректно. Для фильтра списка ситуация обратная: min_capacity не упоминается в фигурных скобках маршрута "/api/rooms", поэтому FastAPI читает его из query-строки — никакой дополнительной аннотации для этого не нужно.
Фильтр отсутствует, true или false
Чтобы фильтр был необязательным, аргументу задают значение по умолчанию. Для числового порога это выглядит так:
def list_rooms(min_capacity: int = 0):
Если ?min_capacity в запросе нет, FastAPI подставит 0, и условие capacity >= 0 пропустит все комнаты.
С булевым фильтром нужно различать три состояния: фильтр не передан, передан true и передан false. Значение 0 здесь не подойдёт — нужен None:
from typing import Optional
def list_rooms(min_capacity: int = 0, has_projector: Optional[bool] = None):
Optional[bool] означает, что аргумент может быть True, False или None. Именно = None определяет, что получит функция, если параметр не передан. Импорт Optional из typing обязателен для Python 3.8 и 3.9; начиная с 3.10 можно писать bool | None, но Optional работает везде.
Как FastAPI преобразует значения из строки:
?has_projector=true→True?has_projector=false→False?has_projector=1→True?has_projector=0→False- параметр отсутствует →
None
Если передать ?has_projector=maybe, FastAPI вернёт 422 ещё до вызова функции — строка maybe не является допустимым булевым значением.
Важно не путать это с обычным Python. Выражение bool("false") возвращает True, потому что непустая строка — истинна. FastAPI делает другое: он анализирует содержимое строки и преобразует её в настоящий True или False. Это разные механизмы, и полагаться на bool() вместо аннотации типа нельзя.
Два условия фильтрации одного каталога
Каталог хранится как словарь rooms_by_id, где ключ — числовой id, а значение — словарь с полями комнаты. Нас интересуют два поля: capacity (количество мест) и has_projector (булево наличие проектора).
Фильтрация работает в два шага. Первый — отсев по вместимости:
result = [r for r in rooms_by_id.values() if r["capacity"] >= min_capacity]
Мы берём все значения словаря и оставляем только те комнаты, где мест не меньше, чем запрошено. Если min_capacity=0 (значение по умолчанию), условие пройдут все комнаты.
Второй шаг — фильтр по проектору, но только если он был передан:
if has_projector is not None:
result = [r for r in result if r["has_projector"] == has_projector]
Проверка is not None принципиальна. Если написать if has_projector:, то явно переданный False тоже не пройдёт условие — Python посчитает его ложным, и фильтрация по комнатам без проектора молча отключится. Проверка is not None отличает «фильтр не задан» от «фильтр задан как False».
Когда has_projector=False, второй шаг оставит только комнаты с has_projector == False — то есть именно те, где проектора нет. Когда параметр не передан и равен None, шаг пропускается, и ограничений по оборудованию нет.
Оба условия применяются последовательно к одному списку, поэтому результат — пересечение: комнаты, удовлетворяющие обоим критериям одновременно.
При этом rooms_by_id остаётся нетронутым. Мы создаём новые списки через list comprehension, а не удаляем записи из словаря. Следующий запрос снова получит полный каталог.
Полный маршрут и проверка результатов запросов
Каталог содержит три переговорные: Лондон (6 мест, проектор есть), Берлин (12 мест, проектора нет), Токио (4 места, проектор есть).
from fastapi import FastAPI
from typing import Optional
app = FastAPI()
rooms_by_id = {
1: {"id": 1, "name": "Лондон", "floor": 2, "capacity": 6, "has_projector": True},
2: {"id": 2, "name": "Берлин", "floor": 3, "capacity": 12, "has_projector": False},
3: {"id": 3, "name": "Токио", "floor": 1, "capacity": 4, "has_projector": True},
}
@app.get("/api/rooms")
def list_rooms(min_capacity: int = 0, has_projector: Optional[bool] = None):
result = [r for r in rooms_by_id.values() if r["capacity"] >= min_capacity]
if has_projector is not None:
result = [r for r in result if r["has_projector"] == has_projector]
return result
Проверим разные комбинации запросов:
GET /api/rooms— оба параметра по умолчанию, фильтры не применяются. Ответ 200: все три комнаты.GET /api/rooms?min_capacity=6— остаются Лондон (6 ≥ 6) и Берлин (12 ≥ 6). Токио (4) не проходит. Ответ 200: два объекта.GET /api/rooms?min_capacity=6&has_projector=true— из Лондона и Берлина проектор есть только у Лондона. Ответ 200: один объект.GET /api/rooms?has_projector=false— без ограничения по вместимости, только без проектора. Ответ 200: только Берлин.GET /api/rooms?min_capacity=100— ни одна комната не вмещает 100 человек. Ответ 200: пустой массив[].GET /api/rooms?min_capacity=abc— строкаabcне является целым числом. FastAPI вернёт 422 до вызова функции.GET /api/rooms?has_projector=maybe— недопустимое булево значение. Тоже 422.
После любого из этих запросов GET /api/rooms без параметров снова вернёт все три комнаты: словарь rooms_by_id не изменился.

Закрепите тему на задачах
Проверьте запрос без необязательного параметра, корректное значение и неверный тип. У endpoint должны быть понятны значения по умолчанию и границы.
Query-параметры и фильтрация каталога — задача по FastAPI
Откройте условие и решите задачу перед чтением следующей темы. В задачах на код проверяется программа, в тестовых заданиях — выбранный ответ. Для последовательного обучения вернитесь в программу бесплатного курса.
Задача 1
Маршрут принимает has_projector: Optional[bool] = None. Клиент отправляет GET /api/rooms?has_projector=true. Какое значение получит функция-обработчик?
Задача 2
Обработчик всегда выполняет выражение room["capacity"] >= min_capacity. Как объявить min_capacity, чтобы запрос без параметра был допустим и не требовал отдельной проверки на None?
Задача 3
Каталог содержит: Лондон (capacity 6, projector true), Берлин (12, false), Токио (4, true). Какие комнаты вернёт GET /api/rooms?min_capacity=6&has_projector=true, если фильтры применяются последовательно и оба условия обязательны?
Задача 4
Подбор переговорной для встречи
Любая рабочая встреча начинается с поиска подходящего места: достаточно ли стульев, есть ли проектор, не придётся ли теснить коллег у окна. В крупном офисе переговорных может быть десятки, и просматривать каждую вручную — значит тратить время до того, как разговор даже начался. Именно здесь появляется Roomly: сервис, который берёт на себя подбор комнаты по нескольким простым критериям.
Каталог переговорных хранится в памяти, и к нему обращаются сразу несколько частей системы: общий список, просмотр отдельной комнаты по её числовому идентификатору и фильтрация. Все они работают с одним и тем же источником данных, поэтому информация везде согласована. Если в запросе на получение комнаты указан идентификатор в нечисловом формате, сервис автоматически сообщает об ошибке ещё до того, как дело дойдёт до поиска.
Фильтрация устроена гибко: можно указать минимальное число мест, потребовать наличие проектора или задать оба условия сразу — тогда комната должна соответствовать каждому из них. Если ни один параметр не передан, возвращается весь каталог целиком. Когда подходящих вариантов не находится, Roomly честно отвечает пустым списком — это тоже успешный результат, а не ошибка. Вместе все эти возможности образуют первый рабочий контур сервиса, в котором список комнат, точечный доступ по идентификатору и каталожная фильтрация действуют как единое целое.
Что уже дано
В редакторе открыт полный накопительный main.py: объект верхнего уровня app, единый словарь rooms_by_id, рабочий GET /api/rooms/{room_id} и базовый GET /api/rooms, который пока возвращает весь каталог. Каждая комната имеет поля id, name, floor, capacity и has_projector. Перед файлом выполняется видимый служебный переходник проверки: он определяет функции для запросов и чтения состояния, но не создаёт приложение или данные каталога. Менять переходник не нужно.
Что нужно сделать
Расширьте обработчик GET /api/rooms: объявите min_capacity: int = 0 и has_projector: Optional[bool] = None. Возвращайте JSON-массив значений единого rooms_by_id. Оставляйте комнаты с capacity >= min_capacity. Фильтр has_projector применяйте только при значении, отличном от None, и сравнивайте поле комнаты с переданным bool. При двух фильтрах должны выполняться оба условия. Без фильтров возвращайте весь каталог; отсутствие совпадений возвращайте как пустой массив со статусом 200. Не изменяйте рабочий path-маршрут.
Ввод и вывод
stdin не используется. Печатать через print в stdout ничего не нужно. После вашего кода проверяющая система читает объекты верхнего уровня app и rooms_by_id и отправляет запросы через TestClient. Она проверяет полный checkpoint: список, получение существующей комнаты по числовому id, автоматический 422 для нечислового path-значения, оба query-фильтра по отдельности и вместе, пустую выборку и автоматический 422 для несовместимых скалярных query-значений.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- Query-параметры и фильтрация каталога — задача по FastAPI
mincapacity=6&hasprojector=true, если фильтры применяются последовательно и оба условия обязательны? Проверьте ответ в задаче из бесплатного курса FastAPI.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
