バージョニング

この議論では、 Python パッケージのバージョニングについてあらゆる角度からカバーします。

正当なバージョン番号

相異なるPythonプロジェクトがそれぞれの事情に合わせて異なるバージョン体系を採用することは構いませんが、しかし、pip のようなツールとの互換性を保つためには、バージョン識別子のための自由度の高いフォーマット、その権威ある参照先は バージョン識別子仕様 ですが、これに準拠することが要求されます。ここではバージョン番号のいくつかの例を紹介しましょう [1]:

  • 簡明なバージョン (最終的なリリース): 1.2.0

  • 開発リリース: 1.2.0.dev1

  • アルファリリース: 1.2.0a1

  • ベータリリース: 1.2.0b1

  • リリース候補: 1.2.0rc1

  • ポストリリース: 1.2.0.post1

  • アルファリリースのポストリリース (こういうことも可能ではあるが非推奨): 1.2.0a1.post1

  • 二つの部分からのみ構成される簡明なバージョン: 23.12

  • たったひとつの部分から構成される簡明なバージョン: 42

  • エポック付きのバージョン (非推奨): 1!1.0

プロジェクトにおいては、最終的なリリースの前にいくつかのプレリリースのサイクルを置くことで既存ユーザによる試験をサポートすることができます。その段階は、アルファリリース、ベータリリース、リリース候補、最終的なリリースの順です。 pip や他の近代的な Python パッケージインストーラは、依存関係にあるパッケージのどのバージョンをインストールするかを決定する際、 (例えば pip install pkg==1.1a3 とか pip install --pre pkg のように) 明示的に要求されていない限りデフォルト設定ではプレリリース群を無視します。

開発リリースの目的は、例えば夜毎のビルド <nightly build> のように開発サイクルの早い時期にリリースを行うことや、Linux ディストリビューションの最新のソースコードからビルドを行うことです。

ポストリリースは、リリースノート内のエラーのように配布されたソフトウェアに大きな悪影響を与えないようなファイナルリリースの軽微なエラーを修正するために使われます。バグ修正のために使うべきではなく、それは新たな (即ち、セマンティックバージョニングを使っているなら第3要素を1だけ増加させた) ファイナルリリースで行うべきです。

最後に、エポックは、バージョニングの方法を変更する時にソート順を修正することを意図しています。例えば、 23.12 のようなカレンダーバージョニングを使っているプロジェクトが 1.0 のようなセマンティックバージョニングに移行するような場合に、 1.023.12 の間の比較は誤った結果になるでしょう。これを訂正するために、 1!1.0 のような明示された エポックを持つことで、新しいバージョン番号が古いバージョン番号よりもより最近のものとして扱われるようにするべきです。しかしながら、これは非推奨であって、例えば 100.0 のようなより大きなバージョン番号でユーザが混乱しそうにないものを使う方が好ましいです。

セマンティックバージョニング対カレンダーバージョニング

バージョニング方法とは、バージョン番号の断片を翻訳するために決められたやり方で、パッケージの新しいリリースように次のバージョン番号がどのようなものになるべきかを決めるやり方です。Python パッケージにしばしば用いられる二つのバージョニング方法は、セマンティックバージョニングとカレンダーバージョニングです。

注意

どのようなバージョン番号を選択するべきかの決定権はプロジェクトのメンテナに委ねられています。これは、バージョン上げにメンテナの観点を反映しているということを実質的に意味します。そのような観点は、バージョニング方法が約束するやり方についてのエンドユーザの受け取り方とは異なるかも知れません。

次のバージョン番号を選定する上で既知の例外があります。保守者は、最後のバージョン断片の変更では後方互換性を保つ変更だけを含むという想定を破るような選択を意識的に行っても構いません。そのような場合の一つは、セキュリティ上の脆弱性を修正する必要がある時です。セキュリティリリースは、しばしば、パッチバージョンとして提供されますが、そうすると必然的にここで述べているような変更をもたらします。

セマンティックバージョニング

セマンティックバージョニング (または SemVer) のアイデアは、3個の部分から構成されるバージョン番号、つまり major.minor.patch を用いることで、プロジェクトの作者は以下のように各段階の数字を増やします:

  • APIの変更で互換性を失う時には major 番号、

  • 後方互換性を保ったままで新機能を追加する場合には minor を、そして

  • 後方互換性を維持したままのバグ修正の場合には patch を増加させます。

Python プロジェクトの大多数は、セマンティックバージョニングに似た方式を使っています。しかしながら、ほとんどのプロジェクト、とりわけ大規模なものでは、多くの変更が技術的には互換性を保たないけれども少数のユーザにしか影響を与えないので、セマンティックバージョニングに厳密に従うことはしていません。そのようなプロジェクトでは、小さな非互換性であってもいつも major 番号を増やす [2] というよりは、非互換性が高い場合やプロジェクトの方向性が変わることを示す時に増やす傾向にあります。逆に、時には、重要ではあるが後方互換性を損なわない新機能に注目を集めるために major バージョン番号を増加させることもあります。

厳密なセマンティックバージョニングを採用しているプロジェクトでは、このアプローチによってユーザが ~= 演算子を用いた 互換リリースバージョン指定子 を使うことができるようになります。例えば、 name ~= X.Yname >= X.Y, == X.* と大まかに言って等価で、つまり、少なくともリリース X.Y を要求していて X が同じである限りは Y が大きくなった後続リリースを許容するということです。同様に、 name ~= X.Y.Z は大まかに name >= X.Y.Z, == X.Y.* と等価で、少なくとも X.Y.Z を要求していて X と Y が同じである限り Z が大きくなった後続のリリースを許容するということです。

