はじめに
開発部の tasaki です。Python 3.7 のリリースが今月末に行われるということで、あらためて 2018 年現在の Python のパッケージ構成におけるベストプラクティスについて検討してみたいと思います。
対象読者
この記事は、
- 書き捨ての Python スクリプトなら書けるが、ちゃんとしたパッケージの作り方がよく分からない
- 公式リファレンスのモジュールの章を読んだが、結局具体的にどういう構成にすればよいのか分からない
setuptools.setup
関数の大量の引数のどれを使えばよいのか分からない
というような人を対象としています。
対象バージョン
処理系とツールチェーンのバージョンは、
- Python 3.4 (2014/03/16 リリース)以降
- pip 8.1.2 以降
- setuptools 19.2 以降
を対象とします。 EPEL の python34, python34-pip, python34-setuptools がこのバージョンであり、これらが使われている環境はかなり多いと考えられます 1 。
結論 (TL;DR)
Flask のパッケージ構成がわかりやすくソースコードの規模感も丁度よいため、できるだけこの構成を踏襲するとよいでしょう。
今回使ったソースコードは GitHub に置いてあります。
テスト可能な最小構成を作る
Flask を参考に最小構成の、ただし pytest と tox によるテストができるパッケージを作ってみましょう。 パッケージ名は python_boilerplate とし、最低限以下のようなことができるものとします。
pip install .
でパッケージをインストールできる- インストール後、他パッケージから
import python_boilerplate
できる - インストール後、
python_boilerplate
コマンドでアプリケーションを実行できる
- インストール後、他パッケージから
pip install -e '.[dev]'
で開発者向けの依存パッケージも含めてインストールできるpytest
コマンドでテストが走るtox
コマンドで複数バージョンの Python 処理系でテストが走り、カバレッジレポートが出力される
python setup.py bdist_wheel
で wheel パッケージ(ビルド済み配布物)を作れる- この wheel パッケージは
pip install python_boilerplate-x.y.z-py3-none-any.whl
でインストールできる
- この wheel パッケージは
ファイル構成
ディレクトリとファイルの配置は以下のようになります。
. ├── setup.py ├── MANIFEST.in ├── python_boilerplate │ ├── __init__.py │ ├── __main__.py │ ├── cli.py │ └── core.py ├── tests │ └── test_core.py ├── setup.cfg ├── tox.ini ├── README ├── docs │ └── ... └── examples └── ...
ファイルの内容
setup.py
setuptools.setup
関数を呼び出すメインの設定スクリプトです。setup.py は python setup.py <サブコマンド>
の形で使われたり pip 経由で実行されたりします。
import ast import re import os from setuptools import setup PACKAGE_NAME = 'python_boilerplate' with open(os.path.join(PACKAGE_NAME, '__init__.py')) as f: match = re.search(r'__version__\s+=\s+(.*)', f.read()) version = str(ast.literal_eval(match.group(1))) setup( # metadata name=PACKAGE_NAME, version=version, # options packages=[PACKAGE_NAME], include_package_data=True, zip_safe=False, python_requires='>=3.4', install_requires=[], extras_require={ 'dev': [ 'pytest>=3', 'coverage', 'tox', ], }, entry_points=''' [console_scripts] {app}={pkg}.cli:main '''.format(app=PACKAGE_NAME.replace('_', '-'), pkg=PACKAGE_NAME), )
setup
関数には他にも沢山の引数がありますが最低限これだけあればよさそうです 2 。PyPI にパッケージを公開する場合はより多くのメタ情報(Metadata)を加えた方がよいでしょう。
バージョンについては「正規表現でバージョン情報を __init__.py から抜き出し、それを文字列リテラルとして評価する」ということをやっています。
その他、exec
を使う、VERSION ファイルを使うなどの手法もあり、以下のページが参考になります。
その他の引数の意味については以下の通りです。
include_package_data
- True の場合、MANIFEST.in 記載のファイルまたは
package_data
に指定したファイルをビルド済み配布物 (bdist) に含める(「補足事項: wheel パッケージにデータファイルを含める」にて後述) (Including Data Files)
- True の場合、MANIFEST.in 記載のファイルまたは
zip_safe
- True の場合 zip されたパッケージを展開せずにスクリプトを実行することを許可する (Setting the
zip_safe
flag)python setup.py install
時に site-packages 下に zip 形式の egg パッケージがそのまま配置される
pip install
する場合には関係ないが、念のため False にしておく
- True の場合 zip されたパッケージを展開せずにスクリプトを実行することを許可する (Setting the
install_requires
- 実行時に必要なパッケージを列挙する (Declaring Dependencies)
extras_require
- テストや開発に必要なパッケージを列挙する (Declaring “Extras” (optional features with their own dependencies))
entry_points
pip install
時に指定したメソッドを呼び出すコマンドラインアプリケーションがグローバル 3 にインストールされる (Dynamic Discovery of Services and Plugins)- この例では
python_boilerplate.cli
パッケージのmain
関数を呼ぶ CLI アプリケーションがpython-boilerplate
実行可能ファイルとしてインストールされる
より詳細な情報については setuptools の公式ページを参照してください。
MANIFEST.in
Python パッケージの配布形式としてはソースコード配布物(source distribution, sdist)とビルド済み配布物 (built distribution, bdist) がありますが、 前者のソースコード配布物に含めるべきファイルを MANIFEST.in で指定します 4 。
include tox.ini graft tests graft examples graft docs global-exclude *.py[co] prune docs/_build prune docs/_themes
コマンドの意味は以下の通りです(Python モジュールの配布 (レガシーバージョン) の コマンドリファレンス より抜粋)。
include
: 指定したパターンにマッチする全てのファイルを含めるgraft
: 指定したディレクトリ配下の全てのファイルを含めるprune
: 指定したディレクトリ配下の全てのファイルを除外するglobal-exclude
: ソースツリー内にある、指定したパターンにマッチする全てのファイルを除外する
python_boilerplate/__init__.py
setup.py から参照するため __version__ = 文字列リテラル
の形でバージョン情報を埋め込んでおきます。また、python_boilerplate
パッケージのトップレベルに置きたい変数・関数・クラスを再エクスポートします。
__version__ = '1.0.0' from .core import add __all__ = ['add']
python_boilerplate/__main__.py
python -m python_boilerplate
を実行するとこのコードが実行されます。
ここでは cli
モジュールの main
関数に処理を丸投げします。
if __name__ == '__main__': from .cli import main main()
python_boilerplate/cli.py
プログラムをコマンドライン引数を受け取るアプリケーションとして実行するためのコードをここに書きます。setup.py の entry_points
でこの main
関数が指定されています。
def main(): import sys from .core import add if len(sys.argv) == 3: x, y = map(int, sys.argv[1:]) print(add(x, y)) else: print('please specify 2 arguments', file=sys.stderr) sys.exit(1) if __name__ == '__main__': main()
なお、コマンドライン引数のパースには標準の argparse ライブラリやサードパーティ製の click を使うとよいでしょう。
python_boilerplate/core.py
今回はこのファイルにメインロジックを書いています。
def add(x, y): return x + y
tests/test_core.py
ここに pytest から実行されるテストコードを書きます。
from python_boilerplate import add def test_add(): assert add(1, 1) == 2
setup.cfg
setup.cfg は本来は distutils (setuptools の根底にあるライブラリ)の設定ファイルですが、サードパーティ製のライブラリもこの setup.cfg に書かれた設定を読む(読める)ことが多いです。 ここでは pytest と coverage の設定をします。
[tool:pytest] minversion = 3.0 testpaths = tests [coverage:run] branch = True source = python_boilerplate tests [coverage:paths] source = python_boilerplate .tox/*/lib/python*/site-packages/python_boilerplate
tox.ini
tox は様々な処理系・様々な依存ライブラリのバージョンの組み合わせでテストを実行してくれるテストランナーです。 ここでは tox.ini(設定ファイル)に Python 3.4 から 3.7 までの処理系を対象としてテストを実行し、カバレッジレポートを出力するよう指定しています。
[tox] envlist = py{37,36,35,34} coverage-report skip_missing_interpreters = true [testenv] passenv = LANG deps = pytest>=3 coverage commands = coverage run -p -m pytest tests [testenv:coverage-report] deps = coverage skip_install = true commands = coverage combine coverage report coverage html
開発フロー
venv 環境の用意
Python 3.4 からは Python 仮想環境を作成する venv が標準パッケージとして用意されています(従来の virtualenv
のような仕組みです)。
開発中はシステムやユーザの Python 環境 5 を汚さないように venv を使った方がよいでしょう。
python3 -m venv .venv . .venv/bin/activate
カレントディレクトリのパッケージをインストール
setup.py の存在するディレクトリで以下のコマンドを打つとパッケージが環境にインストールされます。
pip install . pip install --upgrade . # アップグレードの場合(-U でもよい)
別のライブラリから python_boilerplate がライブラリとして import できるようになります。
import python_boilerplate print(python_boilerplate.add(1, 1)) # => 2
また、setup
関数の entry_points
で指定した関数を呼ぶスクリプトが bin/ 下にインストールされます。
$ type python-boilerplate python-boilerplate is /path/to/.venv/bin/python-boilerplate $ python-boilerplate 1 1 2
開発者向けのパッケージインストール
開発時には以下のコマンドでパッケージをインストールするとよいでしょう。
pip install -e '.[dev]'
-e
(--editable
) オプションを付けることで開発モード(編集可能モード)でインストールされます。
通常、カレントディレクトリ下のソースを編集しても再度 pip install
するまで site-packages 下のファイルに変更は反映されません。
-e
オプションを付けると site-packages
下に python-boilerplate.egg-link
というファイル 6 が作成され、
カレントディレクトリのソースコードに対する変更が即座にパッケージへの変更として反映されるようになります。
また、インストールするパッケージ(ここでは .
)の後に [dev]
7 を付けると setup
関数の extras_require
で dev
として指定したパッケージが依存パッケージとして同時にインストールされます。
pytest を使ったテスト
pytest
コマンドを実行するとテストが実行されます。
pytest
setup.cfg で指定したディレクトリ以下の test_*.py または *_test.py がロードされ、test_
で始まる関数(および Test*
クラスの test_*
メソッド)が実行されます。
tox を使ったテスト
tox
コマンドで複数の処理系・依存ライブラリのバージョンに対してテストを行えます。
tox
また、htmlcov/ 下にカバレッジレポートが生成されます。
ソースコード配布物の作成
以下のコマンドで dist/python_boilerplate-1.0.0.tar.gz が生成されます。ソースコード配布物には MANIFEST.in で指定したファイルが含まれます。
python setup.py sdist
ソースコード配布物を pip でインストールすることも可能です。
pip install --upgrade python_boilerplate-1.0.0.tar.gz
wheel 形式のビルド済み配布物の作成
wheel 形式 (*.whl) は PEP 427 で定められているビルド済み配布物のフォーマットであり、現在のデファクトスタンダードです。 これを作成するには最初に wheel パッケージをインストールしておく必要があります。
pip install wheel
以下のコマンドで dist/python_boilerplate-1.0.0-py3-none-any.whl が生成されます。
python setup.py bdist_wheel
wheel パッケージには tox.ini や tests/ などパッケージディレクトリ(ここでは python_boilerplate/ )の外にあるファイルは含まれていません。 デプロイ時はこのファイルをサーバに配布して pip でインストールするとよいでしょう。
pip install --upgrade python_boilerplate-1.0.0-py3-none-any.whl
補足事項
wheel パッケージにデータファイルを含める
テキストファイル・JSON ファイル・CSV ファイルなど、Python のソースファイル (*.py) ではないデータファイルを wheel パッケージに含めたい場合を考えます。 ここでは、python_boilerplate/data/foo.json をそのファイル名とします。
MANIFEST.in に何も指定がない場合、ソースファイル配布物 (sdist) にも wheel パッケージにも foo.json は含まれません。 MANIFEST.in に以下の記述を追加してみます。
include python_boilerplate/data/*.json
ここで、setup
関数の include_package_data
引数を False と指定した場合、sdist には foo.json は含まれますが wheel パッケージには foo.json は含まれません。
include_package_data=True
とすることで MANIFEST.in で指定したパッケージ内のデータファイルを wheel パッケージに含めることができます。
詳細は setuputils のリファレンスの Including Data Files の章を参照してください。
API ドキュメント出力 (sphinx)
API ドキュメント(Javadoc 的なもの)を生成したいだけなら sphinx-quickstart
コマンドより sphinx-apidoc
コマンドを使った方が手軽です。sphinx パッケージをインストールした状態で以下を実行します。
if [ -e docs/conf.py ]; then sphinx-apidoc -f -o docs/ python_boilerplate/ # 2回目以降 else sphinx-apidoc -F -o docs/ python_boilerplate/ # 初回実行 fi make -C docs/ html
.gitignore
.gitignore は github/gitignore の Python.gitignore をそのまま使えばよさそうです。
curl -L https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore -o .gitignore
setup.cfg に setup 関数の引数を書く
setuptools 30.3.0 からは setup.cfg に setup
関数の引数のほとんどを書けるようになりました (Configuring setup() using setup.cfg files)。
setuptools のバージョンを気にしなくてよい場合はこちらの記法を使ってもよさそうです。
setup.cfg(抜粋)
バージョン情報については VERSION ファイルから取得する方法に変更しています。
[metadata] name = python_boilerplate version = file:python_boilerplate/VERSION [options] packages = python_boilerplate include_package_data = True zip_safe = False python_requires = >=3.4 install_requires = [options.extras_require] dev = pytest>=3 coverage tox [options.entry_points] console_scripts = python_boilerplate = python_boilerplate.cli:main
setup.py
setup.py は setup
関数を呼ぶだけになりました。
from setuptools import setup setup()
pyproject.toml
PEP 518 で pyproject.toml というファイルでビルド時に必要な依存パッケージを設定できるよう定められており、pip 10 はこれに対応しています。 また、pyproject.toml は setuptools 代替のビルドシステム(flit, Poetry など)用の設定ファイルとしても使われつつあります (PEP 517)。
他のパッケージ構成例
- pypa/sampleproject: A sample project that exists for PyPUG's "Tutorial on Packaging and Distributing Projects"
- PyPA のサンプルプロジェクト
- boto/boto3: AWS SDK for Python
- flask と似たパッケージ構成
- yaml/pyyaml: Canonical source repository for PyYAML
- Python 2 系向けのソースコードが lib/、3 系向けのソースコードが lib3/ 下に存在している
- ext/ 下に Cython を利用したソースコードも存在し、setup.py がかなり複雑
- pallets/itsdangerous: Various helpers to pass trusted data to untrusted environments
- ソースコードとテストが 1 ファイルずつしかない構成
参考
- Python Packaging User Guide — Python Packaging User Guide
- Python Packaging Authority (PyPA) によってメンテナンスされているパッケージングに関するドキュメントです。
- Repository Structure and Python — Kenneth Reitz
- requests ライブラリの作者によるパッケージ構成例です。
- Python パッケージ管理技術まとめ (pip, setuptools, easy_install, etc)
- Python のパッケージ管理の歴史的な事情について知ることができます。
- Pythonとパッケージングと私
- setuptools と setup.cfg について詳しく解説されている 2017 年のスライドです。pyproject.toml と PEP 517, PEP 518 にも触れられています。
おわりに
今回は 2018 年現在の Python のパッケージ構成のベストプラクティスについて考えてみました。 「最小構成を作る」と言いつつとても長い記事になってしまいました……。
Python のパッケージングは歴史的な事情もあり非常に複雑です。 そして setuptools や pip のバージョンアップ、pyproject.toml の標準化、setuptools 代替のツールの出現など、今後エコシステムがより複雑化していく可能性もあります。 しかし新しいツールが現実的に使われ出すにはそれなりの時間がかかるはずで、まずは現在最も使われており安定している setuptools の使い方について習熟するのがよいと思います。 この記事がその手助けとなれば幸いです。
追記 (2018/11/19)
続編となる記事「2019年に向けてPythonのモダンな開発環境について考える」を書きましたのでよろしければそちらもご覧ください。
更新履歴
- 2018/06/15
- 初版
- 2018/11/19
- 続編の記事に合わせてディレクトリ名・パッケージ名を変更
- ikasat/python-boilerplate リポジトリへのリンクを追加
- tox.ini に
skip_missing_interpreters = true
を追加
採用情報
朝日ネットでは新卒採用・キャリア採用を行っております。
-
ちなみに Red Hat Enterprise Linux 8(および CentOS 8)からは Python 3 系が標準のリポジトリに入る(というより 2 系から置き換えられる)ようです。↩
-
python setup.py sdist
時に警告を出したくない場合はurl
,author
,author_email
も追加してください。↩ -
Linux では例えば /usr/bin や ~/.local/bin (
--user
指定時)など(ディストリビューションによって異なります)。↩ -
setup
関数でinclude_package_data
を True とした場合、MANIFEST.in にビルド済み配布物に同梱するファイルを指定することもあります(「補足事項: wheel パッケージにデータファイルを含める」を参照)。↩ -
Linux では例えば /usr/lib/python3.x/site-packages や ~/.local/lib/python3.x/site-packages など(ディストリビューションによって異なります)。↩
-
実態は
pip install -e
時に指定したパスの絶対パスが記載されたテキストファイルです。↩ -
zsh では
[
]
は特殊文字であることに注意。↩