続々はじめてのPythonモジュール:AI的言語処理その5

はじめに

前回までで、形態素解析システム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→

コメントを残す