Введение
Когда я создавал свой первый Python-пакет, dataclass-sqlalchemy-mixins(Github или pypi), я столкнулся с интересной задачей:как настроить CI/CD в GitHub так, чтобы при отправке новых изменений ничего не ломалось, а код автоматически публиковался в PyPI. О том, как опубликовать собственный пакет, я рассказывал в этой статье.
Обычно, чтобы проверить любой коммит, попадающий в master-ветку через pull request, необходимо запускать тесты. Кроме того, полезно использовать линтеры для проверки стиля кода, особенно если над проектом работают несколько разработчиков.
CI (Continuous Integration) — практика автоматической сборки и тестирования изменений в коде после их добавления в репозиторий.
CD (Continuous Delivery) — автоматизированная доставка кода в окружения разработки или продакшена.
Создание workflows
GitHub поддерживает Actions — автоматизированные процессы, которые могут запускать одну или несколько job, включая CI/CD-пайплайны. Подробнее о них можно прочитать в документации GitHub.
Workflows — это то, что GitHub будет запускать согласно вашей конфигурации.
Они должны находиться в директории .github/workflows и иметь формат .yaml.
Например, если мы хотим запускать тесты после каждого коммита, нужно создать файл test.yml. Я выбрал имя test для удобства и чтобы показать, что этот workflow отвечает именно за тесты, но название может быть любым.
name: "Test"on: pull_request: types: - "opened" - "synchronize" - "reopened" push: branches: - '*' workflow_dispatch:jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v4 - name: Set up Python 3.12 id: setup-python uses: actions/setup-python@v5 with: python-version: 3.12 - name: Cache poetry install uses: actions/cache@v4 with: path: ~/.local key: poetry-${{ steps.setup-python.outputs.python-version }}-1.7.1-0 - uses: snok/install-poetry@v1 with: version: 1.7.1 virtualenvs-create: true virtualenvs-in-project: true - name: Load cached venv id: cached-poetry-dependencies uses: actions/cache@v4 with: path: .venv key: venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} - name: Install dependencies if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction - name: Run tests run: poetry run pytest --cov-report=xml
Разберём основные элементы этого файла:
-
name— имя workflow. -
pull_request— указывает, какие типы pull request будут запускать workflow.По умолчанию обычно рекомендуютсяopened,synchronizeиreopened.Подробнее об этих типах можно прочитать здесь. -
push— указывает ветки, при push в которые будет запускаться workflow. -
jobs— workflow может состоять из одной или нескольких job, которые по умолчанию выполняются параллельно, но при необходимости их можно настроить и на последовательный запуск.
Теперь чуть подробнее о том, что происходит при выполнении job в этом примере. Во-первых, имя job можно использовать для настройки rulesets в репозитории или для конкретных веток. В данном случае будет запускаться только одна job — test. Про rulesets подробнее поговорим позже.
Ключ runs-on указывает, на какой операционной системе будет выполняться job. Настройка strategy: fail-fast: false означает, что GitHub не будет отменять все выполняющиеся и ожидающие job в matrix, если одна из job в матрице завершится с ошибкой. Параметр matrix мы рассмотрим немного позже.
Как видно, в steps много похожих записей в ключе uses. Например:
-
actions/setup-python@v5 -
actions/cache@v4 -
snok/install-poetry@v1
а первый взгляд названия actions могут сбивать с толку, но на самом деле это просто пути к GitHub-репозиториям этих actions, без части @v. Например, исходный код первой action можно посмотреть по адресу https://github.com/actions/setup-python . To же самое относится и к другим actions. Часть @v используется для указания версии action.
Ключ with нужен для передачи параметров в action.Например, если мы хотим установить Python 3.12, мы передаём python-version: 3.12 в шаге Set up Python 3.12. Тот же подход применяется и в других шагах.
Если коротко, job test выполняет следующие действия:
-
Устанавливает
Pythonнужной версии. -
Устанавливает зависимость
Poetry1.7.1 с использованием кэша GitHub. -
Создаёт Python
.venvв корне проекта, если кэш не найден. Кэш виртуального окружения зависит от версииPythonиpoetry.lock. -
Если закэшированное виртуальное окружение не найдено, устанавливаются зависимости проекта.
-
Запускаются тесты.
Использование сервисов в workflows
Созданный workflow довольно простой и может не подойти для тестирования более сложных приложений или проектов. Дело в том, что таким проектам часто нужны дополнительные сервисы — например, базы данных или кэш. Конечно, можно писать тесты, которые будут мокать запросы к этим сервисам, но я бы не рекомендовал такой подход, так как он может снизить эффективность тестового покрытия. Я покажу, как добавить сервис в workflow, на примере PostgreSQL.
jobs: test: services: postgres: image: postgres:latest env: POSTGRES_PASSWORD: postgres ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
Добавление ключа services с именем нужного сервиса позволяет поднять его во время выполнения job.Более того, ему можно передавать настройки почти так же, как при использовании docker.
Использование нескольких параметров при запуске job
И наконец, важный случай — когда job нужно запускать с разными параметрами. Например, вам может понадобиться запускать её не только на последней версии Python, но и на нескольких версиях сразу, либо с разными зависимостями. Это особенно полезно, если нужно поддерживать обратную совместимость со старыми версиями зависимостей. Для этого в GitHub есть ключ strategy. Параметр matrix позволяет запускать одну и ту же job с несколькими наборами значений.
jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ["3.11", "3.12"] sqlalchemy-version: ["1.4.52", "2.0.31"]
Job с такой матрицей будет запускаться для каждой комбинации значений параметров.
В результате мы получим четыре запуска job со следующими сочетаниями версий Python и SQLAlchemy:
-
3.11,1.4.52 -
3.11,2.0.31 -
3.12,1.4.52 -
3.12,2.0.31
Доступ к параметрам внутри job можно получить через ${{ matrix.python-version }} или ${{ matrix.sqlalchemy-version }}.
- name: Set up Python ${{ matrix.python-version }} id: setup-python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install sqlalchemy ${{ matrix.sqlalchemy-version }} run: pip install sqlalchemy==${{ matrix.sqlalchemy-version }}
Использование GitHub rulesets
Но иногда простого запуска job для проекта недостаточно. Бывают ситуации, когда нужно ограничить возможность слияния коммитов в master-ветку. Если вы работаете над проектом один, это может быть не так критично, но при командной разработке такая настройка крайне желательна. Она помогает избежать ситуации, когда кто-то случайно сливает коммит, который ломает код, даже если пайплайны падают.
Для этого GitHub предоставляет удобный инструмент под названием rulesets.Открыть его можно в настройках репозитория по адресу https://github.com/{Author}/{Repository}/settings/rules .После создания нового ruleset прокрутите страницу вниз до пункта Require status checks to pass, включите его и укажите, какие job должны завершаться успешно.Чтобы найти нужную проверку, начните вводить имя требуемой job, и GitHub предложит варианты.Обратите внимание: каждая job в matrix будет иметь собственное имя, поэтому их можно выбирать по отдельности.
Публикация в PyPI
В последней части я хотел бы рассказать о том, как настроить интеграцию с PyPI и публиковать новые версии через release. Для этого нужно создать отдельный yaml-файл с workflow для публикации.
name: Upload Python Package to PyPi on releaseon: release: types: [published]permissions: contents: readjobs: deploy: runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/project/dataclass-sqlalchemy-mixins/ steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v3 with: python-version: '3.x' - uses: snok/install-poetry@v1 with: version: 1.7.1 virtualenvs-create: true virtualenvs-in-project: true - name: Build package run: poetry build - name: Publish package uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }}
Этот workflow:
-
Устанавливает
Python. -
Устанавливает зависимость
Poetry1.7.1 с использованием кэша GitHub. -
Собирает source- и wheel-архивы.
-
Публикует пакет в
PyPI.
Да, всё действительно настолько просто, как выглядит. Вам нужно только добавить PYPI_API_TOKEN в secrets репозитория (https://github.com/{Author}/{Repository}/settings/secrets/actions ). После этого создайте новый release (https://github.com/{Author}/{Repository}/releases), и пакет будет автоматически загружен.
ссылка на оригинал статьи https://habr.com/ru/articles/1029354/