パッケージのバージョンを1箇所で管理する#
プロジェクトのバージョン番号をたったひとつの真実の場所で管理するテクニックはたくさん存在しています:
setup.py
に書かれたファイルを読み込んでバージョンを得ましょう。( pip setup.py での) 例はこちら:import codecs import os.path def read(rel_path): here = os.path.abspath(os.path.dirname(__file__)) with codecs.open(os.path.join(here, rel_path), 'r') as fp: return fp.read() def get_version(rel_path): for line in read(rel_path).splitlines(): if line.startswith('__version__'): delim = '"' if '"' in line else "'" return line.split(delim)[1] else: raise RuntimeError("Unable to find version string.") setup( ... version=get_version("package/__init__.py") ... )
注釈
setuptools 46.4.0 のリリースでは、代わりに次のものを file:
setup.cfg
ファイルに置くことで同じことを達成できています ("package" をパッケージをインポートする際の名前で置き換えてください) :[metadata] version = attr: package.__version__
setuptools 61.0.0 のリリースの時点では、プロジェクトの
pyproject.toml
ファイルの中にバージョンを動的に指定することができます。[project] name = "package" dynamic = ["version"] [tool.setuptools.dynamic] version = {attr = "package.__version__"}
attr:
ディレクティブを含む装飾的な設定指示子が、setup.py
向けのパラメータとしてはサポートされていないことに注意してください。外部のビルドツールを使う場合は、両方の場所(ロケーション)を更新できるようなもの、あるいは、両方のサイトから使える API を提供しているものを使いましょう。
使えるかもしれない外部のビルドツールのリスト、ただし、順不同で、ここに挙げられていなくても使えるものがあるかもしれません: bump2version, changes, commitizen, zest.releaser.
プロジェクト内にグローバル変数の
__version__
に値を設定した専用のモジュール (例えばversion.py
) を作ってsetup.py
からそれを読み取ってexec
で値を変数に取り込むと良いでしょう。version = {} with open("...sample/version.py") as fp: exec(fp.read(), version) # later on we use: version['__version__']
このテクニックを使っている例: warehouse 。
単純に
VERSION
という名前のテキストファイルに値を書いておいて、setup.py
とプロジェクトのソースコードの両方から読み込みます。with open(os.path.join(mypackage_root_dir, 'VERSION')) as version_file: version = version_file.read().strip()
このテクニックを使う利点は、Python に限定されたやり方ではないということです。どんなツールでもバージョン番号を読み取ることができます。
警告
このやり方を採用するなら、
VERSION
ファイルがすべてのソースコードとバイナリの配布物に含まれているように気を付けてください (例えばMANIFEST.in
にinclude VERSION
を追加しておくなど)。setup.py
に値を保存して、プロジェクトのソースコードがimportlib.metadata
API を使ってその値を動作中に取得するようにしましょう。 (importlib.metadata
は Python 3.8 で導入されていて、それより古いバージョンではimportlib-metadata
プロジェクトとして利用可能になっています) インストール済みのプロジェクトのバージョン番号をこの API で取り込むには次のようにします:import sys if sys.version_info >= (3, 8): from importlib import metadata else: import importlib_metadata as metadata assert metadata.version('pip') == '1.2.0'
importlib.metadata
API が知っているのはインストールされたパッケージのメタデータだけであって、現在インポートされているソースコードについて知っているとは限らないことに注意してください。あるプロジェクトで動作中にバージョン番号を取得する方法を使っているのであれば、Python 3.8 よりも古いバージョンを使う場合はそのプロジェクトの
install_requires
にimportlib-metadata
を書いておかなければなりません:setup( ... install_requires=[ ... 'importlib-metadata >= 1.0 ; python_version < "3.8"', ... ], ... )
importlib.metadata
に対するもっと古い (かつ、より非効率な) 代替策は、setuptools
が提供するpkg_resources
API です:import pkg_resources assert pkg_resources.get_distribution('pip').version == '1.2.0'
あるプロジェクトで自分自身のバージョン番号を動作中に取得するために
pkg_resources
を使っているなら、プロジェクトのinstall_requires
のリストには必ずsetuptools
が入っていないといけません。このテクニックを使っている例: setuptools.
sample/__init__.py
で__version__
に値を設定して、setup.py
からsample
をインポートしましょう。import sample setup( ... version=sample.__version__ ... )
警告
このテクニックはよく知られたものだが、
install_requires
で定義された依存先パッケージをsample/__init__.py
がインポートしている場合には、そのようなパッケージはsetup.py
が実行される時点ではまだインストールされていない可能性が高いので、失敗するであろうということに注意してください。ソースコード内ではなくバージョンコントロールシステム (Git, Mercurialなど) のタグの中にバージョン番号を保持して、そこから setuptools_scm を使って取り出しましょう。