Привет, Хабр!

FastAPI сейчас — стандарт де-факто для микросервисов на Python. Flask сдаёт позиции, потому что прикручивать к нему валидацию и Swagger руками всем давно надоело. Django всё ещё крут, но для простых API он слишком громоздкий. В итоге FastAPI — везде.

Раз фреймворк так популярен, то и на собеседованиях спрашивают про него всё чаще. Если у вас в резюме заявлен FastAPI, вас точно будут гонять по асинхронности, внедрению зависимостей и Pydantic.

Я собрал 10 вопросов, которые реально задают на технических интервью разработчикам уровня Junior+ и Middle.

Сразу о главном: зазубривать текст ниже бесполезно. Нормальный собеседующий на первом же уточняющем вопросе поймет, где вы разбираетесь в теме, а где просто выучили ответ. Используйте эту статью как чек-лист: пробегитесь по списку, найдите пробелы в знаниях и пойдите потыкайте код руками. А если в процессе поймете, что база пока хромает и хочется выстроить всё в четкую систему, залетайте на мой курс «FastAPI для начинающих» на Stepik — там мы как раз плавно переходим от теории к реальной практике.

1. В чем главные преимущества FastAPI перед Flask и Django?

Ответ: Здесь от вас ждут не просто перечисления фичей из документации, а понимания, какую именно боль решает фреймворк.

  • Асинхронность из коробки и скорость: Под капотом работает Starlette, поэтому FastAPI изначально спроектирован для работы с asyncio. Производительность на уровне NodeJS и Go.

  • Мощная валидация: За нее отвечает Pydantic. Вы просто пишете аннотации типов Python, а фреймворк сам берет на себя валидацию, сериализацию и десериализацию JSON. Меньше бойлерплейта.

  • Автоматическая документация: OpenAPI (Swagger и ReDoc) генерируется на лету. В том же Flask для этого нужно прикручивать отдельные расширения и писать кучу докстрингов.

Вопрос со звездочкой: В каком случае вы бы НЕ стали использовать FastAPI, а выбрали бы Django?

Как отвечать: FastAPI — это инструмент для микросервисов и создания API. Если задача — быстро собрать классический монолитный проект (например, новостной портал или интернет-магазин), где нужна встроенная панель администратора для нетехнических пользователей, управление сессиями и тесная связка с реляционной БД «из коробки», лучше взять Django. Его философия «батарейки в комплекте» сэкономит недели разработки.

2. Как в FastAPI работает асинхронность? В чем разница между def и async def?

Ответ: Это базовый, но самый коварный вопрос, на котором часто сыпятся джуны.

  • Если вы определяете эндпоинт как async def, код запускается напрямую в event loop. Фреймворк рассчитывает, что внутри вы будете использовать неблокирующие вызовы (await).

  • Если вы используете обычный def, FastAPI понимает, что внутри может быть блокирующий код. Чтобы не “повесить” сервер, он запускает эту функцию во внешнем пуле потоков (threadpool).

Частая ошибка (что любят уточнять на собеседованиях): Что будет, если внутри async def использовать обычный requests.get() или синхронную ORM?

Как отвечать: Вы заблокируете event loop. Фреймворк не сможет переключаться на другие задачи, и все остальные пользователи будут ждать, пока ваш синхронный запрос не завершится. Пропускная способность приложения упадет до нуля.

Главное правило: используете синхронные библиотеки (обычный requests, psycopg2 без асинхронности) — объявляйте эндпоинт через def. Хотите писать async def — используйте асинхронные аналоги (httpx, asyncpg, SQLAlchemy 2.0+).

3. Что такое Pydantic и какую роль он играет во фреймворке?

Ответ: Это библиотека для валидации данных и управления настройками, которая использует стандартные аннотации типов Python.

