小ネタ④ 今風のパッケージ化
構成
top
├── pyproject.toml
├── setup.cfg
├── mypackage
│ ├── __init__.py
│ └── sample.py
└── tests
└── test_sample.py
※ディレクトリ構成はpytestのドキュメントでtestモジュール配置のベストプラクティスとして紹介されていたものを参考にしました。
pyproject.toml
パッケージをビルドする際に必要な情報(システム要件や依存関係など)を記載するファイル
pipコマンドでパッケージをインストールする際に使用されます。
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
setup.cfg
Setuptoolsのコンフィグファイル
(上述のpyproject.tomlでbuild-systemとしてstuptoolsを採用したため)
参考した記事に合わせて、metadataとoptionsをこちらのコンフィグに記載したのですが、この内容別にpyproject.tomlに書いても一緒じゃね??って思いました。
[metadata]
name = mypackage
[options]
install_requires =
requests
importlib-metadata; python_version < "3.8"
※最近はsetup.pyではなく、pyproject.tomlとsetup.cfgを使用するのが推奨なのだそうです。まだ完全移行はできていないようで、setup.pyが必要な場面もいくつかあるようですが…
バイナリ(コマンド)をインストールする場合
setup.cfgにoption.entry_pointsのセクションを追記します。
[options.entry_points]
console_scripts =
my_cmd = mypackage_mathkuro:main
“my_cmd”がインストール時のコマンド名になります。
ここはpythonのファイル名縛りが効かないので’-‘を使用することも可能です。
その右辺の”mypackage_mathkuro:main”の部分が、コマンドで実行されるエントリポイント(pythonの関数)になります。
これはパッケージ直下の__init__.pyに該当する関数を記載(or import)しておくのが一般的なようです。
# 該当する関数をimport
from .main import main
# または該当する関数を定義
def main():
...
コマンドはpipインストール先のbinディレクトリにインストールされます。
(–prefixオプションをつけてインストールする場合は、–prefixで指定したディレクトリ内にbinディレクトリが作成されます)
また、コマンドライン引数を渡すことも可能で、その場合はargparse等を使用してsys.argvからコマンドライン引数を読み込むようにエントリポイントの関数に記載しておきます。
参考
コマンド
必要なパッケージのインストール
ビルド作業等を行う際に標準以外のパッケージが必要なのでインストールしておきます。
python3 -m pip install --upgrade pip build setuptools wheel twine
パッケージ化(ローカル作業用)
ディレクトリ構成に記載のtopディレクトリで以下のコマンドを実行します。
python3 -m pip install -e .
-eをつけることで、パッケージが編集モードでローカルのpython環境にインストールされます。
※編集モードとしておくと、インストールしたパッケージがソースコードと紐づきます。(今回の例ではカレントディレクトリのソース)
パッケージ化(PyPIへアップロード)
まず、以下のコマンドでパッケージをビルドします。
python3 -m build
distディレクトリが作成され、その中にwheelファイルとソースファイルが作成されます。
次に、以下のコマンドで作成したファイルをPyPIにアップロードします。
python3 -m twine upload --repository testpypi dist/*
※上記はtestリポジトリの方なので、本番リポジトリにアップロードする際は読み替えてください。
pytest
python3 -m pytest ./tests/
テストコードの例
from pytest_mock import MockerFixture
from mypackage_mathkuro import sample
def test_sum():
assert sample.sum(1, 2) == 3
def test_queue(mocker: MockerFixture):
m = mocker.patch("mypackage_mathkuro.sample.deque")
max_len = 123
sample.MyClass(max_len)
m.assert_called_once_with(maxlen=max_len)
参考
https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/
https://setuptools.pypa.io/en/latest/userguide/declarative_config.html
https://packaging.python.org/en/latest/tutorials/packaging-projects/
https://docs.pytest.org/en/7.1.x/explanation/goodpractices.html
コメント