セマンティックバージョニングを採用しているPythonプロジェクトでは、 セマンティックバージョニング 2.0.0 仕様書 の第1節から第8節までを甘受すべきです。

人気のあるドキュメンテーションジェネレータである Sphinx は、厳密なセマンティックバージョニング (Sphinx バージョニング方針) を採用しているプロジェクトの例です。有名な科学計算パッケージである NumPy は、マイナーバージョンの更新でも後方互換性のない API 変更を含む場合がある (NumPy バージョニング方針) ということで、明示的に "ゆるい" セマンティックバージョニングを採用しています。

カレンダーバージョニング

セマンティックバージョニングはすべてのプロジェクト向きと言うわけではなく、例えば定期的なリリースサイクルに従う場合や、ある機能を削除する前に何世代にもわたるリリースで非推奨(deprecation)の警告を出すような場合には適していないかもしれません。

calendar versioning (CalVer) の最大の利点は、バージョン番号を見ただけで基盤になっている機能セットがどれほど古いのかが直截にわかることです。

カレンダーバージョン番号は、典型的には 年.月 (例えば2023年12月に対して 23.12) の形を取ります。

標準的な Python パッケージインストーラである Pip はカレンダーバージョニングを採用しています。

他の方法

一連番号によるバージョン付与は、リリースの度に増加する単一の数字で構成されていて、可能な限り単純なバージョニング方法であると見なされています。一連番号によるバージョン付与は開発者にとってはとても管理しやすい反面、一連番号によるバージョン番号を見てもAPIの後方互換性に関する情報がほとんど又は全くわからないので、ユーザにとっては追跡するのが最も困難です。

上に述べた方式を組み合わせて用いることもできます。例えば、日付ベースのバージョン付与と一連番号によるバージョン付与を組み合わせて year.month 型のバージョン番号付与方式を作り出して、それがあるリリースのおよその年数が自動的にわかるようにしているが、あるリリースのその年の中での歩調についてはあまり気にしていないこともあります。

ローカルバージョン識別子

公的バージョン識別子は、 PyPI を通じた配布をサポートするように設計されています。Pythonのパッケージングツール群は、ローカルでの開発でビルドごとの識別子や再配布者が維持管理している変種のリリースの識別子として用いるような 局所的バージョン識別子 の考え方もサポートします。

ローカルバージョン識別子は、公的バージョン識別子の後ろに "+" とローカルバージョンラベルを並べた形を採ります。例えば、Fedora に特有のパッチが適用されたパッケージは、 "1.2.1+fedora.4" といったバージョンになるという具合です。別の例としては、 setuptools-scm によって計算されたバージョンで、Git のデータからバージョンを読み取る setuptools プラグインによるものです。最新のリリース以後に幾つかのコミットがなされた Git リポジトリでは setuptools-scm は "0.5.dev1+gd00980f" のようなバージョンを生成することもありますし、トラックされていない変更のあるリポジトリなら "0.5.dev1+00980f.d20231217" のようになることがあります。

ランタイムにバージョン情報にアクセスする

現在の環境でローカルに利用可能な 配布パッケージ のバージョン情報は、標準ライブラリの importlib.metadata.version() 関数を使ってランタイム内から取得することが可能です:

>>> importlib.metadata.version("cryptography")
'41.0.7'

多くのプロジェクトでは、パッケージレベルの __version__ アトリビュートを提供することで、トップレベルの インポートパッケージ にバージョン番号を付与することも選択しています:

>>> import cryptography
>>> cryptography.__version__
'41.0.7'

このテクニックは、とりわけ、バージョン番号を問い合わせる呼び出し (pip -V など) が可能な限り素早く動作することが保証されることを望む CLI アプリケーションにおいて価値があります。

報告された配布パッケージとインポートパッケージのバージョン番号が互いに整合性のあるものとなることを保証したいと望むパッケージの公開者は、そのようにするための潜在的なアプローチとして シングルソースのバージョン番号 の議論を復習することができます。

インポートされたパッケージやモジュールはランタイムのバージョン情報をこのやり方で公開することを 要求 されてはいない ( PEP 396 で撤回された提案を見てください) ので、 __version__ アトリビュートは、それを提供するものとして知られているインターフェース (あるプロジェクトが自身のバージョン番号やその直接の依存先のバージョン番号を問い合わせるなど) だけから問い合わせを受けるか、または、問い合わせる側のコードがアトリビュート欠損の場合 [3] を取り扱えるように設計されているかのいずれかであるべきです。

プロジェクトによっては、モジュール自身のバージョン番号ではないバージョン情報を外部の API 群向けに公開する必要があるかもしれません。そのようなプロジェクトでは、ランタイムに適切な情報を取得するための、そのプロジェクト特有の方法を定義するべきです。例えば、標準ライブラリの ssl モジュールは、下敷きにしている OpenSSL ライブラリのバージョンにアクセスするための複数の方法を提供しています:

>>> ssl.OPENSSL_VERSION
'OpenSSL 3.2.2 4 Jun 2024'
>>> ssl.OPENSSL_VERSION_INFO
(3, 2, 0, 2, 0)
>>> hex(ssl.OPENSSL_VERSION_NUMBER)
'0x30200020'