📦

PyPIでパッケージを配布するときのプラクティス

2022/10/16に公開

概要

Python Packaging User GuideエキスパートPythonプログラミング 改訂3版 を読みました。

https://packaging.python.org/en/latest/

パッケージの生成や配布には様々なプラクティスがあるように思えたので個人的に印象的だったものを抜粋してまとめました。

中には古い情報もありそうでしたが、新しいツールを理解する下地としては使えそうだったのでそう言うものも含めて記録してます。

前提

以下のような経験や知識があること

  • PyPIへのパッケージ登録を行ったことがある、もしくは大まかな手順は把握している

https://zenn.dev/k0kishima/articles/7a977bd2dd055e

定義

自分が最初調査したときはいきなり 「ソース配布物」 みたいな単語が出てきて何のことかわからなかったので、そういったものは下表にまとめました。

用語 意味
ビルド成果物 一般的にパッケージを公開するためにはパッケージ配布サービス(一般的にはPyPI)に登録しなければいけないが、そこにアップロードするもの。ソース配布物とビルド済み配布物に大別される
ソース配布物
(ソースパッケージ)
ビルド成果物の一種 (sdist) 。パッケージのモジュールを圧縮済みのアーカイブ (.tar.gz ファイル)にしたもの
ビルド済み配布物
(ビルド済みパッケージ)
ビルド成果物の一種 (wheels)
バイナリ配布物 ビルド済み配布物のうち、コンパイル済みのエクステンションを含むもの
純Python CやC++などの他言語で書かれた拡張を含まず、Pythonのソースだけで構成されていること

sdistwheels は頻出用語ですが、以下で詳細を確認できます。
https://packaging.python.org/en/latest/overview/#python-source-distributions

プラクティスのindex

ツールの選定のプラクティス

  • ソース配布物の生成には setuptools を用いる
  • ビルド済みパッケージ配布形式には wheel を用いる
  • ビルド成果物のアップロードには twine を用いる

パッケージ生成のプラクティス

  • ある程度の規模のパッケージでは名前空間を分ける
  • メタデータの設定値を極力ハードコーディングしない
  • ライセンスは必ず指定する
  • ビルド前にメタデータを検証する

パッケージ配布のプラクティス

  • PyPIで配布する前にTestPyPIで予行演習をしておく
  • バイナリ配布物は対応するソース配布物とペアで配布する
  • PyPIでの検索利便性向上のために classifiers を設定しておく
  • twine check で PyPI で long description が正しく描画されるかを確認しておく

ツールの選定のプラクティス

ソース配布物の生成には setuptools を用いる

標準ライブラリで distutils というものがあり、それでもプロジェクトの定義やソース配布物の作成が行えるが、より高機能な setuptools の使用が推奨されている。
(distutils は1998年に導入され、 setuptools はその強化版として2003年にリリースされている)

従って、ソース配布物の生成には setuptools を用いる。

※ Python 3.12 では distutils が廃止される予定

ビルド済みパッケージ配布形式には wheel を用いる

wheelegg を置き換えるために開発されたパッケージ配布の新しい標準。
egg形式は現在では非推奨となっているため wheel を使う。

pipコマンドなどで wheel をインストールしておくと bdist_wheeldistutils に追加される。
それにより python setup.py bdist_wheel のようにビルドが実行できる。

wheel の利点は以下で確認できる。
https://pythonwheels.com/

ビルド成果物のアップロードには twine を用いる

パッケージ配布サービスへビルド成果物をアップロードするためのツールには twine があり、これを使用することが推奨されている。

Warning In other resources you may encounter references to using python setup.py register and python setup.py upload. These methods of registering and uploading a package are strongly discouraged as it may use a plaintext HTTP or unverified HTTPS connection on some Python versions, allowing your username and password to be intercepted during transmission.
cite: https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#uploading-your-project-to-pypi

python setup.py registerpython setup.py upload は通信上の脆弱性があるので使ってはいけない。

パッケージ生成のプラクティス

ある程度の規模のパッケージでは名前空間を分ける

ある程度大きなパッケージは構造化を工夫して並行して開発できるようにした方がいい。
例えば、当方が個人開発で自作しているボートレース関連のパッケージは汎用的な名前空間として metaboatrace があって、その下にそれぞれ models, scrapers などの名前空間が派生している。

https://pypi.org/project/metaboatrace.models
https://pypi.org/project/metaboatrace.scrapers

これならboatrace.modelsboatrace.scrapers をそれぞれ独立して開発することができる。

メタデータの設定値を極力ハードコーディングしない

setup.pysetup.cfg の各項目には、値をプロジェクトに点在する他のソースコードや設定ファイルなどから取得できるものがある。
そういったものを動的に取得することで不整合を回避できる。

例えば、バージョンは以下のように設定することでモジュールでの定義を動的に読み込める。

[metadata]
version = attr: boatrace.models.__version__

long_description なんかも README のコンテンツを適用するなどのプラクティスがある。

ライセンスは必ず指定する

Every package should include a license file detailing the terms of distribution. In many jurisdictions, packages without an explicit license can not be legally used or distributed by anyone other than the copyright holder. If you’re unsure which license to choose, you can use resources such as GitHub’s Choose a License or consult a lawyer.
cite: https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#license-txt

