pythonのsetup.pyについてまとめる

# はじめに
Pythonのパッケージ管理や配布について。

どうやって他人からも未来の自分からも再利用しやすいパッケージが書けるのか分からなかったので調べた。結論、2018年8月時点ではsetup.pyを書くのが良さそうと判断。以下に調べたことをまとめる。Python3を対象とする。




# 参考
公式がパッケージや配布について解説したドキュメント
https://packaging.python.org/

日本語の詳しいサイト(最終更新が2016年なのでちょっと古い)
http://www.yunabe.jp/docs/python_package_management.html

setuptoolsの公式ドキュメント
https://setuptools.readthedocs.io/en/latest/setuptools.html





# この記事で作るファイルの配置
setup.py初めて作る人が参考になるように記載。
setup.py関連のファイルは全てプロジェクトのルートに配置する。

```
project-folder
+ your_great_package
|  + __init__.py
|  + awsome_module.py
+ setup.py
+ setup.cfg
```



# setup.pyを書くメリット
## 特別なツールが不要
Pythonをインストールする際に標準で付属するpipを使ってコードを他人と共有できる。つまり特別な準備が不要。

## 慣れ親しんだ方法でコードを共有できる
setup.pyを書くと

```console
pip install foopackage
```

と打ち込むだけで依存関係も含めた環境を簡単に構築できる。

当然ながらこのようにしてインストールしたパッケージはコードに

```python
from foopackage.barmodule import hogehoge
```

と記述することでどこからでも参照できる。






# setup.pyの書き方
## シンプルな記述方法

```Python:setup.py
from setuptools import setup

setup(
    name="パッケージの名前",
    version="パッケージのバージョン(例:1.0.0)",
    install_requires=["packageA", "packageB"],
    extras_require={
        "develop": ["dev-packageA", "dev-packageB"]
    },
    entry_points={
        "console_scripts": [
            "foo = package_name.module_name:func_name",
            "foo_dev = package_name.module_name:func_name [develop]"
        ],
        "gui_scripts": [
            "bar = gui_package_name.gui_module_name:gui_func_name"
        ]
    }
)
```
設定内容は全てsetup関数の引数として記述する。

install_requiresの引数として指定されたパッケージは`pip install -e .`した時に一緒にインストールされる。またextras_requiresに記述されたパッケージは`pip install -e .[develop]`等と指定することでインストールされる。entry_pointsに指定された関数は`pip install`した時に実行可能ファイルとして生成される。

　※pip installでParmittionエラーが出たら、python -m pip installを試してみること

これはこれでよいのだが、いかんせん読みにくい。特に設定項目が増えてきた場合には。また設定を編集する場合ある程度プログラミングの素養が必要になる。環境構築担当者のスキルが無い場合、設定をいじらせるのがしんどい。

この問題はsetup.pyと同じディレクトリにsetup.cfgという設定ファイルを作ることによって解決できる。

## setup.cfgを使ったsetup.pyの記述方法

setup.cfgを使うことで、setup.pyはこうなる。

```python:setup.py
from setuptools import setup

setup()
```

続いてsetup.cfgはこうなる。

```config:setup.cfg
# metadataセクションではパッケージのメタデータを定義する
# これらの値はpypiで公開した際に表示される。
# なおversion等、一部のキーはディレクティブの指定により外部ファイルから値を取得することができる
# https://setuptools.readthedocs.io/en/latest/setuptools.html#metadata
[metadata]
name = your_package
version = attr: src.VERSION
license = file: license.txt

# optionsセクションではパッケージの依存関係やpip installした時の動作を定義する
# 値が複数ある場合、インデントすることで1行に1つの値を書くことができる。
# https://setuptools.readthedocs.io/en/latest/setuptools.html#options
[options]
install_requires =
    packageA
    packageB

# optionの内、値のTypeがsectionのものは別セクションで記述する。
[options.extras_require]
develop =
    dev_packageA
    dev_packageB

[options.entry_points]
console_scripts =
    foo = package_name.module_name:func_name
    foo_dev = package_name.module_name:func_name [develop]
gui_scripts =
    bar = gui_package_name.gui_module_name:gui_func_name


```

