Как стать автором
Обновить

Как я строю удобную инфраструктуру вокруг Python-проектов: линтеры, Poetry, CI/CD и Docker

Уровень сложностиСредний
Время на прочтение7 мин
Количество просмотров7.4K

Скорее всего, каждый из вас сталкивался с такой ситуацией, когда на просторах интернета видишь статью или видео о новой модной штуке, которую обязательно надо попробовать в своих проектах, но почему-то не пробуешь. Забываешь, или просто становится лениво разбираться.

Например, CI/CD. Как сложно в первый раз сесть и разобраться, как все это настроить. А если дело дошло до того, что уже открыта вкладка с докой Github Actions, то скорее всего вы утонете в количестве информации, так и не вычленив ничего дельного, что как-то помогло бы настроить ваш первый пайплайн. Легче уже найти какое-то видео по этой теме :)

Цель данной статьи - поделиться своими знаниями о том, как удобно организовать инфраструктуру для поддержки Python-приложений, и вынести на обсуждение использование новых библиотек, линтеров, пакетных менеджеров и т.п. Ну и конечно же не обойдусь без критики некоторых практик, которые уже я успел повидать. Ну что ж, начинаем!

Файловая структура

Я привык помещать в корневую папку проекта директорию со скромным названием app/ для хранения исходного кода. Иногда эту папку принято называть именем src/(сокращение от source - исходный код), например, команда create-react-app библиотеки React создает файловую структуру как раз с такой папкой.

Дальше возможны 2 варианта развития событий:

1) Маленький проект;
2) Большой проект.

В первом случае все довольно просто - можем исключить такие вещи, как тесты, паттерны, домены. Причина этого проста - писать тесты, репозитории для работы с БД и делить бизнес-логику приложения на домены займет гораздо больше времени, чем фиксить проблемы, возникающие из-за непродуманной архитектуры.

Другое дело большое количество кода. Исходя из своего скромного опыта могу сказать, что большая кодовая база без тестов и продуманной архитектуры - это ад. Для Django это еще терпимо, но для FastAPI или Flask - уже критично.

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

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

  • Чистая архитектура;

  • Domain-Driven Design;

  • Слоистая архитектура.

Даже в рамках их использования можно построить корявую систему, поэтому все стоит пробовать несколько раз, рефлексировать насчет удобства и поддерживаемости своего приложения.

Линтеры

Для опытных разработчиков должно быть все просто - black, flake8 и тд. Но может быть еще проще и даже быстрее - Ruff. Это супер-быстрый линтер, написанный на Rust, который объединяет в себе все привычные для python-разработчиков линтеры в один. Запустить ruff можно командой ruff check . или засунуть в pre-commit:

default_stages: [pre-commit]

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-merge-conflict
      - id: detect-private-key
      - id: debug-statements

  - repo: https://github.com/astral-sh/ruff-pre-commit
    # Ruff version.
    rev: v0.9.6
    hooks:
      # Run the linter.
      - id: ruff
        args: ["--fix"]
      # Run the formatter.
      - id: ruff-format

Пакетные менеджеры

На замену привычному pip я предпочитаю использовать poetry. Использование poetry в качестве пакетного менеджера имеет сразу несколько преимуществ:

  • Избавляет от проблем с кодированием файла requirements.txt при его обновлении в pycharm (эта проблема очень неприятна и постоянно ломает пайплайн);

  • Позволяет удобно управлять деревом зависимостей;

  • Имеет удобный pyproject.toml с dev-зависимостями (теперь не надо иметь несколько разных файлов зависимостей для разработки и прода).

Poetry можно установить как глобально в систему, так и с помощью самого pip (что не рекомендуется). Себе на винду я поставил poerty глобально, прокинул путь до исполняемого файла в переменные окружения и чувствую себя замечательно. Однако, в докер образах предпочитаю использовать poetry через pip.

Также в будущем обязательно буду пробовать новый модный пакетный менеджер uv, разработанный той же компанией, что и Ruff.

Docker

В основном, докер образ для python-приложения примерно похож на этот:

FROM python:3.13-alpine

RUN apk update && apk add --no-cache build-base gcc musl-dev libffi-dev

RUN pip install poetry

ENV PATH="/root/.local/bin:$PATH"

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /project_root

COPY entrypoint.sh .
COPY pyproject.toml poetry.lock alembic.ini ./

RUN poetry config virtualenvs.create false

RUN poetry install --no-interaction --no-root

ENV PYTHONPATH=/project_root

RUN chmod +x entrypoint.sh

ENTRYPOINT ["./entrypoint.sh"]

Разберем этот образ, чтобы всем было понятно что, зачем и почему:

  1. Используем образ alpine, так как это сократит кол-во используемых контейнером ресурсов, а соответственно, поможет сэкономить на ресурсах сервера при деплое;

  2. ENV PATH="/root/.local/bin:$PATH" - прокидывает poetry в переменные окружения системы, чтобы можно было обращаться к poetry из командной строки (все, что устанавливается через pip попадает в /root/.local/bin);

  3. ENV PYTHONPATH=/app - добавляет /project_root в системный путь поиска модулей Python. Это позволяет избавить от ошибок импорта, если используются абсолютные импорты from app.module import some_function;

  4. RUN poetry config virtualenvs.create false - говорит poetry не создавать виртуальное окружение;

  5. entrypoint.sh - bash-скрипт, который выполняется при старте контейнера (используется, когда при старте контейнера надо выполнить сразу несколько команд);

  6. RUN chmod +x entrypoint.sh - делаем файл исполняемым, чтобы можно было его выполнить.