上記引用に記載があるように多くの国では明示的なライセンス条項がないと、著作権保持者でなければ誰も合法的にパッケージを使用したり配布したりすることができない。

ライセンスファイル(LICENSE.txt)を含めて、メタデータの設定ファイルでは適用するライセンスを明示しておくといい。

Optional, but it is highly recommended to supply this.
cite> https://python-poetry.org/docs/pyproject/#license

Poetry のドキュメントの pyproject.toml の節でも定義が推奨されている。

ビルド前にメタデータを検証する

setup.py で定義する項目が不足していないかなどは以下のように確認できるので、ビルド前にチェックしておく。

$ python setup.py check
running check
warning: check: missing required meta-data: version, url

warning: check: missing meta-data: either (author and author_email) or (maintainer and maintainer_email) should be supplied

パッケージ配布のプラクティス

PyPIで配布する前にTestPyPIで予行演習をしておく

PyPIには本番と同じ外観で、パッケージのインストールなど一連の動作も同様に行えるテスト環境がある。
https://testpypi.python.org/

実際にPyPIで登録する前にこれを利用して動作確認をしておくのがベター。

ただ、依存パッケージなどを利用する際に、本番のPyPIに登録されているバージョンが必ずしもTestPyPIにも登録されているとは限らないのでその点は注意。

例えば、numpy

https://test.pypi.org/project/numpy/#history

この記事を記載している時点だとTestPyPIには 1.9.3 しか登録されていない。

pip install 実行の際にパッケージインデックスに TestPyPI を指定すると依存パッケージも TestPyPIから取得されるようで、ここで存在しないバージョンを取ろうとしてしまいエラーになることもある。

例えば以下。

$ pip install -i https://test.pypi.org/simple/ boatrace.official==0.0.7
Looking in indexes: https://test.pypi.org/simple/
Collecting boatrace.official==0.0.7
  Downloading https://test-files.pythonhosted.org/packages/dc/e8/de01130d29b2edb554859915473589aecffdb166e5563464fcb45b7d32cd/boatrace.official-0.0.7-py3-none-any.whl (41 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 41.4/41.4 KB 1.0 MB/s eta 0:00:00
ERROR: Could not find a version that satisfies the requirement numpy==1.23.4 (from boatrace-official) (from versions: 1.9.3)
ERROR: No matching distribution found for numpy==1.23.4

バージョンが 1.23.4numpy に依存しているパッケージを TestPyPI からインストールしようとしているが、 バージョンが 1.23.4numpy は本番のPyPIにはあるが、 TestPyPIにはないのでダウンロードができずにエラーになる。

この点は注意が必要。

バイナリ配布物は対応するソース配布物とペアで配布する

pip はソース配布物(sdist)とビルド済み配布物(wheels)両方に対応している。
それら両方がPyPIに存在しているときは wheels の方が優先される。

Wheels are a pre-built distribution format that provides faster installation compared to Source Distributions (sdist), especially when a project contains compiled extensions.
cite: https://packaging.python.org/en/latest/tutorials/installing-packages/#source-distributions-vs-wheels

wheels がバイナリ配布物である場合は sdist (ソース配布物) に比べて短時間でインストールされる。
ただし、バイナリ配布物がパッケージを使用するプラットフォームに適合しているとは限らず、そういった場合にソース配布物からビルドを行えるように、バイナリ配布物は対応するソース配布物とペアで配布するのがベター。

純Python パッケージでは、典型的には、ひとつの "万能型" の wheel さえあれば十分らしいが、wheels が利用できない場合のフォールバック先としてソース配布物が利用されるため、ソース配布物も同梱しておくのが無難。

PyPIでの検索利便性向上のために classifiers を設定しておく

PyPIとdistutils は trove classifiers と呼ばれる分類名をパッケージのカテゴライズに用いる。
(setup.pyclassifiersで指定しているもの)

これはパッケージのインストール自体に関わる情報ではないが、パッケージのカテゴリ検索などで用いられる。

適切に分類すればパッケージユーザーの利便性が上がり、パッケージエコシステムを健全に保つ責任を果たすことができるので設定するのがベター。

twine check で PyPI で long description が正しく描画されるかを確認しておく

これも前節と同様パッケージ配布サイト側(PyPI)での最適化の取り組み。

配布物をアップロードする前に以下のコマンドで brief/long description が文法的に妥当かどうかを確かめることができる。

$ twine check dist/*
Checking dist/boatrace.official-0.0.1-py3-none-any.whl: PASSED
Checking dist/boatrace.official-0.0.1.tar.gz: PASSED

最後に

冒頭でも触れたように中には古くなっている情報もありそうでした。

例えば今だと pyproject.yml に設定が集約される傾向があったり、それを利用するなら python -m build でビルド成果物を生成するのでそれなら setup 系のコマンド使う必要がなかったりしそうでした。

The configuration file depends on the tool used to create the build artifacts. The standard practice is to use a pyproject.toml file in the TOML format.
cite: https://packaging.python.org/en/latest/flow/#the-configuration-file

※ Python Packaging User Guide にも pyproject.toml を使うのが一般的という記載がある

トレンドが変わりこの記事の大半がdeprecatedなものになるなどした時点で、記事を更新するなり新規作成するなりしたいと思います。

参考

https://speakerdeck.com/aodag/setuptoolsnozui-jin

Discussion