Pythonでwheelパッケージとその中のコマンドのバージョンを合わせる方法

タイトルが長いな。

コマンドを wheel パッケージにしたときに、そのコマンドとパッケージのバージョンを合わせたい、という当然の欲求を満足するための、(現在のところ)最良と思われる方法を見つけたのでメモしておく。

コマンドでバージョンを出力するには、コマンドのスクリプト中でバージョンがわからなければならないし、パッケージングするためには setup.py の中でバージョンがわからなければならない。当然のことながら、両方にバージョンを書くのは愚の骨頂。だからどこか1ヵ所でバージョンを定義して、それをコマンドと setup.py から参照することにする。

で、それはどこなのかというと、モジュールの __init__.py の中だ。この定義をコマンドと setup.py の両方から参照するんだけど、ちょっと工夫が必要だった。

ここでは、簡単な Hello world コマンドを例に説明する。ファイル構成はこんな感じ。

^o^ > tree /f .
フォルダー パスの一覧
ボリューム シリアル番号は 666A-93A9 です
C:\USERS\TAKATOH\DOCUMENTS\SANDBOX\HELLOPY
│ setup.py

└─hello
hello.py
__init__.py

hello ディレクトリがモジュールになっていて、コマンドの実体(関数)は hello/hello.py ファイルに書いてある。

バージョンの定義は、次の通り、hello/__init__.py の中。

__version__ = '0.3.2'

バージョン番号はいろいろ試行錯誤した結果を反映している。

これを setup.py から参照するのは簡単。モジュールをインポートして変数を参照するだけ。

import setuptools
import hello


setuptools.setup(
    name='hellopy',
    description='hello app.',
    version=hello.__version__,
    author='takatoh',
    author_email='takatoh.m@gmail.com',
    packages=setuptools.find_packages(),
    classifiers=[
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
    ],
    entry_points={
        'console_scripts': [
            'hellopy=hello.hello:main',
        ],
    }
)

コマンドから参照するにはもうちょっと工夫が必要。こうなった。

import os
import argparse


here = os.path.abspath(os.path.dirname(__file__))
about = {}
with open(os.path.join(here, '__init__.py')) as f:
    exec(f.read(), about)
VERSION = 'v' + about['__version__']


def main():
    parser = argparse.ArgumentParser(description='Hello tool.')
    parser.add_argument('name', metavar='NAME', type=str, nargs='?', default='world')
    parser.add_argument('-v', '--version', action='version', version=VERSION)
    args = parser.parse_args()

    print('Hello, {name}!'.format(name=args.name))


if __name__ == '__main__':
    main()

hello/__init__.py を読み込んで exec で評価して変数への参照を得ている。直接 hello モジュールをインポートできれば楽なんだけど、このコマンド(スクリプト)自体が hello モジュールに属しているので、それはできないみたいだ。

というわけで、この方法が一番いいようだ。もっといい方法が見つかるまではこれで行こうと思う。

最後に、実際に wheel パッケージを作って、インストールして試してみよう。

^o^ > python setup.py bdist_wheel
^o^ > pip install dist/hellopy-0.3.2-py3-none-any.whl
^o^ > hellopy --version
v0.3.2

^o^ > hellopy
Hello, world!

^o^ > hellopy Andy
Hello, Andy!

うまくいってる。