Как использовать GitHub Actions и настроить интеграцию с PyPI для Python-проектов

от автора

Github actions

Github actions

Введение

Когда я создавал свой первый 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.По умолчанию обычно рекомендуются openedsynchronize и 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 выполняет следующие действия:

  1. Устанавливает Python нужной версии.

  2. Устанавливает зависимость Poetry 1.7.1 с использованием кэша GitHub.

  3. Создаёт Python .venv в корне проекта, если кэш не найден. Кэш виртуального окружения зависит от версии Python и poetry.lock.

  4. Если закэшированное виртуальное окружение не найдено, устанавливаются зависимости проекта.

  5. Запускаются тесты.

Использование сервисов в 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.111.4.52

  • 3.112.0.31

  • 3.121.4.52

  • 3.122.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:

  1. Устанавливает Python.

  2. Устанавливает зависимость Poetry 1.7.1 с использованием кэша GitHub.

  3. Собирает source- и wheel-архивы.

  4. Публикует пакет в 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/