Когда клиент отправляет JSON-запрос, Pydantic берет на себя всю работу:

  1. Проверяет, правильные ли типы данных пришли.

  2. Автоматически конвертирует их (например, строку "1" в число 1, если в модели указан int).

  3. Если всё ок, отдает вам готовый Python-объект, с которым работает автодополнение в IDE.

  4. Если есть ошибка, сам генерирует понятный ответ со статусом 422 Unprocessable Entity, где подробно расписано, в каком поле косяк.

Пример из практики: Часто спрашивают, как сделать кастомную проверку (например, запретить регистрацию пользователям младше 18). Делается это через декоратор @field_validator:

from pydantic import BaseModel, field_validator

class UserCreate(BaseModel):
    username: str
    age: int

    @field_validator('age')
    @classmethod
    def check_age(cls, value: int):
        if value < 18:
            raise ValueError('Регистрация доступна только с 18 лет')
        return value

4. Как работает система Dependency Injection (DI) в FastAPI?

Ответ: Внедрение зависимостей (DI) — это киллер-фича FastAPI, реализованная через функцию Depends(). Суть проста: если вашему эндпоинту что-то нужно для работы (подключение к БД, данные текущего юзера, параметры пагинации), вы не пишете логику получения этих данных прямо в эндпоинте. Вы выносите её в отдельную функцию-зависимость.

Пример: функция get_db_session(). Вы просто добавляете её в параметры эндпоинта как session = Depends(get_db_session). FastAPI сам вызовет эту функцию перед выполнением запроса, получит сессию и передаст её вам. Это избавляет от дублирования кода и делает эндпоинты максимально чистыми.

Вопрос со звездочкой: Как мокать зависимости (override dependencies) при написании тестов?

Как отвечать: Для этого во фреймворке есть встроенный механизм app.dependency_overrides. Это обычный словарь на уровне приложения. В тестах мы берем реальную зависимость (например, подключение к боевой БД или проверку токена в стороннем сервисе) и подменяем её на тестовую заглушку.

Выглядит это так:

app.dependency_overrides[get_db_session] = override_get_db_session

Как только тест завершился, словарь можно очистить, и приложение снова начнет использовать реальные зависимости. Идеально для изоляции юнит-тестов.

5. Как организовать структуру крупного приложения на FastAPI?

Ответ: Главная ловушка FastAPI (как и Flask) в том, что он не диктует разработчику строгую структуру проекта, как это делает Django. Поэтому джуны часто пишут монолитный main.py на пару тысяч строк. На собеседовании хотят услышать, что вы умеете разбивать код на логические слои.

Главный инструмент для этого — APIRouter. Он работает как мини-версия приложения FastAPI, позволяя разбить монолит на независимые модули (например, отдельно users, отдельно orders).

Пример из практики: Хорошим тоном считается разделение логики на слои. Стандартное дерево проекта выглядит примерно так:

src/
├── main.py             # Точка входа, инициализация FastAPI
├── api/                # Эндпоинты (роутеры)
│   ├── dependencies.py # Общие зависимости (например, get_db)
│   └── v1/             # Версионирование API
│       ├── users.py    # router.get("/users")
│       └── items.py
├── core/               # Конфиги (pydantic-settings), безопасность (JWT)
├── db/                 # Настройка подключения к БД, миграции (Alembic)
├── models/             # ORM-модели (SQLAlchemy)
├── schemas/            # Pydantic-схемы (DTO) для валидации ввода/вывода
└── services/           # Бизнес-логика (или crud/), чтобы не держать её в роутерах

Главное правило, которое стоит озвучить: роутеры не должны содержать бизнес-логику. Их задача — принять запрос, отдать данные в слой services, получить ответ и вернуть его клиенту.

6. Как реализовать фоновые задачи (Background Tasks)?

Ответ: Классический кейс: юзер регистрируется, и нам нужно отправить ему приветственное письмо. Отправка email может занять пару секунд. Заставлять юзера смотреть на лоадер всё это время — плохая идея.