Ну и сам entrypoint.sh:

#!/bin/sh
set -e

poetry run alembic upgrade head
poetry run python3 ./app/main.py

Особое внимание надо обратить на первую строку. Так как Bash не установлен, необходимо использовать именно shell (sh). Цель файла - запустить сразу две команды: провести миграции и запустить само приложение.

CI/CD

Я приведу пример с Github Actions, но Gitlab CI/CD не имеет концептуальных отличий. Пайплайн состоит из трех базовых шагов:

  • Прогон линтеров и тестов (можно разделить на два разных этапа);

  • Сборка приложения (по сути просто сбилдить все контейнеры);

  • Деплой обновленного приложения на сервер.

Вот сам файл-конфигурации пайплайна для пуша или пул-реквеста в ветку main:

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]


jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@main
      - uses: actions/setup-python@v2
        with:
          python-version: '3.13'
      - name: Install dependencies
        run: |
          sudo curl -sSL https://install.python-poetry.org | python3 -
          export PATH="$HOME/.local/bin:$PATH"
          poetry install --with dev
      - name: Lint with ruff
        run: |
           poetry run ruff check .

  build:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Set up docker
        run: |
          # Add Docker's official GPG key:
          sudo apt-get update
          sudo apt-get install ca-certificates curl
          sudo install -m 0755 -d /etc/apt/keyrings
          sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
          sudo chmod a+r /etc/apt/keyrings/docker.asc

          # Add the repository to Apt sources:
          echo \
            "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
            $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
            sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
          sudo apt-get update

          sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

          sudo docker run hello-world

      - name: Create .env
        run: |
          echo "DB_USER=${{ secrets.DB_USER }}" >> .env
          echo "DB_NAME=${{ secrets.DB_NAME }}" >> .env
          echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}" >> .env

      - name: Run docker compose
        run: |
          docker compose up -d

  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts

      - name: Deploy
        run: |
          ssh -i ~/.ssh/id_rsa ${{ secrets.USER }}@${{ secrets.SERVER_IP }} << 'EOF'
            cd MovieBot
            git pull origin main
            docker compose down
            docker compose up --build -d
          EOF

С линтерами всё просто. Сборка приложения требует установки docker и docker-compose, команды для установки берем и копируем с официального сайта. С деплоем все немного сложнее, но основная идея такая:

  1. Прокидываем SSH-ключ из secrets в текущую ОС, чтобы иметь беспарольный доступ к удаленному серверу;

  2. Подключаемся к удаленному серверу;

  3. Прокидываем пачку команд для рестарта контейнеров на удаленный сервер.

Самый простой CI/CD готов.

Документация

Помимо очевидного совета вести README.md для проекта, хочу обратить внимание на ведение документации эндпоинтов. Самый простой способ вести описание эндпоинтов - сразу писать веб-приложения на FastAPI, ведь автогенерируемая документация это невероятно удобно. Но если вы пишете на django, то забивать на документацию не стоит, ведь это одна из первых вещей, через которую новые разработчики будут знакомиться с проектом.

Прочее

Сделайте шаблон для переменных окружения .env-template. Это явно не будет лишним и может немного помочь тем, кто будет пулить ваш репозиторий.

Python движется в сторону типизации, об этом говорит активное развитие библиотек Typing и Pydantic. В сторону типизации также смотрят FastAPI и Aiogram (начиная с 3 версии). Именно сейчас стоит начать пользоваться типизацией, потому что помимо реальной пользы тайпчекинга и подсказок использование типов помогает сделать код более чистым и более читаемым.

Немного про библиотеки

Мир библиотек и фреймворков не стоит на месте. Это не значит, что стоит постоянно держать руку на пульсе и мониторить все новые библиотеки. Но определенно стоит быть знакомым с новыми удобными инструментами, чтобы в будущем, будучи лидом, не выбирать стек технологий по принципу:

НУ Я ЗНАЮ ТОЛЬКО ДЖАНГО, НО ОТ ДЖАНГО НАМ МАЛО ЧТО ВООБЩЕ НАДО, НО Я ЗНАЮ ТОЛЬКО ДЖАНГО

В большинстве случаев, под вашу задачу уже написана какая-то либа, поэтому прежде чем писать свое решение, задайте себе вопрос: "А может быть для моей задачи есть библиотека?". Вот пример полезных библиотек, которые использую я:

  1. Loguru - невероятно простая по сравнению с встроенной logging библиотека для логирования

  2. Pydantic - быстрая библиотека для валидации на основе встроенных типов Python

  3. Pydantic-setting - определение настроек через модели с валидацией

  4. SQLModel - сочетание pydantic и sqlalchemy

  5. FastAPI - микрофреймворк с очень качественной документацией, предпочитаю использовать его там, где django или слишком медленный и неповоротливый, или оверхед.

Заключение

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

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

Буду рад, если вы заглянете в мою предыдущую статью про структуру FastAPI приложения.

Теги:
Хабы:
+4
Комментарии29

Публикации

Работа

DevOps инженер
30 вакансий
Data Scientist
41 вакансия

Ближайшие события