Python

Python小ネタ

小ネタ④ 今風のパッケージ化

構成

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

コメント

タイトルとURLをコピーしました