Pythonで作成したライブラリを、PyPIに公開/アップロードする

便利なツールを作成したら、ぜひみんなにも使ってもらいたいものです。Pythonでは[PyPI](https://pypi.python.org/pypi)というパッケージを登録するサイトがあり、ここに登録することで`pip install`などでインストールすることができるようになります。

本文書では、その手順を紹介したいと思います。

# 注意: 古い方法について

・・・と思ってGoogleで検索すると、現在では推奨されない古い方法がたくさん引っかかります。以下は古い方法の代表例なので、注意をしてください。

* `setup.py`で`distutils`を使っている
  * 現在は`setuptools`を使うのが推奨です
* `.pypirc`に`distutils`の記載がある
  * 現在は必要ありません
* `python setup.py register`や`python setup.py sdist upload`を使っている
  * これらは[平文のHTTP/認証されていないHTTPSの通信を利用するため、利用が非推奨となっています](https://packaging.python.org/distributing/#uploading-your-project-to-pypi)(PyPIにログインするユーザー名/パスワードを盗まれる危険性がある)。
  * 現在は[`twine`](https://packaging.python.org/distributing/)というパッケージ公開用ツールの使用が推奨されているので、こちらを利用しましょう。

# 事前準備

PyPIにパッケージを公開する前に、以下の事前準備が必要です。

* PyPIへのアカウント登録
* アカウント認証情報を記載した`.pypirc`の配置(オプション)

## PyPIへのアカウント登録

以下から、PyPIのアカウントを作成しましょう。

[PyPI Manual user registration](https://pypi.python.org/pypi?%3Aaction=register_form)

## アカウント認証情報を記載した`.pypirc`の配置

アップロードするたびにユーザー名やパスワードを入力するのがめんどくさい、という場合はホームディレクトリの直下に`.pypirc`を作成し、そこに認証情報を記載しておくことができます。

```ini
[pypi]
username = <username>
password = <password>
```

ただ、当然このファイルは暗号化されているわけではなく、中身が平文で晒されることになるので注意してください。

# PyPIへの登録

では、PyPIへの登録を行なっていきます。解説のため、以下のような構成となっているライブラリを例にとります。

```
YOUR-PROJECT-FOLDER
├── CHANGES.txt (OPTIONAL)
├── LICENSE.txt
├── README
├── setup.py
└── your_package (FOLDER)
    ├── __init__.py
    ├── ...
    └── subpackage (FOLDER)
        ├── __init__.py
```

## `setup.py`の用意

この構成の中で重要な役割を担うのが、ライブラリのパッケージ化処理を記述する`setup.py`です。中身は以下のような形になります。

```setup.py
from setuptools import setup


requires = ["requests>=2.14.2"]


setup(
    name='your_package',
    version='0.1',
    description='Awesome library',
    url='https://github.com/whatever/whatever',
    author='yourname',
    author_email='your@address.com',
    license='MIT',
    keywords='sample setuptools development',
    packages=[
        "your_package",
        "your_package.subpackage",
    ],
    install_requires=requires,
    classifiers=[
        'Programming Language :: Python :: 3.6',
    ],
)
```

* `name`: パッケージの名前です。実際のパッケージフォルダ名と合わせましょう
* `version`: パッケージのバージョンです。アップデートするときは、ここを変更する必要があります。
* `packages`: インストール対象となるパッケージを指定します。サブパッケージがある場合は、それを指定する必要がある点に注意してください。このパッケージは全部なんだよ！という場合は[`find_packages`を使うと良いです](https://github.com/pypa/sampleproject/blob/master/setup.py#L71)。
* `install_requires`: パッケージが依存するライブラリがあれば記載します。記法は`pip freeze`で得られる形式と同一です
* `classifiers`/`keywords`: パッケージを検索するときのタグやキーワードを記載できます。`classifiers`の一覧は[こちら](https://pypi.python.org/pypi?%3Aaction=list_classifiers)です

なお、`download_url`にGitHubのアーカイブリンクを載せる例が多数ありますが、GitHub側からダウンロードしてほしいという要望がない限り不要です(そもそも、uploadするなら不要なはず)。多くのライブラリの`setup.py`にも`download_url`の記載はありません。

実際にはもっといろいろなことを行うことができますが、基本的には上記が満たされていれば大丈夫です。詳細は公式のサンプルプロジェクトがあるため、そちらを参照すると良いと思います。

[pypa/sampleproject](https://github.com/pypa/sampleproject)

また、有名ライブラリの`setup.py`を見てみるのも良いでしょう。
この`setup.py`があれば、ライブラリのパッケージ化が行えます。

## ライブラリのパッケージング

`setup.py`が作成できたら、以下のコマンドでパッケージ化を行うことができます。

```
python setup.py sdist
```

`sdist`とは"source distribution”の意味で、その名の通りソースコードベースの配布になります。そのため`pip install`を行う際にはユーザー側で`setup.py`の記載に基づいたビルド処理が実行されます。

このビルド処理がOSによって異なる、Python2と3で・・・とかいろいろ問題をはらむ場合、ビルド済みのパッケージを配布することも可能です。それに利用されるのが、[wheel](http://pythonwheels.com/)になります。このwheelの登場により、Windowsでエラーが出たみたいなことがだいぶなくなりました。

wheelを利用するには、まずwheelをインストールします。

```
pip install wheel
```

wheelをインストールすると`bdist_wheel`のオプションが使えるようになるので、それでパッケージングを行います。

```
python setup.py bdist_wheel
```

wheelの使い方も深いためここでは深く触れませんが、多様なプラットフォームで安全なインストールを提供したい場合は、wheel形式を検討すると良いと思います。

## ライブラリのアップロード

では、いよいよアップロードを行います。まず、事前にアップロードに必要な`twine`をインストールしておきます。

```
pip install twine
```

あとは、パッケージングしたフォルダをアップロードするだけです。

```
twine upload dist/*
```

# 参考

* [Tom Christie/Uploading to PyPI](https://tom-christie.github.io/articles/pypi/)
* [Python Packaging User Guide/Uploading your Project to PyPI](https://packaging.python.org/distributing/#uploading-your-project-to-pypi)
* [Python Packaging User Guide/Packaging and Distributing Projects](https://packaging.python.org/distributing/)