В FastAPI для этого есть встроенный класс BackgroundTasks. Он позволяет выполнить функцию после того, как клиенту уже ушел HTTP-ответ.

Как это выглядит в коде:

from fastapi import BackgroundTasks, APIRouter

router = APIRouter()

def write_log(message: str):
    with open("log.txt", "a") as f:
        f.write(message)

@router.post("/send-notification/")
async def send_notification(email: str, background_tasks: BackgroundTasks):
    # Добавляем задачу в фон
    background_tasks.add_task(write_log, f"Уведомление отправлено на {email}")
    return {"message": "Уведомление будет отправлено на фоне"}

Вопрос со звездочкой: Когда встроенных BackgroundTasks становится недостаточно и пора тащить в проект Celery или Taskiq?

Как отвечать: У встроенных фоновых задач есть три критичных ограничения:

  1. Отсутствие гарантии выполнения (Persistence): Задачи хранятся в оперативной памяти конкретного воркера. Если сервер (или pod в Kubernetes) перезагрузится или крашнется — все невыполненные задачи потеряются навсегда.

  2. Блокировка CPU: Если фоновая задача требует тяжелых вычислений (например, ресайз картинок или ML-инференс), она сожрет ресурсы event loop’а или threadpool’а текущего процесса, и приложение начнет тормозить.

  3. Масштабирование: Нельзя раскидать выполнение задач на отдельные сервера.

Поэтому BackgroundTasks — это для легких и некритичных I/O операций (кинуть простую метрику, быстрое письмо). Для всего остального (генерация тяжелых отчетов, парсинг, надежная рассылка) берем брокеры сообщений (Redis/RabbitMQ) + полноценные воркеры вроде Celery, Taskiq или RQ.

7. Как работать с базами данных в асинхронном контексте?

Ответ: Работа с БД — это то место, где чаще всего ломается асинхронность у новичков. Если в async def эндпоинте вызвать синхронный запрос к базе (например, через стандартный psycopg2 или старую алхимию), то весь event loop заблокируется в ожидании ответа от базы.

Поэтому для FastAPI жизненно необходимо использовать асинхронные драйверы (чаще всего это asyncpg для PostgreSQL) и асинхронные ORM.

Два самых популярных варианта сейчас:

  1. SQLAlchemy 2.0+: Наконец-то завезли полноценную нативную асинхронность. Используем AsyncEngine, AsyncSession и пишем await session.execute(query).

  2. Tortoise ORM или Piccolo: ORM, которые изначально писались под asyncio (синтаксис Tortoise очень похож на Django ORM, что облегчает переход).

Сложность из практики: Как правильно управлять сессиями базы данных?

Как отвечать: Сессию нужно открывать при начале запроса и обязательно закрывать после (чтобы не исчерпать пул соединений). В FastAPI это элегантно решается через систему Dependency Injection (генераторы).

from sqlalchemy.ext.asyncio import AsyncSession

# Наша зависимость-генератор
async def get_db_session() -> AsyncSession:
    async with async_session_maker() as session:
        yield session # Отдаем сессию эндпоинту
        # Код после yield выполнится после завершения запроса (закрытие сессии)

8. Middleware в FastAPI — зачем нужны и как работают?

Ответ: Middleware (промежуточное ПО) — это функция, которая стоит “на границе” вашего приложения. Она перехватывает каждый входящий HTTP-запрос до того, как он доберется до роутера/эндпоинта, и каждый исходящий ответ перед тем, как он улетит клиенту.

Зачем это нужно (практические кейсы):

  1. CORS (Cross-Origin Resource Sharing): Встроенный CORSMiddleware добавляет нужные заголовки, чтобы ваш фронтенд на React/Vue мог делать запросы к API с другого домена. Без него браузер просто заблокирует запросы.

  2. Логирование: Можно написать свою middleware, которая будет логировать IP клиента, URL запроса и тело ответа.

  3. Замер времени (Time process): Популярная задача на собеседовании — написать middleware, которая считает, сколько миллисекунд выполнялся запрос, и добавляет это время в заголовок ответа (например, X-Process-Time).

