PEP 440 (Python のバージョン・スキーマ) 概説
Overview
自作 Python モジュールに 0.4-SNAPSHOT
というバージョンを付けたら poetry
に怒られたので調べたところ、いわゆる semver (Semantic Versioning) とはやや形式が違うことが判明した。これに関する日本語の文献が見当たらなかったので、知見を共有したい。
なお、「正規形」のセクションでは標準の形式についてのみ説明し、「書き方のバリエーションと正規化」のセクションで他の書き方を紹介する。
参考文献
- PEP 440 -- Version Identification and Dependency Specification. https://www.python.org/dev/peps/pep-0440/
- Semantic Versioning 2.0.0. https://semver.org/
- Version Handling, document of
packaging
. https://packaging.pypa.io/en/latest/version.html
Summary
- いわゆる semver とやや違うので注意。Python のほうが形式に関する制約が強い。
- 正規形は
[N!]N(.N)*[{a|b|rc}N][.postN][.devN]
(N
は非負整数) - 「同じバージョン」を指す場合にも書き方にはある程度自由度がある。その中には正規形というものが存在する。
- たとえばパッケージの依存関係の解決など、バージョン間の順序を考える必要がある場合には、正規形に変換してから考える。
正規形 (Normal Form)
基本的には public version identifier として表現されるが、内部的な使用においては local version label を +
でつないで後置してもいい。
これを非形式的に書くと、
<public version identifier>[+<local version label>]
例
Local 部を含まない例は 1.0.0
などで、含む例は 1.0.0+2021.06.12
など。
Public version identifier
非形式的に書くと、正規形は以下の通り:
[N!]N(.N)*[{a|b|rc}N][.postN][.devN]
ここで、!
や .
や a
や .post
などはリテラル文字列。N
は 0 以上の整数 (0
以外で、先頭に0が来てはいけない) であって、省略されている場合は 0
であるとみなす。
バージョン番号は、5つのセグメントに分かれている:
- Epoch segment:
N!
- Release segment:
N(.N)*
- Pre-release segment:
{a|b|rc}N
- Post-release segment:
.postN
- Development release segment:
.devN
少し解説:
- PEP 440 は semantic なバージョン付け (メジャー、マイナー、パッチバージョンみたいなやつ) を強制していない。リリース年月でバージョン付けしてもよい。
- 見てわかるように、リリース・セグメントの要素数は1以上としか決まっていない。よく使われるのは2要素 (
3.1
) と 3要素 (2.7.1
) の場合である。 - 見慣れないのはエポック・セグメントであると思われる。
Local version label
ビルド時間やリビジョン番号など開発中に内部で使われることを想定している。PyPI などの外部サーバに公開されるもののバージョンは public version identifier 部のみ使うこと。
これに許されるのは [a-zA-Z]
と .
で構成される文字列で、先頭と末尾に .
が来ないもの。
例 | OK? |
---|---|
2021.06 |
OK |
3..b |
OK |
.23 |
NG |
2a35. |
NG |
各セグメントについて解説
エポック・セグメント
最上位セグメント。非負整数を与える。
これは、途中でバージョン付けの法則が大きく変わった場合に使用する。
たとえば、2020年まではリリース年月をバージョンに使用していたが、2021年からは semver に移行した場合に、移行後のバージョンはエポックを 1
とすることによって、バージョンを正しく並べられるようになる。
バージョン | 省略値を補ったもの | エポック | リリース |
---|---|---|---|
2020.11 | 0!2020.11 | 0 | 2020.11 |
2020.12 | 0!2020.12 | 0 | 2020.12 |
1!1.0 | 1!1.0 | 1 | 1.0 |
1!1.1 | 1!1.1 | 1 | 1.1 |
なお、私は実際にエポックが使われているのを見たことがない。
リリース・セグメント
バージョン番号の本体の部分である。数字をドットで区切って並べる。
例 | OK? |
---|---|
1 |
OK |
1.0 |
OK |
1.2.3 |
OK |
1..3 |
NG |
バージョンの比較のところで説明するように、1.2
と 1.2.0
は等価、つまり同じバージョンを指す、とみなす。
プレリリース・セグメント
ファイナルバージョン (リリース・セグメントのみ持つバージョン) の前のバージョンを指すセグメントである。
a
は alpha、b
は beta、rc
は release candidate を意味する。
順序としては a < b < rc
という感じ。
バージョンの順序としては 1.2.0a3 < 1.2.0b1 < 1.2.0rc0 < 1.2.0
のようになる。
ポストリリース・セグメント
ファイナルバージョンの直後のバージョンを指すセグメントである。.postN
の形式で与える。
想定する使用例は、リリースのドキュメントの一部を修正した場合のような、プログラムの動作を変更しない微修正を行う場合である。バグを修正した場合に使用するのは強く非推奨であり、そういう場合はリリース・セグメントを長くするなりして適切に bump してほしいとのこと。例えば 1.2.0.post1
よりは (メジャー、マイナー、パッチ、メンテナンス) のようなバージョン形式を採用し 1.2.0.1
とするのがいい。
順序は 1.2.0 < 1.2.0.post0 < 1.2.0.post1 < 1.2.1a1
のような感じ。
1.2.0 < 1.2.0.post0
であると理解しているが、これが誤解ならば指摘してほしい。
また、プレリリースとポストリリースを組み合わせることができるが、これは非推奨である。単にプレリリース番号を bump するのがいい。つまり、1.2.0a1.post1
とするより 1.2.0a2
とするのがいい。
開発リリース・セグメント
安定版リリースではないけれど開発のために定期的にリリースするような、いわゆる開発リリースのためのセグメントである。.devN
の形式で与える。
これはプレリリースよりも前のバージョンとして扱われる。よって、1.2.0 < 1.2.0.post1 < 1.2.1.dev0 < 1.2.1a0
のような順序が成り立つ。
また、プレリリースおよびポストリリースと、開発リリースを組み合わせることも許容されているが、
外部サーバなどに登録するバージョンとして使用するのは強く非推奨である。
これを使用するときの順序は 1.2.0.a1.dev0 < 1.2.0.a1 < 1.2.0 < 1.2.0.post0.dev0 < 1.2.0.post0
のようになる。見ての通りわかりにくいので、非推奨となる理由はわかる。
書き方のバリエーションと正規化 (Normalization)
to be written.
ざっくりいうとドットの代わりに -
や _
が使える。正規形は
- こういうのを全部
.
に揃え、 - 数字が省略されている場合は
0
を挿入し、 -
090
を90
にするなど、数字を正規化し、 -
alpha
などをa
に、c
やpre
などをrc
に書き換え、 -
rev
やr
などをpost
に書き換え、 - アルファベットをすべて小文字にして、
-
v1.0
を1.0
にするなど、先頭のv
を削除して、 - 前後の空白文字を削除する、
などの変換を行う。(全部書いた気がする)
バージョンの比較・順序付け
2つのバージョンを比較する際には、それぞれを正規形にした上で、上位の (より左の) セグメントから比較していく。
- 省略されている数字は
0
で補う。例えばエポックが存在しない1.2.0
は、0!1.2.0
とみなす。 - リリース・セグメントを比較する際には両者を同じ長さに揃える。例えば、
1.2
と1.2.1
を比較する際には前者を1.2.0
とみなした上で比較を行う。
詳しくは to be written.
正当なバージョン番号を表す正規表現が知りたい
上記 PEP の文書の下部に書いてある。
より実装に近いのは、次に紹介する packaging
モジュールの判定部分のソースコードに書かれているものである。ただし現時点では、PEP と同じものが書かれているように見える。
ある文字列が正当なバージョンかどうか判定したい
バージョン文字列が PEP 440 準拠であるかの判定をする、および正規形を取得するには、packaging
パッケージの parse()
を呼ぶか、Version
クラスのインスタンスを作ればいい。詳しくは参考文献に載せたサイトを見てほしい。バージョン間の比較についてもありそうな気がしているが一見では見つけられなかった。
Discussion