Автоматический bump версии python пакета

0
(0)

В прошлой статье я рассматривал один из способов публикации python пакета в registry. Сегодня попробуем добавить в этот способ автоматический инкремент версии пакета.

Проект будет все тот же.

Итак, что мы меняем? Во-первых нам нужно изменить файл cli.py, так как мы хотим уметь выводить версию нашего пакета используя ключ «-v» или «—version».

Python
import argparse
from dataclasses import dataclass
from importlib.metadata import version

@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"
        )
        parser.add_argument(
            "-v", "--version",
            action="version",
            version=f"%(prog)s {version('test-package')}",
            help="Show package version"
        )
        return parser

Версию берем из уже установленного пакета через либу importlib.metadata.

Но как быть с версией во время сборки, когда пакет еще не существует?

Для начала __init__.py в корне проекта (директория src) приведем к следующему виду:

Python
import os

__version__ = os.environ.get("PACKAGE_VERSION", "0.1.0")

Здесь мы задаем переменную __version__ в которую будем помещать значение переменной окружения PACKAGE_VERSION (об этом будет чуть дальше) и устанавливать дефолт, если значение переменной не задано.

Редактируем setup.py:

Python
from setuptools import setup, find_packages
from test_package import __version__

setup(
    name='test_package',
    author="Trusikhin Andrei",
    version=__version__,
    description="Test packages",
    packages=find_packages(where='src/test_package'),
    package_dir={'': 'src/test_package'},
    install_requires=[
        "PyYAML",
        "requests",
        "semver"
    ],
    entry_points={
        'console_scripts': ['cmd_test = test_package.main:main']
    },
    python_requires='>=3.8'
)

Здесь мы импортируем нашу переменную и присваиваем переменной version значение нашей переменной.

Для автоматического бампа версии пишем небольшой скрипт на bash:

Bash
#!/bin/bash

function git_fetch() {
    git config --global --add safe.directory '*'
    git remote set-url origin "ssh://git@$CI_SERVER_HOST:2222/$CI_PROJECT_PATH.git"
    git config --global user.name "${GITLAB_USER_NAME}"
    git config --global user.email "${GITLAB_USER_EMAIL}"
    git checkout $CI_COMMIT_REF_NAME
    git fetch
    git reset --hard "origin/$CI_COMMIT_REF_NAME"
}

function git_commit() {
    git commit -am "CI set new $1 -> $2"
    git push origin $CI_COMMIT_REF_NAME -o ci.skip
}

function bump() {
    git_fetch
    export CURRENT_VERSION=$(cat version.yml|grep $1|awk '{print $2}'|tr -d '"')
    export PACKAGE_VERSION=$(python3 -c 'import os;import semver;print(semver.bump_patch(os.environ["CURRENT_VERSION"]))')
    echo -e "\033[0;32mCURRENT_VERSION: $CURRENT_VERSION\nNEW_VERSION: $PACKAGE_VERSION\e[0m"
    echo "PACKAGE_VERSION=$PACKAGE_VERSION" > package_version.env
    sed -i 's/'$1'.*/'$1': "'$PACKAGE_VERSION'"/' version.yml
    git_commit "$(echo $1|tr '_' ' ')" "${PACKAGE_VERSION}"
}

if [[ $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH ]];then
    bump "version_prod"
else
    bump "version_stage"
fi;

Он будет парсить файл version.yml и автоматически обновлять патч версии для прода и дева. При пуше, используем ci.skip, чтобы избежать дублей. Код один и тот же, изменится только версия в version.yml, нет смысла запускать пайплайн ради этого, так как он уже был запущен при первом нашем коммите.

Так как в gitlab-ci это отдельный stage, то нам нужно передать артефакт между stage в котором мы будем хранить версию пакета. Для этого записываем файл package_version.env.

YAML
version_stage: "0.1.0"
version_prod: "1.0.0"

Создаем .gitlab-ci.yml

YAML
default:
  image: "python3.10"
  tags: [ test_tag ]

python:lint:
  stage: quality
  script:
    - find src -name "*.py" |xargs pylint

bump:version:
  stage: bump
  script:
    - pip3 install semver
    - ./bump_version.sh
  artifacts:
    reports:
      dotenv: package_version.env

release:
  stage: release
  needs: 
    - job: python:lint
    - job: bump:version
      artifacts: true
  script:
    - export PACKAGE_VERSION=$PACKAGE_VERSION
    - pip3 install -r src/requirements.txt
    - python3 src/setup.py sdist bdist_wheel
    - TWINE_PASSWORD=${CI_DEPLOY_PASSWORD} TWINE_USERNAME=${CI_DEPLOY_USERNAME} python3 -m twine upload --repository test_package --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi dist/* --verbose
  rules:
    - { when: manual }

Через artifacts мы передаем нашу переменную в другой stage и экспортируем ее в виде переменной окружения, чтобы python мог ее забрать.

Этот CI весьма условный и по-сути набросок. В конкретных компаниях, проектах и так далее обычно используется удаленный CI, построенный в конечном проекте на include.

Насколько статья полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Оставить комментарий