Структура FastAPI приложения

b089b7b5ba238ab713aeb826d13a4d15.png

Введение

Когда я встал на путь становления fullstack’ом, передо мной появилась проблема: Для того, чтобы спроектировать простенький api для тестирования своих frontend-приключений, необходимо создать fastapi-приложение, развернуть базу данных, настроить подключение к бд с помощью алхимии и так далее.

После нескольких почти одинаковых разворачиваний «простенькой» апишки, которые забирали достаточно много времени, я решил, что пора уже наконец создать репозиторий-шаблон для быстрого разворачивания FastAPI с минимально-необходимой инфраструктурой, чтобы он работал из коробки.

В этой статье я хочу привести свое видение того, как должна выглядеть базовая инфраструктура FastAPI, и описать, как, что и почему я решил включить в структуру своего fastapi-шаблона. Если вы знаете варианты, как сделать лучше — пишите в комментариях, я обязательно это попробую. Я планирую улучшать свой шаблон по мере того, как буду набираться знаний и опыта.

Ссылка не репозиторий с шаблоном: https://github.com/max31ru12/FastAPI-Template

Немного про мой опыт

Я работаю разработчиком в крупной IT-компании, занимающейся, в основном, работой с данными или же базами данных. На работе я использую Django и React, но я также имею опыт разработки на FastAPI в стартапе.

База данных

Я еще не оказывался в такой ситуации, чтобы с каким-то python-приложением мне было необходимо развернуть нечно иное, чем postgres. Поэтому в качестве субд была выбрана постгря, развернутая в докере.

Конфигурация файла docker-compose для БД:

services:
  postgres:
    container_name: postgres
    image: postgres:alpine
    environment:
      - POSTGRES_USER=test
      - POSTGRES_DB=test
      - POSTGRES_PASSWORD=test
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U test"]
      interval: 30s
      timeout: 10s
      retries: 5
    networks:
      - default

Тут все довольно просто:

  • использую postgres:alpine для того, чтобы контейнер занимал меньше места;

  • прокидываю базовые переменные окружения для postgres’а;

  • волюм обязательно;

  • healthcheck по желанию.

В принципе, БД можно использовать любую, но самый частный кейс в моей практике — postgres. Если понадобится, например, Mongo, то придется написать новый сервис.

Структура бэкенда

Шаблон не должен навязывать слепое следование какой-то определенной структуре папок. Поэтому основная директория, где расположен бэкенд, не имеет строгой структуры файлов и папок. Основное приложение находится в директории app/, где расположены папки:

  • models — модели SQLAlchemy;

  • api — все, что с связано у роутингом;

  • utils — какие-то вспомогательные функции;

  • entities — pydantic модели.

Все эти папки (за исключение, наверное, api/) опциональны. Можно их выносить, удалять, объединять и т.п. Важно отметить, что в больших монолитах внутри основной директории /app могут быть различные приложения, внутри которых и будут находиться вышеперечисленные папки.

Внутри main.py все стандартно. Из интересного могу отметить использование ORJSONResponse в создании инстанса приложения. Это позволяет ускорить работу с JSON, так как orjson — самая быстрая либа для работы с JSON (по крайней мере они так заявляют в своем репозитории).

В config.py я решил вынести все, что связано с каким-то константами и переменными окружения. setup_db.py содержит в себе все, что необходимо для конфигурации SQLAlchemy.

Миграции Alembic

Папку с миграциями alembic/ я решил расположить в корневой папке репозитория. Папка с миграциями у меня одна, поэтому ее нет особого смысла помещать в директорию с бэкендом. При появлении приложений внутри /app можно для каждого их них сделать отдельную папку с миграциями, как это реализовано в Django.

Инициализация таблиц — это полностью ответственность alembic, поэтому никакого ручного создания таблиц через:

async with test_engine.begin() as conn:
    await conn.run_sync(Base.metadata.create_all)

Тестирование

Все базовые настройки для тестирования эндпоинтов и взаимодействия с БД находятся в tests/contest.py, так также фикстура для сессий. Тестовая БД создается для всех прогонов тестов и удаляется в конце. В test_example.py два простых теста для того, чтобы пайплайн не прошел, если что-то связанное с БД сломалось.

CI/CD

Я написал простенький и не очень красивый пайплайн, который проверяет установку все зависимостей, прогоняет линтеры и тесты, а также поднимает docker-compose с БД и бэкендом.

Также есть pre-commit с несколькими линтерами.

Образ FastAPI

Для бэкенда я написал докер-файл, который находится по пути compose/backend/Dockerfile. В принципе, можно было оставить этот файл в корневой папке проекта, но что если надо будет написать еще один Dockerfile для какого-нибудь celery-воркера? Именно для таких ситуаций я разделил докер-файлы по папкам внутри директории compose.

Конфигурация докер-файла:

FROM python:3.12-slim

RUN apt-get update -qy

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

WORKDIR /backend

COPY requirements-dev.txt ./

RUN pip install -r requirements-dev.txt

COPY ./app ./app
COPY ./alembic ./
COPY alembic.ini ./

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--reload"]

EXPOSE 8000

В образе все стандартно:

  • Образ python использую slim ради экономии места;

  • Использую pip. Иногда я использую poetry для fastapi-проектов, но пока что я не увидел на практике явных преимуществ poetry по сравнению с pip. Понятное дело, что они есть, но они не всегда нужны;

  • alembic — папка с миграциями. Можно перенести ее в директорию приложения app;

  • alembic.ini меняться вряд ли будет, поэтому скопируем его отдельной командой, чтобы docker его закэшировал.

Сервис для бэкенда в docker-compose файле:

  backend:
    container_name: backend
    build:
      context: .
      dockerfile: compose/backend/Dockerfile
    depends_on:
      - postgres
    environment:
      - DB_HOST=${DB_HOST:-postgres}
      - DB_PORT=${DB_PORT:-5432}
      - DB_PASSWORD=${DB_PASSWORD:-test}
      - DB_USER=${DB_USER:-test}
      - DB_NAME=${DB_NAME:-test}
      - DEV_MODE=${DEV_MODE:-true}
    ports:
      - "8000:8000"
    restart: unless-stopped
    volumes:
      - ./app/:/backend/app/
      - ./alembic/:/backend/alembic
    networks:
      - default

Запуск приложения

Любое приложение, построенное на основе моего шаблона, запускается с помощью команды:

docker compose -f local.yml up --build -d

После запуска контейнеров бэкенд будет доступен по стандартному адресу.

Миграции проводятся через контейнер с бэкендом:

docker compose -f ./local.yml run --rm backend alembic upgrade head

Аналогично можно создавать миграции:

docker compose -f ./local.yml run --rm backend alembic revision --autogenerate -m "name" --rev-id "001"

Заключение

Шаблон репозитория получился довольно топорный, но имеющий базовую инфраструктуру для разработки и тестирования, а также возможности для более гибкой настройки и пути для масштабирования приложения.

Из явных проблем могу отметить небольшие танцы с бубном при использовании удаленного интерпретатора из докера, но это терпимо. Также можно написать более красивый пайплайн и, при необходимости, более продуманно разместить миграции и конфигурационные файлы, но это вкусовщина и зависит от масштаба бэкенда.

Habrahabr.ru прочитано 2051 раз