DevOps инженеры часто пишут скрипты и консольные утилиты на python, которые хочется распространять не в первозданном виде, а в качестве пакета, поставив который из приватного registry вашей компании, на машине сразу появился бинарный файл для использования утилиты.
Сначала заведем тестовый репозиторий в gitlab с такой структурой проекта:

Наш тестовый проект будет уметь читать переданный через аргумент CLI yaml файлик и выводить его в консоли.
Структура проекта:
src — наш пакет тут.
test_package — имя пакета. Внутри этой директории лежит код.
depends — директория(модуль) с нашим вспомогательным кодом, который нужен для пакета.
Все файлы __init__.py в данном примере пустые и нужны, только чтобы python понял, что это пакет, а не просто директория. Также считается хорошим тоном заводить эти файлы, пусть и пустые.
Рассмотрим main.py:
from depends.yaml_reader import YamlReader
from depends.cli import CommandLineParser
args = CommandLineParser.create_parser().parse_args()
cmd_args = CommandLineParser(**vars(args))
read_yaml = YamlReader(cmd_args.file)
def main():
    yaml_data = read_yaml.read_yaml()
    print(yaml_data)
if __name__ == "__main__":
    main()Тут импортируем нужные нам пакеты и создаем CLI, читаем yaml и «принтуем» его.
cli.py:
import argparse
from dataclasses import dataclass
@dataclass
class CommandLineParser:
    file: str
    @classmethod
    def create_parser(cls):
        parser = argparse.ArgumentParser(description="Test CMD")
        parser.add_argument(
            "-f", "--file", dest="file",
            required=True,
            help="Path to yaml file"
        )
        return parserЭто класс, в котором мы реализуем на CLI. В тестовом варианте всего один аргумент, этого достаточно.
yaml_reader.py:
import yaml
from dataclasses import dataclass
@dataclass
class YamlReader:
    yaml_path: str
    def read_yaml(self):
        with open(self.yaml_path, "r") as yaml_file:
            data = yaml.safe_load(yaml_file)
        return dataКласс который принимает на вход путь из аргумента CLI до нашего yaml файлика.
setup.py:
from setuptools import setup, find_packages
setup(
    name='test_package',
    author="Trusikhin Andrei",
    version='0.1',
    description="Test packages",
    packages=find_packages(where='src/test_package'),
    package_dir={'': 'src/test_package'},
    install_requires=[
        "PyYAML",
        "requests"
    ],
    entry_points={
        'console_scripts': ['cmd_test = test_package.main:main']
    },
    python_requires='>=3.8'
)Здесь описано то, как мы будем собирать наш пакет с использованием setuptools. Обязательно используем метод find_packages для автоматического поиска всех пакетов и указываем директорию поиска.
entry_points — это точка входа в нашу консольную тулзу. cmd_test — бинарь, который прилетит вместе с установкой пакета и автоматически будет добавлен в PATH переменную. Он равняется имени пакета, функции и файлу из которого вызываем функцию. В нашем случае это main:main.
Файл requirements.txt:
setuptools
wheel
twineИ тестовый test.yaml:
test: testСборка пакета:
Устанавливаем нужные зависимости:
pip3 install -r src/requirements.txtСобираем пакет:
python3 src/setup.py sdist bdist_wheelПосле сборки внутри директории dist будут нужные нам файлы:

Уже сейчас мы можем установить пакет вот так:
pip3 install dist/test_package-0.1-py3-none-any.whlИ использовать нашу утилиту:
cmd_test -f test.yaml
Публикация пакета в gitlab registry:
Чтобы опубликовать пакет, нужно авторизоваться. Для этого существует далеко не один вариант, я же использую авторизацию через deploy_token. Когда определен deploy token в проекте, при запуске pipeline автоматически становятся доступны переменные CI_DEPLOY_USERNAME и CI_DEPLOY_PASSWORD.
Также можно использовать и CI_JOB_TOKEN, это временный токен, который существует во время выполнения pipeline и он обладает правами пользователя, который этот pipeline запустил.
Создать deploy token можно в Settings — Repository — Deploy tokens:

Токен создан, креды получены. Теперь мы можем опубликовать наш пакет:
TWINE_PASSWORD=${CI_DEPLOY_PASSWORD} TWINE_USERNAME=${CI_DEPLOY_USERNAME} python3 -m twine upload --repository ci_generator --repository-url http://localhost:8080/projects/1/packages/pypi dist/* --verbose
После этого мы можем увидеть его в gitlab package registry:

Чтобы скачать пакет из registry нужно воспользоваться командой:
pip install test-package --index-url http://__token__:<your_personal_token>@localhost:8080/api/v4/projects/1/packages/pypi/simpleТаким образом можно распространять python скрипты удобным способом.