基本的な書き方はWindowsの.iniファイルに似ている。"["と"]"でセクションを表し、key=valueという形式で値を指定する。詳細な仕様についてはsetuptoolsのドキュメントを参照。
https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files

## 依存パッケージのバージョンを指定したい場合
setup.pyおよびsetup.cfgでは依存するパッケージのバージョンを指定することはできない。そこでconstraints.txtを作成し、そこにパッケージのバージョンを書き込む必要がある。

constraints.txtの作り方

```console
pip freeze > constraints.txt
```

constraints.txtの使い方

```console
pip install -e . -c constraints.txt
```

pipのバージョンによっては上記のコマンドでエラーが出る。

```console
C:\prj\myprj > pip install -e .[develop] -c constraints.txt
Could not satisfy constraints for 'myprj': installation from path or url cannot be constrained to a version
```

これはpipのバグのようなのでその内修正されるはず。

エラー文の内容はmyprjというパッケージがPYPIに登録されていないことに起因する。従ってmyprjパッケージをPYPIにアップするか、constraints.txtからmyprjの行を削除すればよい。



# setup.pyに関連するパッケージの説明とリファレンス
以下の記述は豆。

## setuptools
https://setuptools.readthedocs.io/en/latest/
今回のまとめの中心となるパッケージ。Python標準ではないが、pipを導入する際に標準でインストールされる。
公式ドキュメントにはsetuptoolsについて以下の通り記述されている。便利そうだし公式がわざわざ言及しているのだから、おそらくこれが標準に最も近いと思われる。
> Most Python users will not want to use this module directly, but instead use the cross-version tools maintained by the Python Packaging Authority. In particular, setuptools is an enhanced alternative to distutils that provides:
>
> - support for declaring project dependencies
> - additional mechanisms for configuring which files to include in source releases (including plugins for integration with version control systems)
> - the ability to declare project “entry points”, which can be used as the basis for application plugin systems
> - the ability to automatically generate Windows command line executables at installation time rather than needing to prebuild them
> - consistent behaviour across all supported Python versions
>
> Python公式より引用 - https://docs.python.org/3/library/distutils.html

(以下意訳。英語が得意な方、添削してくださるとありがたいです)
一般的なPythonユーザーはdistutilsモジュールを直接使わない方がよいでしょう。代わりにPyPA(Pythonの主要なパッケージをメンテナンスしているグループ)でメンテナンスしているツールを使うべきです。これらのツールの動作はバージョン非依存になるようにメンテナンスされています。setuptoolsはdistutilsの代替として特に広く使用されており、以下の機能を提供しています。

 - プロジェクトの依存関係を定義
 - リリース毎に含めるファイルを構成するための追加機能(バージョン管理システム統合用プラグインを含む)
 - プロジェクトのエントリーポイントを定義。これによりプラグイン構築のための基盤が提供される。
 - パッケージインストール時にWindowsコマンドラインで動作する実行可能ファイルを自動生成する機能。これによりパッケージインストール後にビルドする手間が省けます。
 - Pythonがサポートしている全バージョンにおける一貫した動作の保証

## distutils
https://docs.python.org/3/library/distutils.html
python標準パッケージ。名前の通りコードを配布するために必要な機能が提供される。ただしユーザーがこのパッケージを直接参照することはほぼない。(公式ドキュメントにも「一般ユーザーは使うな」と書かれている)
上述したsetuptoolsはこのパッケージの機能を拡張したものである。

## wheel
https://pythonwheels.com/
Pythonのパッケージ形式のこと。またファイルをwheel形式にパッケージングするためのツール名でもある。wheel形式は[PEP376](https://www.python.org/dev/peps/pep-0376/)に定義されている。