О чем статья: при разработке проектов, и, особенно, распределенных приложений, возникает необходимость использования некоторых частей приложения в качестве отдельных модулей. Например скомпилированные классы для gRPC, модули для работы с БД, и многое другое, могут применяться в неизменном виде в кодовой базе десятка микросервисов. Оставив за скобками копипасту, как «хорошую» плохую практику. Можно рассмотреть git submodules, однако, такое решение не очень удобно тем, что, во‑первых, нужно предоставлять разработчикам доступ к конкретным репозиториям с кодовой базой, во‑вторых, нужно понимать, какой коммит надо забрать себе, и в‑третьих установка зависимостей для кода, включенного в проект как субмодуль, остается на совести разработчика. Менеджеры пакетов (pip, или, лучше, poetry), умеют разрешать зависимости из коробки, без лишних действий, и, в целом, использование менеджера пакетов значительно проще, чем работа с субмодулем. В статье рассмотрим, как организовать реестр пакетов в GitLab, а также различные подводные камни, поджидающие на пути к удобной работе с ним. Для кого: статья будет полезна разработчикам, столкнувшимся с необходимостью организации приватного реестра пакетов, в качестве руководства по организации такого реестра в GitLab. Шаги по организации частного реестра пакетов отражены в документации. По сути, в описанном варианте размещения, реестр представляет собой директорию, раздаваемую по HTTP, и содержащую .tar.gz и\или .whl пакеты, распределенные по папкам, соответствующим именам пакетов. Автоматическая загрузка пакетов в репозиторий в таком варианте является задачей "на подумать". В случае с GitLab становится возможным организовать работу таким образом, что кодовая база пакетов и реестр пакетов хранятся в одном пространстве, что дает следующие возможности:  использовать каждый репозиторий как реестр пакетов; создать общий реестр, содержащий все пакеты. Второй способ, с моей точки зрения является более предпочтительным, так как для установки пакетов из разных реестров потребуется генерировать учетные данные, прописывать адреса реестров в конфигурации, в большем объеме, чем при использовании единого реестра. Мы будем использовать отдельный проект, как реестр пакетов, создаваемых нами. Достаточно просто создать новый проект, и, опционально, обозвать его так, чтобы было понятно, что это реестр пакетов. Список загруженных пакетов и дополнительную информацию о них можно посмотреть, перейдя в пункт Packages & Registries > Package Registry. К слову, GitLab может быть реестром не только PyPI-пакетов, но и npm, NuGet, и т.д. Для загрузки пакетов в реестр, и их установки в проекте, нам учетные данные. В документации указано, что можно использовать следующие виды токенов: Personal access token - аутентифицирует владельца токена в соответствии с правами на репозиторий. Данный вид токенов лучше использовать для выдачи доступа конкретных пользователей к реестру. В этом случае пользователю необходимо назначить права на чтение из реестра, а access token генерируется ими самостоятельно. Project access token - подойдет в случае, если необходимо дать доступ к реестру пакетов большому числу пользователей. Если использовать этот вид токена для передачи пользователям - возникает опасность “утечки” токена. Да и банально отобрать права у конкретного пользователя (в случае использования одного токена) не получится. Либо все, либо ничего. Group access token - позволяет получать доступ ко всем пакетам в группе проектов. В моем случае используется следующая схема: Для загрузки пакетов в реестр используется Project access token с правами write_registry. Данный токен используется исключительно при автоматической сборке пакетов. Прописан в Variables для репозиториев, содержащих исходный код модулей; Пользователи получают доступ с применением Personal access token. Создадим новый проект, в котором будет храниться код модуля. Для сборки пакета будем использовать poetry. Для начала установим poetry. Далее, если репозиторий для модуля уже создан, и там есть код, можно воспользоваться командой: В противном случае можно создать новый проект (вместе со структурой каталогов) командой: И в том и в другом случае в проекте появится файл pyproject.toml, который используется poetry для хранения информации о проекте (наименование, описание, зависимости). Если у вас уже есть файл requirements.txt и вы не хотите руками переносить все, что наросло за время разработки, можно перенести зависимости в poetry одной командой: Теперь давайте заглянем в pyproject.toml: Здесь нужно обратить внимание на первую секцию файла - , в ней описывается общая информация: имя пакета, версия, описание, и поддиректории, в которых непосредственно находится код модуля (за это отвечает строчка packages = [{include = "hello_world_package"}]). Если файлы с кодом находятся в корне проекта - перенесите их в отдельную поддиректорию, и укажите ее имя в строчке packages. Также создайте README.md с детальным описанием пакета. Этих настроек достаточно для построения файлов пакета.  Команда poetry build создаст в корне проекта папку dist, в которой будут размещены .tar.gz и .whl файлы пакетов (в соответствии с версиями). Пакет, как правило, не живет по принципу “выстрелил и забыл”. В процессе разработки в исходный код могут вноситься изменения, и возникает необходимость отличать одну версию от другой. Версия, хранимая в pyproject.toml, является текущей, и при пересборке пакета без обновления версии содержимое папки dist (для конкретной версии, разумеется) будет просто перезаписано. Поэтому, перед построением новой версии, нужно изменить номер в pyproject.toml. Руками оно, понятное дело, веселее, но предлагаю отойти от “обновления версий курильщика”. В poetry доступна команда poetry version с опциональными аргументами (patch, major, minor…) инкрементирующими версию в соответствии с правилом для каждого типа инкремента. Например, для выпуска новой версии, содержащей незначительные багфиксы, и не ломающей совместимость, можно использовать poetry version patch. При выходе новой версии, содержащей значительные изменения - poetry version major. Забегая вперед замечу, что GitLab не примет пакет, если такая версия уже есть в его реестре, поэтому “поднимать” версию перед загрузкой пакета - необходимо. Загрузка пакета в реестр возможна с помощью команды poetry publish, однако на своем опыте я неизменно получал 422-ю ошибку при попытке отправить изменения. Поэтому расскажу о том, как загрузить пакет в реестр, используя пакет twine. Сначала, конечно же, установим нужный пакет: Нам понадобится создать файл .pypirc для хранения информации о реестре (адрес, токен для доступа): Подставив в адрес реестра, логин и пароль нужные значения, можем загрузить собранный ранее пакет. Информация о пакете должна отобразиться на странице реестра пакетов. Собственно, устанавливать пакет я также рекомендую через poetry, поскольку pip не хранит информацию о том, откуда пакет был взят, и pip install -r requirements.txt будет искать пакет не там, где надо. Либо придется делить зависимости на части, хранить в отдельный requirements.txt, и указывать pip`у откуда что нужно брать. Poetry же хранит информацию об источнике пакета в pyproject.toml. При развертывании проекта остается только добавить данные для аутентификации. В общем случае, порядок действий следующий: Добавляем новый адрес реестра в список (gitlab в данном случае - просто имя) Добавляем данные для аутентификации Указываем poetry откуда поставить пакет В pyproject.toml, в информации о пакете, появятся данные об удаленном реестре, и связи пакета с конкретным реестром. Теперь, для установки пакета другими разработчиками им нужно сообщить: Адрес реестра; Имя пакета; Данные для доступа (либо указать на необходимость самостоятельной генерации). Напоследок немного автоматизации. Гораздо удобнее, когда в каждом репозитории с кодом модулей настроена автоматическая сборка и загрузка в реестр прямо в CI\CD-пайплайне. Для этого можно использовать следующий скрипт: Данный скрипт работает внутри docker-контейнера, запускаемого раннером. В контейнер в качестве env-переменных передаются данные для аутентификации. В файл .pypirc заранее внесена информация об адресе реестра, и остается только заменить токен. Но возникает проблема с обновлением версии. Два разработчика могут установить одну и ту же версию в pyproject.toml, и, в результате, какие-то изменения могут потеряться, так как GitLab не примет новый пакет с уже существующей в реестре версией. Выход из этой ситуации видится только один - определять последнюю актуальную версию в реестре, записывать ее в pyproject.toml, поднимать версию с помощью poetry version [patch|minor|major|...]. Для получения пакетов из реестра можно воспользоваться скриптом: Скрипт, используя GitLab API, получит список пакетов в json, отсортированный по убыванию (самый последний загруженный пакет будет первым). Остается только подменить версию в pyproject.toml, поднять ее, и загрузить пакет в реестр. Управление PyPI-пакетами в GitLab достаточно простое. Буквально за пару часов можно заменить копипастнутые\загруженные через git submodules части приложения на более изящное решение, которое, к тому же, обновляет версии при каждом пуше в репозиторий.  В конце статьи хочу порекомендовать бесплатный урок от OTUS по теме: "Design patterns". На уроке будут рассмотрены основные категории и наиболее известные паттерны. Подробности по ссылке.   Web-developer