雑記: 自分のパッケージを TestPyPI(PyPI のテスト環境)に登録するまで―外部パッケージに依存する場合

外部パッケージに依存するパッケージを TestPyPI(PyPI のテスト環境)に登録します。先に結論をいうと、

  • コードは外部パッケージに依存しているがメタデータに依存性を記述しないという無作法なパッケージをつくることができるのはできます。ユーザはインストールを終えて実行時に必要な外部パッケージがないことに気付かされることになります。そのようなパッケージはだめだと思います。
  • なので pyproject.toml に依存性をかけばよいです。ローカルでのインストールは問題ないし開発用の依存性も指定できます。ただ、ビルドしたパッケージを TestPyPI に登録してそこからインストールすると PyPI にある外部パッケージを自動で取り入れられないと思います(ビルドツールによっては外部依存性の解決時には PyPI を向くことをできるかもしれないですが?)。「そのような外部パッケージはないです」といわれるのでいわれた端から手動でインストールする運用になりますが、無作法にするよりはましだと思います(?)。そもそもきちんと開発を進めて PyPI のほうに登録すべきだと思います。
  • なお、pytorch のような外部パッケージを自動で入れられるのかに私は興味があったはずですが、もう面倒なので「pytorch は予め入れてください」という運用にしたいです。

  1. Dependencies Management in Setuptools - setuptools 68.0.0.post20230720 documentation
    • setuptools は使用しませんが Python や Hatch のドキュメントでは optional-dependencies が何のためにあるものかわからなかったのでこれを参照しました。
  2. How to specify extra-index in a pyproject.toml for pip and pip-tools? - Packaging - Discussions on Python.org
    • pyproject.toml で複数のインデックス URL を扱えないか調べましたが扱えないと思います。
  3. Dependencies Management in Setuptools - setuptools 68.0.0.post20230720 documentation
    • 1. と同じページ内ですが PyPI and other standards-conformant package indices do not accept packages that declare dependencies using direct URLs とあります。なので PyPI 上の外部パッケージを URL ごと指定するのはローカルではできそうですが TestPyPI に登録できないと思います。

まず依存関係を考慮せずファイル一式を用意する
まず外部パッケージに依存するクラスをかいて、その他のファイルは外部パッケージを依存するときと同様にします。ファイル一式はこちらにある通りですがツリーを描くと以下です。

cookies_models/
├── pyproject.toml
├── LICENSE
├── README.md
├── src/
│   └── cookies_models/
│       ├── __init__.py
│       └── mylinear.py
└── tests/
    └── test_mylinear.py

今回 mylinear.py に MyLinear というクラスを実装しましたが実態はほぼ torch.nn.Linear です。このクラスは torch と torchinfo に依存しています。今回仮想環境でなくシステム環境で開発していますが、私のシステム環境には torch と torchinfo があるのでテストは問題なく通ります。そして、前回同様にパッケージのビルド → アップロード → インストールもやってみるとできてしまいます。インストール後は以下のように動きます。

$ python
>>> import cookies_models as cm
>>> model = cm.MyLinear(4, 3)
>>> model.summary()
=================================================================
Layer (type:depth-idx)                   Param #
=================================================================
MyLinear                                 --
├─Linear: 1-1                            15
=================================================================
Total params: 15
Trainable params: 15
Non-trainable params: 0
=================================================================

ただ、試しに手元の torchinfo と cookies_models をアンインストールした上で再度 cookies_models をインストールすると、cookies_models のインストール自体はできますがインポートできないことがわかります。torchinfo が自動でインストールされないためです。

>>> import cookies_models as cm
Traceback (most recent call last):
  File "", line 1, in 
  File "C:\Users\c-mih\AppData\Local\Programs\Python\Python310\lib\site-packages\cookies_models\__init__.py", line 1, in 
    from cookies_models.mylinear import MyLinear
  File "C:\Users\c-mih\AppData\Local\Programs\Python\Python310\lib\site-packages\cookies_models\mylinear.py", line 2, in 
    import torchinfo
ModuleNotFoundError: No module named 'torchinfo'

pyproject.toml に依存関係を記述する
インストールできてもインポートできないパッケージではあまりに無作法なので、メタデータに外部パッケージ(ここでは torch, torchinfo)への依存性を記述します。外部パッケージに依存するついでにテストも unittest ではなく pytest にします。記述したコミットが以下です。
update · CookieBox26/cookies_models@4ca9370 · GitHub

上記のコミットでは pyproject.toml に以下を追記しています。

dependencies = [
    "torch",
    "torchinfo",
]

[project.optional-dependencies]
dev = [
    "pytest",
]

project.optional-dependencies というのはオプショナルな依存性ですが、和訳しても何を指定するものかわからないですが、参考文献 1. によれば、例えばあるパッケージにおいてオプションで PDF 出力もサポートしたい場合などに利用するようです。PDF 出力を必要としない人にまで PDF 出力用の依存パッケージの導入を強いたくないので理にかなっていると思います。そして、開発用にだけつかうパッケージ(ここでは pytest)を指定するときも project.optional-dependencies を利用するのがプラクティスであるようです(要出典)。

開発中にカレントディレクトリを editable モードでインストールしてテストするときは以下のようになります。これで手元に torchinfo や pytest がないときでも自動でインストールされます( torch については実験用に削除すると再インストールに時間がかかるので自動でインストールされるか確かめていませんというか上手くいかない気がします)。

pip uninstall cookies_models  # uninstall the package if already installed
pip install -e '.[dev]'  #  install the package in editable mode
pytest  # test


ローカルでのテストも上手くいったのでパッケージングしてアップロードして、手元のパッケージをアンインストールしてからインストールしてみます。

pip uninstall cookies_models
pip uninstall torchinfo
pip uninstall pytest
pip install -i https://test.pypi.org/simple/ cookies-models==0.0.2

そうすると torchinfo というものはないといわれてインストールできないことがわかります。

ERROR: No matching distribution found for torchinfo


それで、TestPyPI には torchinfo がないので実際ないのだろうと思います。であれば前回に TestPyPI にアップロードしてある cookies_utilities なら自動でインストールしてくれるはずです。なのでそれを実験するためにかき直したものがこれです。これをパッケージングしてアップロードしてインストールすると cookies_utilities も自動でインストールしてくれることが確認できます。

$ pip install -i https://test.pypi.org/simple/ cookies-models==0.0.3
(省略)
Installing collected packages: cookies-utilities, cookies-models
Successfully installed cookies-models-0.0.3 cookies-utilities-0.0.3