3  versioning policy

Author

Ryo Nakagami

Published

2024-11-30

Modified

2024-12-24

Versioning policy

Def: Versioning

  • Versioningとは,パッケージの異なるバージョンにユニークな識別子を追加するプロセスのこと
  • 多くのPython packageではsemantic versioningが用いられる

ソースコードを変更すると,昔使えたmethodが使えなくなったり,他のパッケージとの互換性問題から動作しなくなったりする可能性があります. パッケージユーザーがプログラム動作や分析の再現性保証ができるように,開発者はパッケージの各ユニークな状態にユニークなバージョン番号を割り当て, それぞれの新しいバージョンを独立してリリースすることが求められます.

▶  versioningのメリット

  • パッケージユーザーが特定のversionのパッケージを指定してインストールできるようになる
  • versioning infoを通して,バグ修正や新機能追加などパッケージの変更内容をユーザーに伝えることができる
  • 依存関係管理を容易にする

mypackageという新しいパッケージを開発したとします.このパッケージは,Agitoというパッケージを利用しており, また Agito 3.0.0 で追加された機能を利用しているとします.このとき,mypackageを利用するプロジェクトでは, < 3.0.0Agitoが利用できないことになります.

適切なversioning policyを用いてれば,pyproject.tomlなどの開発環境設定ファイルに

dependencies = [
    "Agito>=3.0.0,<4.0.0",
]

と記載することで依存関係管理することができるようになります.

Version numbering

📘 Summary

  • semantic versioningに従ったversion numberは MAJOR.MINOR.PATCH の3つのint > 0から構成される
  • MAJOR: 後方互換性のない変更を入れた場合
  • MINOR: 後方互換性のあるenhancementを入れた場合
  • PATCH: 後方互換性のあるBUG FIXを入れた場合
  • versioningされたパッケージがリリースされたされた場合,そのバージョンの内容を変更してはならない.変更を加える場合は,必ず新しいバージョンとしてリリースすること
  • 1.0.0はパッケージの最初の安定版リリースに使用される

semantic versioningのVersion numberingをここでは取り扱います. 一般的には,ソフトウェアの最初のバージョンは通常 0.1.0 から始まり,開発進捗/リリースに合わせてMAJOR.MINOR.PATCHのインクリメントが行われます. version numberを増加 = インクリメントさせることを,version bumpingと呼びます.

▶  Patch release (0.1.0 → 0.1.1)

Patch releaseは後方互換性のあるBUG FIXを入れた場合に行われます.後方互換性とは,パッケージバージョンをアップグレードしても, 以前に記述したコードがそのまま動作することを意味します.

ユーザー目線で操作性に影響を与えないような,内部的なバグ変更がPatch releaseの対象となります.

▶  Minor release (0.1.0 -> 0.2.0)

Minor releaseは,大規模なバグ修正や後方互換性を保ちながら追加される新機能(例: 新しいmethodの追加)を加えた場合に行われます. Minor releaseに伴うversion bumpingがなされる際,PATCH0 に戻る必要があります.

▶  Major release (0.1.0 -> 1.0.0)

1.0.0はパッケージの最初の安定版リリースに使用されます.その後,後方互換性のない変更や多くのユーザーに影響を与える変更が行われた場合に, Major releaseが行われます.後方互換性のない変更は breaking changes と呼ばれます.

パッケージ内のモジュール名を変更することはbreaking changesの一例です.この場合,ユーザーが新しいパッケージにアップグレードすると, 古いモジュール名を使用していたコードが動作しなくなり,コードを修正する必要が生じます.

Python packageでの実装

Python Packaging User Guide > Single-sourcing the Project Version にて

Many Python distribution packages publish a single Python import package where it is desired that the runtime version attribute on the import package report the same version specifier as importlib.metadata.version() reports for the distribution package

つまり,

  • package versionは __version__ attributeで参照できるようにするのが望ましい
  • __version__ attributeの内容は importlib.metadata.version() と一致するのが望ましい(つまり,import_name.__version__, importlib.metadata.version("dist-name")が一致する)

