сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · FastAPI, Django и API

FastAPI и Pydantic

FastAPI: тип - единственный источник правды

FastAPI - де-факто стандарт нового Python-бэкенда, и собес по нему проверяет не синтаксис декораторов, а понимаешь ли ты, откуда берутся валидация, сериализация и документация. Ответ один: из аннотаций типов и Pydantic-моделей. Не понял это - будешь дублировать проверки руками и ловить утечки.

Типовые формулировки: «как FastAPI валидирует вход?», «зачем разные модели на вход и выход?», «что не так с блокирующим вызовом в async-эндпоинте».

Одна модель - вход, валидация, OpenAPI

Из Pydantic-модели и аннотаций FastAPI выводит СРАЗУ три вещи: валидацию входа, сериализацию выхода и OpenAPI-схему. Источник правды один - тип, дублировать нечего. Pydantic v2 в lax-режиме (для JSON body) мягко приведёт числовую строку '3' к int 3, но 'хай' даст ValidationError; нужна строгость - strict-режим.

Приятный побочный эффект v2: изменяемый дефолт tags: list = [] в модели безопасен - каждому экземпляру своя копия (в обычной def-функции тот же приём - классический баг с общим списком).

// Depends(get_db) - инъекция зависимости: FastAPI выполнит get_db до эндпоинта и подставит результат; общие зависимости кэшируются в пределах запроса.

Pydantic-модель
схема данных: валидация, приведение типов, сериализация
lax/strict
режимы Pydantic: приводить типы или требовать точный

response_model отсекает утечки

response_model - не только документация ответа: он ещё и ФИЛЬТРУЕТ. Поля, которых нет в модели ответа, отбрасываются. Вернул из эндпоинта ORM-объект с hashed_password, а в UserOut этого поля нет - оно не утечёт наружу. Поэтому одна модель на вход и выход рано или поздно протекает: пароль внутрь и наружу; разделяй UserCreate и UserOut.

// Второй капкан - блокировка: обычный def-эндпоинт FastAPI уносит в threadpool, чтобы не заморозить loop, а async def идёт прямо в цикле. Значит блокирующий вызов ОПАСНЕЕ именно в async def - автопул на него не распространяется.

class UserOut(BaseModel):
    id: int; email: str          # hashed_password НЕ описан

@app.get('/me', response_model=UserOut)
def me(): return user  # пароль в user есть, наружу не уйдёт
response_model
схема ответа: валидирует и отсекает лишние поля
Depends
инъекция зависимости; кэш в пределах запроса

Как отвечать: «Зачем отдельные Pydantic-модели на вход и выход?»

Чтобы контракт был точным и ничего не утекало. На входе UserCreate принимает пароль, на выходе UserOut его просто не содержит, а раз response_model отсекает поля вне схемы, hashed_password физически не попадёт в ответ, даже если я по ошибке верну весь ORM-объект. Плюс вход и выход почти всегда разной формы: на входе нет id и created_at, на выходе нет сырого пароля. Одна общая модель рано или поздно протечёт или заставит пихать Optional везде. Разделение делает и валидацию, и документацию честными.

Названа главная причина (защита от утечки через фильтрацию response_model), подкреплена разной формой контрактов и показано, что это влияет и на безопасность, и на документацию - системный взгляд.

На чём валят

  • Одна модель на вход и выход рано или поздно протечёт: пароль внутрь и наружу. Разделяй UserCreate и UserOut.
  • Блокирующий вызов в async def душит event loop - автопул threadpool FastAPI применяет только к обычным def.
  • Считать response_model только документацией: это ещё и защита от утечки лишних полей.
  • Ждать strict-валидации по умолчанию: для JSON body Pydantic в lax-режиме приводит типы - '3' станет 3.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 14, остальные разбираются в тренажёре.

  1. #fastapi_pydantic1 / 5
    Откуда FastAPI берёт валидацию тела запроса и сериализацию ответа?
    A)Из докстрингов эндпоинтов, парся их вручную
    B)Из аннотаций типов через Pydantic-модели
    C)Из middleware, проверяющего каждый запрос по схеме
    D)Из отдельного конфигурационного файла валидации проекта
    показать ответ и разбор
    +B)Из аннотаций типов через Pydantic-модели

    // разбор: FastAPI читает аннотации типов параметров и Pydantic-моделей: из них строит валидацию входа, сериализацию выхода и OpenAPI-схему. Источник правды один — тип. Объявил модель — получил проверку, приведение и доку без отдельного слоя валидации.

  2. #fastapi_pydantic2 / 5
    Зачем эндпоинту параметр response_model?
    A)Ускорить ответ за счёт заранее закэшированной модели
    B)Указать, какую таблицу вернуть клиенту напрямую
    C)Задать модель таблицы базы данных для эндпоинта
    D)Описать и отфильтровать форму ответа
    показать ответ и разбор
    +D)Описать и отфильтровать форму ответа

    // разбор: response_model валидирует и сериализует ответ по схеме и — важно — отбрасывает поля, которых в модели нет. Вернёшь объект с лишним password, а в модели его нет — клиент не увидит. Плюс корректная OpenAPI-схема ответа.

  3. #fastapi_pydantic3 / 5
    Поле объявлено age: int, в JSON пришло "age": "3" (строка). Что сделает Pydantic v2 (как в FastAPI)?
    A)Отклонит: строка это не int, даже если внутри число
    B)Приведёт к int 3 — числовая строка допустима
    C)Оставит значением строку '3' без приведения типа
    D)Молча подставит 0 как значение по умолчанию поля
    показать ответ и разбор
    +B)Приведёт к int 3 — числовая строка допустима

    // разбор: В нестрогом (lax) режиме, который FastAPI применяет к JSON, Pydantic v2 приводит числовую строку "3" к int 3. А вот "хай" вызовет ValidationError. Нужно запретить такие приведения — включай strict на поле или модели.

  4. #fastapi_pydantic4 / 5
    Что делает Depends(get_db) в сигнатуре эндпоинта FastAPI?
    A)Откладывает запуск эндпоинта до готовности соединения с БД
    B)Создаёт один глобальный синглтон соединения на приложение
    C)Резолвит зависимость и подставляет её результат
    D)Помечает этот параметр как необязательный в схеме
    показать ответ и разбор
    +C)Резолвит зависимость и подставляет её результат

    // разбор: Depends объявляет зависимость: FastAPI перед вызовом эндпоинта выполнит get_db, а результат подставит в параметр. Так инжектятся сессии БД, текущий пользователь, настройки. Зависимости кэшируются в пределах запроса и умеют цепляться друг за друга.

  5. #fastapi_pydantic5 / 5
    Эндпоинт объявлен обычным def (не async def). Как FastAPI его исполнит?
    A)Запустит в отдельном процессе через пул процессов
    B)Откажется запускать: путь-операции обязаны быть async
    C)В пуле потоков, чтобы не блокировать event loop
    D)Так же, как async — прямо в цикле событий приложения
    показать ответ и разбор
    +C)В пуле потоков, чтобы не блокировать event loop

    // разбор: FastAPI видит sync-функцию и уносит её в threadpool, чтобы возможный блокирующий код не заморозил event loop. async def исполняется прямо в цикле. Отсюда правило: блокирующий I/O без await безопаснее в обычном def, чем в async с блокировкой.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.