Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → GitHub Actions для Python: матрицы и кеш

GitHub Actions для Python: матрица версий, кеш зависимостей и быстрые пайплайны

Практика CI для Python-проектов: setup-python с матрицей версий, кеширование pip/uv, слои проверок (lint, тесты, сборка), секреты, артефакты — рабочий шаблон воркфлоу.

Прямой ответ

Рабочий CI Python-проекта собирается из четырёх стандартных блоков: setup-python (нужные версии, включая матрицу 3.11/3.12/3.13 — совместимость версий ловится до релиза), кеш зависимостей (cache: pip/uv-кеш по ключу от lock-файла — установка за секунды вместо минут), слои проверок от дешёвых к дорогим (ruff/формат → mypy → тесты → сборка/интеграция — быстрые падения экономят минуты и нервы), артефакты и секреты (wheel-артефакты между job'ами, секреты только через secrets и с ограничением на fork-PR). Матрица — не догма: полный кросс-прогон (версии × ОС) — на релиз/мерж, в PR достаточно опорной версии с линтерами — иначе очередь CI съедает время команды.

Базовый воркфлоу: анатомия

on: push/pull_request → job lint (ruff + формат-чек; настройка линтера — в статье про ruff) → job test с strategy.matrix.python-version: ["3.11", "3.12", "3.13"] → steps: checkout → setup-python (cache) → install → pytest. Ключевые детали: actions/setup-python@v5 с cache: 'pip' (или uv) кеширует site-packages по хешу зависимостей — первая сборка медленная, остальные быстрые; pip install -e ".[dev]" или uv-sync из lock-файла (воспроизводимость — lock в репозитории; упаковка — в статье про pyproject); pytest с --junitxml и загрузкой отчёта артефактом (разбор падений без перезапуска). Сервисы (PostgreSQL/Redis для интеграционных тестов) — через services: job'а (контейнеры рядом, healthcheck'и — альтернатива локальному docker: Testcontainers-подход тоже работает в Actions, если runner с docker).

Матрицы: когда полный кросс, а когда нет

Полная матрица (версии Python × ОС [ubuntu, windows, macos]) умножает время и квоты — её место в релизном воркфлоу и nightly; в PR-пайплайне — одна ОС (ubuntu) и две-три версии (поддерживаемый диапазон), остальное — периодикой. fail-fast: false в матрице — видеть полную картину несовместимостей, а не первую; continue-on-error для экспериментальных версий (3.14-dev — падает, но не блокирует). Для нативных зависимостей (numpy/scipy-стек) кросс-ОС важен (бинарные колёса); для чистого Python — ubuntu-матрицы обычно достаточно. Отдельный приём — include-комбинации (специфичная пара «версия+набор env-переменных») вместо декартова взрыва.

Слои, параллельность и скорость

Принцип «дёшевое раньше»: линтеры и формат падают за 10 секунд и не ждут десятиминутных тестов; тяжёлые integration-тесты — отдельным job'ом (параллельно с юнитами, needs только там, где реально нужна последовательность). Скорость: параллельный pytest (-n auto через xdist — pytest-плагины); кеш всего кешируемого (зависимости, сборки, docker-слои — actions/cache с осмысленными ключами: «по lock-файлу», не «по дате»); таймауты на job'ы (timeout-minutes — зависшая сборка не ждёт час); concurrency-группы с cancel-in-progress (новый пуш отменяет устаревший прогон ветки — экономия квот на активных ветках). Когда GitHub-hosted runner мал (тяжёлые сборки, приватные сети) — self-hosted (настройка своей машины — в статье про self-hosted runner).

Секреты, окружения и безопасность

Секреты — только secrets.* (логи маскируются автоматически; печать env целиком — классическая утечка); fork-PR'ы секретов не получают по умолчанию — и это правильно (workflow из чужой ветки с вашими токенами — вектор атаки: для fork-репозиториев pull_request_target использовать осознанно и никогда с checkout кода PR до проверок). Защита веток: required checks (мерж только с зелёным CI), environments для деплоя (продакшн-секреты — в отдельном environment с approval — случайный мерж не деплоит). Публикация пакета/деплой — отдельным воркфлоу на тег (trusted publishing в PyPI без токенов — механика упаковки); артефакты между job'ами — upload-artifact/download-artifact (собранный wheel тестируется и публикуется один и тот же файл — без пересборки «на удачу»).

Практические грабли

Кэш «не работает» — обычно ключ не совпадает (lock-файл не в репозитории или путь не тот); «тесты падают в CI, проходят локально» — расхождения окружения (версии зависимостей без lock, часовые пояса/локали runner'а — UTC, отсутствие docker-in-docker, переменные, которых нет в env) — лечится lock-файлом и явными env; «CI зелёный, прод сломан» — тесты не покрывают путь деплоя (миграции, сборка — добавляйте smoke-шаг на реальном артефакте); flaky-тесты — карантин и починка, а не перезапуск до зелёного (перезапускаемый CI учит команду игнорировать красный). Ревизия раз в квартал: время прогонов, мёртвые шаги, неактуальные версии — CI тоже код, и он тоже гниёт.

Практический сценарий: минимальный рабочий пайплайн

Соберём CI для свежего Python-пакета целиком — один файл, покрывающий базу. Триггеры — push в main и pull_request. Job lint: checkout, setup-python 3.12 с cache: 'uv', установка ruff, ruff check . и ruff format --check . — падает за двадцать секунд на неотформатированном коде. Job test с матрицей python-version: ["3.11", "3.12", "3.13"] и fail-fast: false: установка uv sync, pytest --junitxml=report.xml, загрузка отчёта артефактом; сервисы — postgres:16 с healthcheck и env DATABASE_URL, указывающим на localhost сервисного контейнера. Job build с needs: [lint, test]: сборка wheel/sdist через python -m build, twine check, артефакт wheel — чтобы публикация (отдельный workflow на тег) ставила ровно проверенный файл. Таймауты — 10 минут на job, concurrency-группа по ветке с cancel-in-progress. Всё — шестьдесят строк YAML; первый прогон медленный (прогрев кеша), следующие — полторы минуты. Дальше пайплайн растёт по потребности: добавляются mypy-job, ночной прогон полной матрицы с ОС, нагрузочный смоук — но база, которая ловит 90% проблем (стиль, сломанные тесты на трёх версиях, небьющийся пакет), уже работает с первого коммита.

FAQ

Матрица версий Python нужна, если прод на одной версии? Да, если библиотека публичная (пользователи на разных версиях) или апгрейд прода планируется (3.13 в матрице сегодня — безболезненный переход завтра). Для приложения на зафиксированной версии достаточно её плюс следующей.

Как ускорить установку зависимостей радикально? uv (Rust-резолвер, кеш и установка в разы быстрее pip) — setup-python умеет cache: 'uv'; плюс предкомпилированные колёса (тяжёлые пакеты без source-сборки). Экономия на больших сюитах — минуты на прогон.

Нужен ли CI на каждый пуш ветки? На каждый PR — да; на каждый пуш в ветку PR — по вкусу (concurrency с отменой устаревших делает это дёшево). В main — обязательно полный набор.

Как гонять браузерные/E2E-тесты? Playwright в Actions — официально поддержан (браузеры кешируются, скриншоты/видео — артефактами); для Python-бэкенда E2E обычно отдельный workflow с поднятым стеком через services/docker compose.

Источники: документация GitHub Actions, actions/setup-python, кеширование зависимостей — ссылки в шапке.