はじめに
自作したPythonプログラムは、最初は次のように直接実行することが多いと思います。
python mytool.py
しかし、ある程度使うようになると、次のようにインストールして使いたくなります。
pip install mytool
mytool
この記事では、自作したPythonプログラムを pip install できる形にする方法をまとめます。
例として、ginzaserver.py というPythonプログラムをパッケージ化し、インストール後に次のようなコマンドで起動できるようにします。
ginzaserver 8888 0
目標
この記事で作る構成は次のようなものです。
ginzaserver/
├── pyproject.toml
├── README.md
├── LICENSE
├── examples/
│ └── ginzaclient.py
└── ginzaserver/
├── __init__.py
└── ginzaserver.py
ポイントは次の3つです。
- Pythonコードをパッケージ用ディレクトリに入れる
-
pyproject.tomlを作る -
[project.scripts]でコマンド名とmain()を対応させる
変更前の構成
最初は次のような構成でした。
ginzaserver/
├── __init__.py
├── ginzaserver.py
├── ginzaclient.py
└── response_ginza.json
この状態でも、ディレクトリ内で直接実行することはできます。
python ginzaserver.py 8888 0
しかし、このままでは他の環境から次のようにインストールして使うには不十分です。
pip install ginzaserver
ginzaserver 8888 0
そこで、Pythonパッケージとして配布できる形に整理します。
ディレクトリ構成を整理する
まず、プロジェクト直下にパッケージ用のディレクトリを作ります。
ginzaserver/
├── pyproject.toml
├── README.md
├── LICENSE
└── ginzaserver/
├── __init__.py
└── ginzaserver.py
サンプルクライアントは、ライブラリ本体ではなく examples/ に移動します。
ginzaserver/
├── examples/
│ └── ginzaclient.py
└── ginzaserver/
├── __init__.py
└── ginzaserver.py
パッケージ本体に含めるべきものは、基本的には利用者がインポートしたり、コマンドとして実行したりするコードです。
一方、動作確認用のサンプル、テスト用JSON、実行例などは examples/ や tests/ に置く方が自然です。
ginzaserver.py に main() を用意する
コマンドとして実行したいPythonファイルには、main() 関数を用意しておきます。
例えば次のような形です。
# ginzaserver/ginzaserver.py
import sys
def main():
args = sys.argv[1:]
if len(args) != 2:
print("usage: ginzaserver port_number option")
print("example: ginzaserver 8888 0")
return
port = int(args[0])
option = int(args[1])
print(f"start server: port={port}, option={option}")
# ここに実際のサーバー起動処理を書く
# server.serve_forever()
if __name__ == "__main__":
main()
このようにしておくと、直接実行もできます。
python ginzaserver/ginzaserver.py 8888 0
さらに、後述する pyproject.toml で main() を指定すると、インストール後に次のように実行できるようになります。
ginzaserver 8888 0
__init__.py を用意する
パッケージディレクトリには __init__.py を置きます。
ginzaserver/
└── ginzaserver/
├── __init__.py
└── ginzaserver.py
中身は空でも動作します。
# ginzaserver/__init__.py
バージョンを書いておく場合は、例えば次のようにします。
__version__ = "0.1.0"
pyproject.toml を作る
プロジェクト直下に pyproject.toml を作ります。
ginzaserver/
├── pyproject.toml
├── README.md
├── LICENSE
└── ginzaserver/
├── __init__.py
└── ginzaserver.py
例です。
[build-system]
requires = ["setuptools >= 77.0.3", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "ginzaserver"
version = "0.1.0"
description = "HTTP Server for GiNZA - Japanese NLP Library"
readme = "README.md"
requires-python = ">=3.8"
license = "Apache-2.0"
authors = [
{ name = "Hiroki Oya", email = "your-email@example.com" }
]
keywords = [
"ginza",
"spacy",
"japanese",
"nlp",
"http-server"
]
dependencies = [
"ginza>=5.2,<6",
"ja-ginza>=5.2,<6"
]
[project.optional-dependencies]
electra = [
"ja-ginza-electra>=5.2,<6"
]
dev = [
"build",
"twine"
]
[project.scripts]
ginzaserver = "ginzaserver.ginzaserver:main"
[project.urls]
Homepage = "https://github.com/yourname/ginzaserver"
Repository = "https://github.com/yourname/ginzaserver"
[tool.setuptools.packages.find]
where = ["."]
include = ["ginzaserver*"]
exclude = ["tests*"]
重要なのは [project.scripts]
特に重要なのはこの部分です。
[project.scripts]
ginzaserver = "ginzaserver.ginzaserver:main"
これは、次の意味です。
インストール後に使えるコマンド名 = "Pythonモジュール:関数名"
つまり、
ginzaserver = "ginzaserver.ginzaserver:main"
と書くと、インストール後に次のコマンドが使えるようになります。
ginzaserver
このコマンドを実行すると、内部的には次の関数が呼ばれます。
ginzaserver/ginzaserver.py の main()
そのため、コマンド化したいPythonプログラムでは main() を用意しておくと扱いやすいです。
依存ライブラリを書く
外部ライブラリを使っている場合は、dependencies に書きます。
dependencies = [
"ginza>=5.2,<6",
"ja-ginza>=5.2,<6"
]
これにより、利用者が次のようにインストールしたときに、必要な依存ライブラリも一緒にインストールされます。
pip install ginzaserver
追加機能だけで必要なライブラリは、optional-dependencies に分けるとよいです。
[project.optional-dependencies]
electra = [
"ja-ginza-electra>=5.2,<6"
]
この場合、通常は軽量なモデルだけを入れて、必要な人だけ次のように追加インストールできます。
pip install "ginzaserver[electra]"
開発中にローカルインストールする
まずはPyPIに公開する前に、ローカル環境でインストールできるか確認します。
プロジェクト直下で次を実行します。
python -m pip install -e .
-e は editable install の意味です。
この形式でインストールしておくと、ソースコードを修正したときに再インストールせずに動作確認しやすくなります。
インストールできたら、コマンドを実行します。
ginzaserver 8888 0
ここで command not found になる場合は、次の点を確認します。
- 仮想環境が有効になっているか
-
pyproject.tomlの[project.scripts]が正しいか -
main()関数が存在するか - パッケージディレクトリに
__init__.pyがあるか
サンプルクライアントは examples/ に置く
サーバー本体とは別に、動作確認用のクライアントがある場合は examples/ に置くのがよいです。
examples/
└── ginzaclient.py
例えば、サーバーを起動したあとに次のように実行します。
python examples/ginzaclient.py
サンプルファイルまでコマンド化する必要はありません。
パッケージとして提供するコマンドは、基本的には利用者が普段使うものだけにした方が分かりやすいです。
今回であれば、提供するコマンドは ginzaserver だけにします。
[project.scripts]
ginzaserver = "ginzaserver.ginzaserver:main"
よくあるエラー: import時点で失敗する
パッケージ化すると、今まで気づかなかった import エラーに気づくことがあります。
例えば、次のような不要なワイルドカードimportがある場合です。
from ginza import *
実際には spacy.load("ja_ginza") だけを使っているのであれば、この import は不要です。
import spacy
必要のない import は削除した方がよいです。
特に from xxx import * は、パッケージ側の公開APIやバージョン差分の影響を受けやすく、import時点のエラーにつながる場合があります。
配布用パッケージをビルドする
ローカルで動作確認できたら、配布用のファイルを作ります。
まず、ビルドツールを入れます。
python -m pip install -U build
次にビルドします。
python -m build
成功すると、dist/ に次のようなファイルが作られます。
dist/
├── ginzaserver-0.1.0-py3-none-any.whl
└── ginzaserver-0.1.0.tar.gz
.whl は wheel 形式の配布ファイルです。
.tar.gz は source distribution、つまりソース配布物です。
wheelをローカルでインストールして確認する
PyPIにアップロードする前に、作成されたwheelをローカルでインストールして確認します。
python -m pip install dist/ginzaserver-0.1.0-py3-none-any.whl
確認します。
ginzaserver 8888 0
ここで正常に動けば、少なくとも配布ファイルとしては動作している可能性が高いです。
GitHubから直接インストールする
PyPIに公開する前でも、GitHubに置いておけば次のようにインストールできます。
pip install git+https://github.com/yourname/ginzaserver
特定のタグを指定する場合は次のようにします。
pip install git+https://github.com/yourname/ginzaserver@v0.1.0
PyPI公開前のテストや、限定的な共有ではこの方法も便利です。
PyPIにアップロードする
PyPIに公開する場合は twine を使います。
python -m pip install -U twine
アップロードします。
python -m twine upload dist/*
公開後は、利用者が次のようにインストールできるようになります。
pip install ginzaserver
そして、コマンドとして実行できます。
ginzaserver 8888 0
バージョンを更新して再リリースする
一度PyPIに公開したバージョン番号は、基本的に再利用できません。
修正版を出す場合は、pyproject.toml のバージョンを上げます。
[project]
version = "0.1.1"
その後、古い dist/ を削除してから再ビルドします。
rm -rf dist build *.egg-info
python -m build
python -m twine upload dist/*
最終的な確認手順
一連の確認手順は次のようになります。
# 開発用インストール
python -m pip install -e .
# コマンド確認
ginzaserver 8888 0
# 配布物作成
python -m pip install -U build
python -m build
# wheelのローカルインストール確認
python -m pip install dist/ginzaserver-0.1.0-py3-none-any.whl
# PyPIへアップロード
python -m pip install -U twine
python -m twine upload dist/*
まとめ
自作Pythonプログラムを pip install できるようにするには、最低限、次の作業を行えばよいです。
- パッケージ用ディレクトリを作る
-
__init__.pyを置く - 実行入口として
main()を用意する -
pyproject.tomlを作る -
[project.scripts]でコマンド名を定義する -
python -m buildで配布物を作る -
twineでPyPIにアップロードする
特に、コマンドラインツールとして配布する場合は、次の設定が重要です。
[project.scripts]
ginzaserver = "ginzaserver.ginzaserver:main"
これにより、Pythonファイルを直接指定して実行するのではなく、次のように自然なコマンドとして実行できるようになります。
ginzaserver 8888 0
小さな自作ツールでも、pip install できるようにしておくと、別環境への移行、他の人への共有、GitHubやPyPIでの公開がかなり楽になります。