Пример (измеряем время запроса):

import time
from fastapi import Request

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.time()
    
    # Передаем запрос дальше (в эндпоинт)
    response = await call_next(request) 
    
    # Запрос выполнился, модифицируем ответ
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    
    return response

9. Как реализовать глобальную обработку ошибок?

Ответ: В базовом сценарии, если внутри эндпоинта что-то пошло не так (например, пользователь не найден), мы просто вызываем встроенное исключение: raise HTTPException(status_code=404, detail="User not found"). FastAPI сам превратит это в аккуратный JSON.

Но на реальных проектах этого мало. Часто нужно перехватывать системные ошибки БД или менять стандартный формат ответов фреймворка, чтобы фронтенду было удобнее их парсить. Для этого используется декоратор @app.exception_handler(). Он позволяет отлавливать конкретные типы исключений на уровне всего приложения.

Практический пример: Классическая задача — кастомизация ошибок валидации Pydantic. По умолчанию FastAPI при неверных данных возвращает статус 422 Unprocessable Entity и довольно громоздкий массив detail. Допустим, мы хотим отдавать ошибки в более плоском и простом виде:

from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    # Достаем только суть ошибки, без лишней меты Pydantic
    errors = [{"field": err["loc"][-1], "message": err["msg"]} for err in exc.errors()]
    return JSONResponse(
        status_code=422,
        content={"detail": "Ошибка валидации данных", "errors": errors},
    )

10. Как тестировать FastAPI приложения?

Ответ: Тестировать FastAPI — одно удовольствие, фреймворк проектировался с оглядкой на это. Золотой стандарт: связка pytest и встроенного TestClient (который живет в fastapi.testclient и работает поверх библиотеки httpx).

Главная фишка TestClient в том, что он позволяет писать синхронные тесты для асинхронных эндпоинтов. Клиент сам управляет event loop’ом под капотом, поэтому вам не нужно везде расставлять await.

Нюансы из практики (где начинаются слезы): Написать client.get("/users") легко. Сложности начинаются, когда к API прикручена база данных. На собеседовании от вас хотят услышать следующий алгоритм:

  1. Изолированная БД: Для тестов нужно поднимать отдельную тестовую базу (например, в Docker через testcontainers или использовать in-memory SQLite, если нет сложных запросов).

  2. Асинхронные фикстуры: В pytest пишется фикстура, которая перед запуском тестов накатывает миграции (Alembic), а после завершения — очищает или удаляет таблицы.

  3. Подмена зависимостей: Тот самый app.dependency_overrides. Мы указываем FastAPI, что при запуске тестов вместо реального подключения к БД get_db_session нужно подсунуть генератор, который отдает сессию от тестовой базы.

# Пример переопределения для тестов
from fastapi.testclient import TestClient
from myapp.main import app
from myapp.dependencies import get_db_session

def override_get_db_session():
    # ... логика выдачи тестовой сессии ...
    pass

app.dependency_overrides[get_db_session] = override_get_db_session
client = TestClient(app)

def test_read_main():
    response = client.get("/")
    assert response.status_code == 200

Заключение

FastAPI обманчиво прост на старте: написать «Hello World» можно буквально в три строки кода. Но чтобы успешно пройти собеседование, нужно понимать, как этот код работает под капотом. Фреймворк требует крепкого знания асинхронности Python, типизации и понимания того, как правильно выстраивать архитектуру, чтобы ваш монолит не превратился в спагетти-код.

Полезные ссылки:

Анонсы новых статей, полезные материалы, а так же если в процессе у вас возникнут сложности, обсудить их или задать вопрос по этой статье можно в моём Telegram‑сообществе. Смело заходите, если что‑то пойдет не так, — постараемся разобраться вместе.

Комментарии (0)