Skip to content

Repository files navigation

python-fastapi-rest-api-example

Python CI

REST API на FastAPI, собранный «по-взрослому»: схема API описана отдельно от кода, база через ORM, проверка входа по схеме.

Зачем это нужно

Пример того, как выглядит API-проект, когда он перерастает один файл с маршрутами:

  • Контракт первичен. API описан на TypeSpec в main.tsp, из него компилируется OpenAPI, а из OpenAPI — pydantic-модели (hey-api). Приложение отдаёт клиенту тот же файл контракта, из которого сгенерированы его модели, поэтому документация не расходится с реализацией. make generate-check в CI не даёт сгенерированному разойтись со спекой.
  • Маршруты регистрируются по спеке. Обработчики связываются с операциями контракта по идентификатору (app/glue.py), поэтому забытая операция роняет приложение на старте, лишняя — тоже, а код успеха, описание и авторизация приходят из контракта.
  • База через SQLModel: модели в app/db/models.py, история схемы — миграции Alembic в migrations/. Приложение прогоняет их на старте, а make migration-check показывает, что модели не менялись без миграции. По умолчанию под ними sqlite в памяти процесса: отдельного сервиса для запуска примера не нужно, постоянное хранение задаётся DATABASE_URL.
  • Проверка входа двумя слоями. Структуру запроса проверяет сгенерированная из контракта схема. Правила, для которых нужна база — уникальность адреса, — живут отдельным слоем в app/validators/, а не внутри обработчика.
  • Авторизация берётся из контракта, а не пишется в обработчиках: схема из security операции применяется при регистрации маршрута. Пока её звали внутри обработчиков, во всех пяти операциях пользователей её забыли, и список, чтение, правка и удаление стояли открытыми. Забыть её теперь негде: обработчик про неё не знает.
  • Права отдельно от аутентификации. Токен сообщает, кто пришёл; распоряжаться курсом может его автор, и это правило вынесено в app/policies.py.
  • Одно тело у всех ошибокRFC 9457, application/problem+json. Модели ошибок тоже сгенерированы из контракта, поэтому ответ описан в нём по построению.
  • Условные запросы. Чтение отдаёт метку версии, повторное чтение с If-None-Match получает 304, а правка с устаревшим If-Match отклоняется — иначе два одновременных PUT перезаписывают друг друга.
  • Две версии контракта из одного приложения. @added(Versions.v2) в TypeSpec — и вторая версия получает поле, которого нет в первой; каждая отдаётся своим документом, из каждого сгенерирован свой набор моделей, и обе проверяются контрактными тестами отдельно.
  • Спека проверяется снаружи. make contract-test натравливает schemathesis на поднятое приложение: тот генерирует запросы из спеки и ловит то, чего не видят ни тесты, ни проверка запросов. Так здесь нашлись 500 на переполнении идентификатора, 500 на null в необязательном поле и ошибка фреймворка, уходившая клиенту в чужом формате.
  • Частота запросов ограничена, и 429 объявлен в контракте у каждой операции. Счётчик живёт в app/middleware.py и считает по адресу клиента.
  • Наблюдаемость. /health отвечает вместе с базой, /metrics отдаёт метрики в формате prometheus, трассировка OpenTelemetry поднимается при заданном коллекторе. В контракт эти адреса не входят — это не часть API.

Генератор python-кода у hey-api молодой, и часть работы, которую в TypeScript-версии он делает сам, здесь пока сделана руками. Что именно и на что это заменится — в TODO.md.

Requirement

  • Python 3.14
  • Node.js 22 — для инструментов контракта: компилятора TypeSpec и генератора моделей. Самому приложению node не нужен.

Структура

main.tsp              контракт: источник истины для всего остального
openapi/              документы OpenAPI, скомпилированные из контракта
app/types/handlers/   pydantic-модели из OpenAPI, руками не правятся
app/main.py           сборка приложения: слои, версии, регистрация маршрутов
app/glue.py           маршруты по спеке — замена серверного плагина
app/contract.py       чтение документов контракта
app/settings.py       конфиг, проверяемый схемой на старте
app/db/               модели SQLModel, проекции и сиды; миграции в migrations/
app/routers/          обработчики и их отображение на операции контракта
app/validators/       правила предметной области, для которых нужна база
app/rules/            правила уровня базы, например уникальность
app/policies.py       права на запись
app/lib/              пароли, метки версий, страницы, ошибки, правка полей
app/middleware.py     метка версии ответа, условные запросы, лимит частоты
scripts/              contract-test.sh, smoke-test.sh и служебные скрипты
tests/                прогон операций через ASGI, без сетевого сервера

Запуск

make setup
make dev
make test

Документация — на http://localhost:8000/docs, спека — на /openapi.json. Вторая версия — /v2/docs и /v2/openapi.json.

Полезное:

make routes             # таблица маршрутов с объявленной авторизацией
make generate-types     # спека из контракта и модели из спеки
make generate-check     # сгенерированное закоммичено и совпадает с контрактом
make contract-test      # schemathesis по спеке
make lint-openapi       # линт контракта
make migration-generate M="описание"  # миграция по изменённым моделям
make migration-check    # модели не менялись без миграции
make test-coverage      # тесты с порогом покрытия
make smoke-test         # собрать образ и проверить его (нужен docker)

Hexlet Ltd. logo

This repository is created and maintained by the team and the community of Hexlet, an educational project. Read more about Hexlet.

See most active contributors on hexlet-friends.

About

Пример REST API на FastAPI: контракт на TypeSpec, модели из OpenAPI, SQLModel и миграции

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages