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

Главная → Статьи → Monorepo в Python: несколько пакетов в одном репозитории

Monorepo в Python: несколько пакетов в одном репозитории

Monorepo в Python: структура нескольких пакетов, редактируемая установка, общие зависимости, выборочный запуск тестов в CI и сравнение с полирепозиторием.

Прямой ответ

Monorepo — один репозиторий с несколькими независимо устанавливаемыми пакетами. В Python это означает несколько каталогов, каждый со своим pyproject.toml, при общих инструментах и единой истории изменений.

Типичная структура:

platform/
├── pyproject.toml          # общие инструменты разработки
├── packages/
│   ├── billing/
│   │   ├── pyproject.toml
│   │   └── src/billing/
│   ├── notifications/
│   │   ├── pyproject.toml
│   │   └── src/notifications/
│   └── shared/
│       ├── pyproject.toml
│       └── src/shared/
└── services/
    └── api/
        ├── pyproject.toml
        └── src/api/

Каждый пакет описывается отдельно и устанавливается независимо; о настройке — в статье про структуру пакета.

Разработка ведётся через редактируемую установку: пакет устанавливается в окружение ссылкой на исходники, поэтому правки применяются без переустановки.

pip install -e packages/shared -e packages/billing -e services/api

Зависимость между пакетами внутри репозитория описывается так же, как внешняя:

[project]
name = "billing"
dependencies = ["shared>=1.0"]

При редактируемой установке shared разрешается из исходников, а при сборке дистрибутива — из реестра пакетов.

Сравнение с полирепозиторием:

Monorepo Полирепозиторий
Атомарное изменение в нескольких пакетах Один коммит Несколько согласованных коммитов
Видимость зависимостей Полная Требует публикации версий
Время прогона CI Растёт без фильтрации Независимо по репозиториям
Права доступа Общие Раздельные
История изменений Единая По репозиториям

Основное преимущество — атомарность. Изменение интерфейса в общем пакете и его использование в зависимом попадают в один коммит и проверяются вместе. В полирепозитории это требует публикации промежуточной версии и согласования; о стратегиях — в статье про trunk-based разработку.

Основная сложность — время прогона проверок. Без фильтрации по изменённым каталогам каждый коммит запускает тесты всех пакетов; о настройке — в статье про конвейер CI.

Когда monorepo начинает мешать

Единый репозиторий даёт преимущества при тесных связях, но создаёт издержки, которые проявляются с ростом.

Время проверок. Без фильтрации по изменённым каталогам каждый коммит запускает проверки всех пакетов. При десятках пакетов время конвейера становится ограничением скорости работы.

Права доступа. Разграничение прав внутри репозитория сложнее, чем между репозиториями. Если части кода должны быть недоступны отдельным командам, единый репозиторий требует дополнительных механизмов.

Размер рабочей копии. Клонирование репозитория с историей всех пакетов занимает больше времени и места, чем клонирование одного пакета.

Разные циклы релизов. Пакет с ежедневными релизами и пакет с квартальными в одном репозитории требуют разного подхода к ветвлению и версиям.

Соблазн общих зависимостей. Единый репозиторий провоцирует создание общих модулей, которые используются всеми и меняются редко. Со временем такой модуль становится узким местом: любое его изменение затрагивает всех потребителей.

Критерий проверки: если пакеты редко изменяются в одном коммите, основное преимущество monorepo не используется, а издержки остаются. В этом случае разделение на репозитории упрощает конвейер и права доступа.

Практика: организация monorepo

1. Держите каждый пакет независимо устанавливаемым. Пакет с собственным pyproject.toml может быть опубликован отдельно, что сохраняет гибкость.

2. Используйте редактируемую установку для разработки. Правки в общем пакете применяются сразу, без переустановки зависимых.

3. Настройте выборочный запуск тестов. Конвейер определяет изменённые каталоги и запускает тесты затронутых пакетов и их зависимых.

4. Разделяйте версии пакетов. Пакеты публикуются независимо, и версия каждого меняется при изменении его кода, а не при каждом коммите.

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

6. Фиксируйте зависимости между пакетами диапазоном. Зависимый пакет указывает нижнюю границу, а не точную версию.

7. Проверяйте обратную совместимость. Изменение общего пакета влияет на все зависимые, поэтому проверка затрагивает не только изменённый код.

8. Ограничьте глубину зависимостей. Цепочка из пяти уровней вложенных пакетов затрудняет понимание влияния изменения.

Рекомендации:

  1. Начинайте с monorepo при тесных связях. Если пакеты изменяются независимо и редко затрагивают друг друга, преимущества единого репозитория не проявляются.
  2. Измеряйте время конвейера. Рост времени проверок — сигнал к настройке фильтрации по изменённым каталогам.
  3. Не смешивайте пакеты с разным циклом релизов. Ежедневные релизы одного пакета и квартальные другого требуют разного подхода к версиям.
  4. Документируйте структуру. Новый разработчик должен понимать, где находится пакет и от чего он зависит.
  5. Проверяйте установку из дистрибутива, а не только из исходников. Редактируемая установка скрывает ошибки описания состава пакета.

Практический сценарий: переход от полирепозитория из-за согласования версий

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

  1. Диагностия. Типичное изменение интерфейса занимало два дня: публикация общего пакета, обновление зависимости в каждом потребителе, ожидание слияния. За полгода накопилось несколько случаев, когда потребители использовали разные версии общего пакета.
  2. Причина. Атомарное изменение в полирепозитории невозможно: каждый репозиторий сливается независимо, а промежуточное состояние оказывается нерабочим.
  3. Действия. (а) Три репозитория объединили с сохранением истории через перенос в подкаталоги; (б) каждый пакет сохранил собственный pyproject.toml и независимую версию; (в) разработку перевели на редактируемую установку всех пакетов в одно окружение; (г) конвейер настроили на определение изменённых каталогов и запуск тестов затронутых пакетов; (д) общий пакет продолжили публиковать отдельно для внешних потребителей.
  4. Результат. Изменение интерфейса выполняется одним коммитом и проверяется со всеми потребителями сразу. Время прогона конвейера выросло на 15 процентов благодаря фильтрации вместо ожидаемого трёхкратного роста.

Вывод: monorepo решает задачу атомарных изменений между связанными пакетами ценой усложнения конвейера. Критерий перехода — частота совместных изменений: если пакеты изменяются вместе регулярно, единый репозиторий устраняет согласование версий.

FAQ

Должны ли пакеты в monorepo иметь общие версии? Нет. Каждый пакет версионируется независимо, и версия меняется при изменении его кода.

Как устанавливаются зависимости между пакетами? В разработке — через редактируемую установку из исходников, при публикации — из реестра пакетов по указанному диапазону.

Что делать с временем прогона CI? Настраивать фильтрацию по изменённым каталогам: запускать тесты затронутых пакетов и тех, что от них зависят.

Когда monorepo не подходит? Когда пакеты изменяются независимо, имеют разные циклы релизов или требуют раздельных прав доступа.

Источники: setuptools docs (Development Mode), Python Packaging Guide (single-sourcing version), pytest docs — ссылки в шапке.