はじめに
前回までで、形態素解析システムJUMANのPythonモジュールを作成してきました。
本稿では前回の教訓(lessons learned)の続きとして、pipで簡単にインストールできるようPYPIへのアップロードについてと、テストのモジュール化について書こうと思います。
PYPIへのアップロード
2017年10月9日現在、半年前にURLが変わったとか、twineが標準になったとかで、PYPIへのアップロード情報は錯綜している感じです。(ここも「PYPIへのアップロード」と書いていますが、「pypi.python.orgへのアップロード」が正確です。)
以下は、packaging.python.orgの「Guides」の情報に基づきます。しかし今後も変わることが容易に予想されますので、各位、最新情報をご参照ください。(しかし私には、python.orgのサブドメインのうち、どこに情報が反映されるのかよくわかりません。サイト構成も変わりそうな。)
Bash on Ubunts on Windowsのインストール
まず、WindowsのMSYS2にインストールしたPython3からは、twineでアップロードできませんでした。
「このシステムではシグナルが使えない」というエラーメッセージだったと思います。そこで私はもうBash on Ubuntu on Windowsとその上でPython3をインストールし、PYPIへのアップロードはそこから行いました。
それ以外のWindows用Pythonは試していません。
ここを読まれるWindows環境の方は、まずご使用の環境(MSYSも含め)でtwineを使うまでやってみて、ダメでしたらBash on Ubuntu on Windowsをお試しください。(Bash on Ubuntu on Windows の利用はWindows10の最新版のみです。)
そのインストールの仕方は、他のサイトをご参照ください。(こちらも新しいので、今後、変わりやすいと思います。)
必要なツールのインストール
参考:packaging.python.org「Packaging and Distributing Projects」
まずは必要なツール、twineとwheelをインストールします。
pip install twine pip install wheel
環境によってはpip3だったりします。(以下同様、pythonもpython3かもしれません。)
また先のGuidでは、vertualenv, venvもお勧めしていますので、もし必要でしたら。
インストールパッケージの作成
setup.pyのある開発環境のディレクトリにて、以下の入力でインストールパッケージを作成します。
python setup.py sdist python setup.py bdist_wheel
1行にてソースパッケージのtar.gzが、2行にてバイナリパッケージのwheelが作成されます。
しかし私の場合は、wheelを作るのはやめました。
私の環境でできるのはWindowsの64bitのバイナリパッケージですが、現状Windowsの64bitと32bitを区別するタグはないようですので。(Pythonのコードだけのモジュールなら作っておくべきなのでしょう。)
アカウントの登録
pypi.python.org及びtest.pypi.orgにアカウントを登録します。
それぞれ、右側にある「Register」をクリックして登録画面から登録します。
.pypircの作成
「vi ~/.pypirc
」などして、アカウントのホームに.pypircを作成します。
これはパスワードの入力を省略するためで、必須ではありません。(WindowsではどこがHOMEなのか?)
内容は以下です。
[distutils] index-servers = pypi testpypi [pypi] username: <ユーザー名> password: <パスワード> [testpypi] repository: https://test.pypi.org/legacy/ username: <ユーザー名> password: <パスワード>
[pypi]にはrepository:は必要ないようです。
test.pypi.orgへのアップロード
参考:packaging.python.org「Using TestPyPI」
いきなり本番のpypi.python.orgにアップロードする前に、テストサイトにアップロードしてpipでインストールできるか試します。
入力は以下です。
python -m twine upload --repository testpypi --skip-existing dist/<モジュール名>-<バージョン番号>*.*
--skip-existing
がないと、2度目以降に「もうあるよ」といわれてアップロードに失敗します。つけても、メッセージは「もうあるからスキップするよ」なので、効果のほどは謎。(ヘルプを読むと、上書きされるように読めるのですが…。もし怪しかったら、パッケージのバージョンを上げます。)
引数はアップロードするファイルですが、公式サイトでは「dist/*
」のように書かれています。distの下がすべて同じバージョンの同じモジュールのパッケージであればそれでよいと思いますが、私の場合は他のものもdistに突っ込んでいますので、specificにパッケージを指定しています。
また.pypircがない場合は以下の様にURLを指定します。
python -m twine upload --repository-url https://test.pypi.org/legacy/ --skip-existing dist/<モジュール名>-<バージョン番号>*.*
アップロードし終えたら、以下のようにpipでinstallを試します。
pip install --no-cache-dir --index-url https://test.pypi.org/simple/ <モジュール名> --user
--no-cache-dir
オプションでキャッシュを無視しています。
必要であれば仮想環境で行うか、--prefix
オプションで場所を指定します。
pypi.python.orgへのアップロード
テストしてOKでしたらpypi.python.orgにアップします。
入力は以下です。
python -m twine upload --repository pypi dist/<モジュール名>-<バージョン番号>*.*
引数のアップロードするファイルの指定は、テストと同様「dist/*
」で構わないかもしれません。
.pypircを作成していない場合は、「--repository pypi
」は必要ありません。
pypi.python.org内で情報が伝搬するのに少々時間がかかるようですので、インストールのテストは一呼吸おいてからです。
pip install <モジュール名>
しばらくは、pypi.python.orgのページにモジュールの情報がうまく表示されませんでした。これについては、次々章に続きます。
テストの実装
テストの実装には、標準のunittestを使いました。
参考:docs.python.jp「26.4. unittest」
setup.pyをやめる
当初は、他のブログなどに書かれていますように、setup.pyの並びに「tests」というディレクトリを作成しその中にテストケースを置き、さらにsetupの引数「test_suite」を指定してsetup.pyから実行できるようにしていました。
しかし前章のようにpipを使ったインストールでは、setup.pyは直接登場しません。しかし開発環境(Windows 64bit)でないPOSIX環境とWindows 32bitでは、インストール後のテストが必要です。
強制テストのオプションもよくわかりませんでしたので、結局パッケージに入れ、しかもモジュールとして実行できるようにしました。
テストの場所
具体的な場所はjumany(パッケージの名前)の下のtestディレクトリです。
そこの__init__.pyと__main__.pyは以下のような内容です。
__init__.py
import os from unittest import TestLoader, TextTestRunner def run(): tests = TestLoader().discover(os.path.dirname(__file__)) TextTestRunner(verbosity=2).run(tests) if __name__ == '__main__': run()
__main__.py
import jumany.test jumany.test.run()
インストール後に「python -m jumany.test
」で実行できます。
開発時はデバッガで__init__.pyを実行しますので、setup.pyは使いません。
テスト対象のimport
テストケースにて、テスト対象をimportするのに少々トリッキーなことをしています。
sys.path.append(os.path.join(os.path.dirname(__file__), "../..")) try: import jumany except ImportError: import python_module as jumany
1行でわざわざ2つ下のディレクトリ(テスト対象の一つ上)をモジュールの検索対象に加え、5行にて開発時のディレクトリ名(python_module)でモジュールをimportしています。(1行はexcept ImportErrorの後でも構いません。そしてPythonモジュールのディレクトリ名はパッケージ名と同じほうが簡単ですね。PYPIで衝突しなければ、ですが。)
シェルスクリプトに書く
「WindowsでUTF-8が使えるJUMANをビルドする」に書きましたが、私はコマンド入力が面倒なのと忘れがちなのとで、全部シェルスクリプトに書くようにしました。
こちらの最後の方「elif [ $cmd = "pymod" ]; then
」以下がPythonモジュール関係です。
もう、パッケージを作ったら即ファイルからインストールしてテスト。
test.pypi.orgからpipでインストールしたら即テスト。
pypi.python.orgからpipでインストールしたら即テスト。
PYPIに表示される情報
test.pypi.orgにアップした時から、一生懸命設定したclassifiersやlong_descriptionが表示されないなあ、と思っていました。
setupの引数author_emailを設定していないからか、とはうすうす思っていました。(メールアドレスを公開してスパムが来るのも嫌なので。)
パッケージを作るとできる「<パッケージ名>.egg-info」というディレクトリの中の「PKG-INFO」を見て、UNKNOWNとなっている引数にすべて対応することにしました。
以下、節タイトルはsetupの引数名です。
author_email
新たなメールアドレスを作って、一応難読化してソースには書きます。変換法則は簡単なのでソースを読まなくても推測できるレベルですが、クローラーは防げるかも。
…と思ったら、test.pypi.orgの方で思いっきりmailtoで表示されています。orz。
license
Windowsのビルド済みライブラリにLGPLが含まれるのでどうしたものか、と思ったのですが、「BSD+LGPL」という表現をしているパケージがあったので、それに習うことにします。
これもスクリプトでclassifiersに展開するようにします。(使うものだけ。)
platforms
このあたりに書いてあるのですが、詳細不明です。リストで指定すれば良いようですので、これも依存情報から自動生成するようにします。
結果…リストの最初の「Windows」のみ表示されています。orz。
long_description
他のsetup.pyに倣って、README.rstから読み込むようにしていたのですが、改行がCRLFだと余計な改行が入るようです。
大差ないかもしれませんが、読み込み時にCRLFをLFにreplaceするようにします。
README.rstの書き方
そもそも、reStructuredTextの書き方自体あやふやでした。(マークダウンと混じっています。)
この辺りで下書きして、ちゃんと表示されるようにします。
まとめ
そんなこんなで、pypi.python.orgにもまともに表示されるようになりました。(Platform以外は…)
ひとまず、(辞書が5年前なのは置いといて)JUMANは落ち着きました。
次はいよいよ…To Be Continued→(Windows版JUMAN++へ)