Python パッケージを作成＆PyPIで公開する方法 - 2019年版

この記事では、Pythonのパッケージの作り方・PyPIで公開する方法を説明します。

もちろん、すでに同様の記事はあるのですが、情報が古かったり（後述）、一部のツール（TwineやPipenv）が紹介されていない記事が多いようなので、「2019年版」として最新と思われる方法を説明します。

# 使用するツール

以下のツールを使います。なお、この記事では、シンプルな（説明が楽な）ケースのみ説明しているので、詳細な使い方は各ツールのドキュメントを参照してください。

**[Setuptools](https://setuptools.readthedocs.io/en/latest/)** 
パッケージの内容を定義するための標準ライブラリ。`setup.py`や`setup.cfg`などの設定ファイルはsetuptoolsが使う

**[Pipenv](https://pipenv-ja.readthedocs.io/ja/translate-ja/)** 
開発中に、仮想環境を作ったり、依存パッケージを管理したり、Pythonのバージョンを切り替えるためのツール。サードパーティだがデファクトスタンダード。

**[pytest](https://docs.pytest.org/en/latest/)**
サードパーティのテストライブラリ＆テストランナー。

**[Twine](https://twine.readthedocs.io/en/latest/)**
PyPIにパッケージをアップロードするためのサードパーティのツール。Setuptools自体にもアップロード機能があるが、Twineの方が使いやすいし、推奨されている。

# 事前準備

## pipenv のインストール（pyenvを使っていない場合）

pipenv をインストールします。OSのPythonをそのまま開発に使う場合は、そのPythonにインストールすればOKです。

```python
$ /usr/local/bin/pip3 install pipenv # システムのPythonにpipenvをインストール
```

## pipenv のインストール（pyenvを使っている場合）

pyenv を使っていてPC内に複数のPythonインタープリタがある場合も、**大抵の場合は pipenv はシステムのPythonにインストールすれば良い** でしょう。

pipenv自体は、単にライブラリをDLしたり環境変数をセットしたりツールなので、使用するPythonとは独立して動作するからです（python 3.6にインストールした pipenv から python 3.7を実行したりできる）。

バージョンがすごく古いなどシステムのPythonを使えない場合は、 pyenv で最新のPythonをビルドして pipenv をインストールしてください。

ここで、インストールする際は `--user`を付け、`~/.local` にインストールするようにしてください。普通にインストールすると pyenv の管理対象になる（CLIでpipenvを実行するとき`~/.pyenv/shims/pipenv`が参照される）ので、`pyenv local`などでPythonを切り替えるとpipenvを使えなってしまいます。

```python
$ .pyenv/versions/3.7.1/bin/pip install --user pipenv
$ export PATH=$PATH:~/.local/
$ pipenv # ~/.local/bin/pipenv を使用する
```

# ソースコード・設定ファイルの作成

シンプルなパッケージなら、以下のようなファイル構成になります。

```python
.
├── LICENSE       # ライセンス
├── Pipfile       # pipenvのパッケージ管理ファイル（開発用）
├── Pipfile.lock  # pipenvのパッケージ管理ファイル（開発用）その2
├── README.md     # README
├── setup.cfg     # パッケージ名・バージョン情報などの設定ファイル
├── setup.py      # お決まりのファイル
├── src
│   └── foo # Pythonのソースコード
│       ├── __init__.py
│       └── hoge.py
└── test # テスト
    └── hoge_test.py
```


## `src/` （Pythonのソースコード）

何はともあれPythonのコードを書いてください。

```python
# src/foo/hoge.py
def greet():
    return 'hello'
```

```python
# src/foo/__init__.py
# __init__.py は空でよい
```

なお、ルート直下に`foo/`を置いてもよい（そうしているプロジェクトも多い）のですが、`src/foo/`とすることをお勧めします。直下に `foo/` を置くと pipenvでインストール（後述）しなくても import できてしまい、混乱が起きるケースがあるためです。

## README.md, LICENSE

書いてください。この段階では空でも問題ありません（リリース時までに書けばよい）。

## `setup.py`

`setup.py` は以下の内容にします。

```python
from setuptools import setup
setup()
```

昔はパッケージ名やバージョンなどは `setup.py` に書いていましたが、現在では `setup.cfg` に書くのをお勧めします。特殊な処理をしない限り `setup.py` に手を入れる必要はありません。

## `setup.cfg`

`setup.cfg` に、パッケージ名、バージョン番号、依存ライブラリなどを書きます。

```ini
[metadata]
name = foo
version = 0.0.1
description = Packaging sample project
long_description = file: README.md
long_description_content_type = text/markdown
license = BSD 3-Clause License # ライセンス名（省略可）
license_file = LICENSE
url = https://url/to/project/

[options]
package_dir=
    =src
packages=find:
install_requires= # パッケージ自身の依存ライブラリ
        psycopg2~=2.7.6
        cx_Oracle==5.3
        sqlalchemy~=1.2.0

[options.packages.find]
where=src
```

なお、「依存ライブラリ」には、実行時に必要なものと、開発時にのみ必要なものの2種類ありますが、

- パッケージの機能として必要なライブラリ → `setup.cfg`
- 開発やテスト時のみ必要なライブラリ → `Pipfile`（後述）

と考えればOKです。

## `Pipfile` と `Pipfile.lock`: pipenv のパッケージ管理ファイル（開発用）

以下のコマンドを実行すると、`Pipfile`, `Pipfile.lock` が作成された上で、開発用の仮想環境が作られます。

```shell
$ pipenv --python=3.7.1 install --dev -e .
```

開発用のライブラリを追加したい時は以下のコマンドを実行します。

```shell
$ pipenv install --dev pytest
```

## テストの実行

`test/` にテストコードを書きます。

```python
import foo.hoge

def test_bar():
    assert foo.hoge.greet() == 'hello'
```

Pythonのテストツールは複数ありますが pytest がメジャーなようです。pytest を実行すると、`test/` 中の、名前に`test`が付くファイルの、`test`で始まる関数を実行してくれます。

```shell
$ pipenv install --dev pytest
$ pipenv run pytest
============================================================ test session starts ============================================================
platform darwin -- Python 3.7.1, pytest-4.4.0, py-1.8.0, pluggy-0.9.0
rootdir: /Users/k-omoto/Documents/foo
collected 1 item

test/hoge_test.py .                                                                                                                   [100%]

========================================================= 1 passed in 0.05 seconds ==========================================================
```

## Twine: PyPI へのアップロード

さて、コードを全て書き、テストも完了したとしましょう。いよいよPyPIへのアップロードです（PyPI以外にアップロードする方法は後述）。

まず、PyPIにアカウントを持っていない場合は、[アカウントを作成](https://pypi.org/account/register/)します。

twine をインストールします。

```shell
$ pipenv install --dev twine
```

PyPIにアップロードするアーカイブファイルを生成します。プレーンなPythonパッケージなら `python setup.py sdist` で良いでしょう。

```shell
$ python setup.py sdist --formats=zip
$ ls dist/
foo-0.0.1.zip
```

生成したアーカイブファイルを`twine upload`に指定して実行するとPyPIにアップロードできます。必要に応じてユーザー名やパスワードを入力してください。

```shell
$ twine upload dist/foo-0.0.1.zip
```

## PyPI以外へのアップロード

社内用のライブラリなど、PyPIではなく別のサーバーに公開したい場合は、環境変数 TWINE_REPOSITORY を追加してください。

```shell
$ export TWINE_REPOSITORY=社内レポジトリのURL
$ twine upload dist/foo-0.0.1.zip
```

# 補足: setup.cfg の entry_points

`[entry_points]` を書くと、パッケージのインストール時にコマンドを自動生成できます。これを使えば、ライブラリだけでなくCLIツールもPyPI経由で公開できます。

以下のように書くと、`foo.cli.main()` という関数を実行する、`foo` という実行可能なファイル（スクリプト）が生成されます。

```python
[entry_points]
console_scripts =
  foo = foo.cli:main
```

# 補足： 古いツール

ググると以下のような、ツール・コマンドがヒットします。これらは古い方法なので、新しいプロジェクトでは使わないようにしましょう[^1]。

[^1]: なお、これらの古い方法は今でも使うこと**は**でき、パスワードをHTTPで送っている問題などを除けば、焦って書き換える必要はありません。


- setup.py にパッケージ名やバージョン番号が書かれている
  - → setup.cfg に書きましょう
- distutils
  - パッケージング用の標準ライブラリ
  - setuptoolsに取って代わられました
- `python setup.py register` `python setup.py upload` 
  - setuptools組み込みのアップロード機能です
  - パスワードをHTTPで送っている問題がありました。Twineを使いましょう
- nose
  - テストライブラリ・ツールです
  - pytestの方が人気があるようです。
- requirements.txt
  - 依存ライブラリを pip で管理するためのファイルです
  - pipenv と `Pipfile`/`Pipfile.lock` に取って代わられました

# 補足: なぜ Pipenvか？

Pipenvに疑問がある人も世の中にはいるようですが、今の所 Pipenv の方がメジャーそうだし、Pipenv を使って大いに困るということはなさそうなので、この記事では Pipenvを使っています。

また、Pipenv周りで混乱することがあったら、以下の記事が役立つかもしれません。

[Pythonのパッケージ周りのベストプラクティスを理解する](https://www.m3tech.blog/entry/python-packaging)

# 書かなかったこと（あとで追記するかも？）

- Cython：拡張ライブラリを実装する
- Sphinx：APIドキュメントを生成する