とされています.しかし,git/GitHubを用いた開発ではversionをgit tagという形で格納していたりしますし,Poetryなどのpackage-management toolを用いている場合は pyproject.tomlに記載されていたりします.これらをより考えるべき問題は,version情報を格納したdata entryをどこにすべきなのか,ということになります.

非推奨: __init__.pyでのハードコーディング

transformer, QuantEconpy, sckit-learnなどでは,ソースコード格納ディレクトリ直下の __init__.py

__init__.py
__version__ = "1.7.dev0"

といった形式でversion情報が記載されています.この方法は,現在では非推奨とされています.

▶  非推奨の理由

  • パッケージのバージョンを更新するたびに,__init__.pyを手動で変更する必要があり,変更し忘れが発生しやすい
  • バージョン情報を__init__.pyに直接記述していると,ビルドツールやCI/CDパイプラインでのバージョン管理や更新が難しくなる

推奨: pyproject.tomlによる管理

PEP 621で紹介されているように,現在ではpyproject.tomlを使ったメタデータ管理が推奨されます. ポイントは,

  • version情報はprojectメタ情報を記載するpyproject.tomlで取り扱う
  • __version__ attributeはpyproject.tomlに記載されたversion情報を参照するようにする

この場合,pyproject.toml, __init__.py にそれぞれのラインを記載します

pyproject.toml
[project]
name = "package_name"
version = "1.0.0"

python runtime中でも__version__attribute経由で確認できるように

__init__.py__
from importlib.metadata import version

__version__ = version("package_name")

も設定します.importlib.metadataはパッケージメタ情報についてのインターフェースを提供するモジュールです(Python 3.8以降に導入). package versionを取得したい場合は,versionを取得したいパッケージ名をimportlib.metadata.version()の引数に設定すると, METADATAファイルのversion fieldよりversion infoを取得してくれます.

METADATAファイルは,build toolによって異なりますが,一般的には<package_name>.<version>.dist-infoに格納されています. <package_name>.<version>.dist-infoディレクトリ内の METADATA ファイルは, パッケージがビルドされてインストールされる際に pyproject.toml(およびその他のビルド構成ファイル)から生成されるという関係性があります.

なので,importlib.metadata.version()pyproject.toml の内容を参照していると理解して良いと思います.

Example 3.1 : importlib.metadataによりversion info取得

from importlib.metadata import version

# Get the version of a package (e.g., 'requests')
package_version = version('requests')
print(f"The version of the 'requests' package is: {package_version}")

OutPut

The version of the 'requests' package is: 2.28.1

Class.show_version(): 継承を用いてClassからversion情報を表示する

個人のプラクティスとなりますが,パッケージ開発にてClass開発をする場合,BaseMetaInfoクラスを定義 & 継承することで メインのClassからshow_version() methodを叩くことでversion infoを実行環境と合わせて確認できるようにしています.

import importlib.metadata
import os
import platform
import pprint
import sys


class BaseMetaInfo:
    @classmethod
    def show_version(cls):
        cls.class_name = "synth_coremodel"
        uname_result = platform.uname()
        version_info = OrderedDict(
            {
                cls.class_name: importlib.metadata.version(cls.class_name),
                "python-version": ".".join([str(i) for i in sys.version_info]),
                "OS": uname_result.system,
                "OS-release": uname_result.release,
                "processor": uname_result.processor,
                "number of available cpu": os.cpu_count(),
            }
        )
        pprint.pprint(version_info)

例として,regmonkey_analysisパッケージでLinearEstimatorというClassを実装した場合,

from regmonkey_analysis import LinearEstimator
LinearEstimator.show_version()

OutPut

OrderedDict([('regmonkey_analysis', '6.1.2'),
             ('python-version', '3.11.8.final.0'),
             ('OS', 'Linux'),
             ('OS-release', '6.8.0-49-generic'),
             ('processor', 'x86_64'),
             ('number of available cpu', 32)])

と表示できるようにしています

References