$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

Pythonで作ったCLIツールをGitHubから直接pipでinstallできるようにする方法

chat-hatenablogをpip installでインストール可能にした - $shibayu36->blog; にて、pip installで直接CLIツールをインストールできるようにした。

pip install git+https://github.com/shibayu36/chat-hatenablog.git

この時に調べたことをメモしておく。

やったこと

setup.pyを配置し、entry_points.console_scriptsにCLIとして動かしたいものを指定するだけ。

import os
from setuptools import setup, find_packages

here = os.path.abspath(os.path.dirname(__file__))

about = {}
with open(os.path.join(here, "chat_hatenablog", "__version__.py")) as f:
    exec(f.read(), about)

setup(
    name="chat_hatenablog",
    version=about["__version__"],
    author="Yuki Shibazaki",
    author_email="shibayu36@gmail.com",
    description="AI-powered software to interact with your HatenaBlog",
    license="MIT",
    packages=find_packages(),
    install_requires=[
        "python-dotenv==1.0.0",
        "html2text==2020.1.16",
        "langchain==0.0.132",
        "tqdm==4.65.0",
        "openai==0.27.4",
        "tenacity==8.2.2",
        "tiktoken==0.3.3",
        "numpy==1.24.2",
    ],
    extras_require={
        "dev": [
            "pytest==7.3.1",
            "mypy==1.2.0",
            "pytest-mypy-plugins==1.10.1",
            "types-tqdm==4.65.0.1",
        ],
    },
    entry_points={
        "console_scripts": [
            "chat-hatenablog = chat_hatenablog.main:main",
        ]
    },
)

このように配置するだけで、pip install git+https://github.com/shibayu36/chat-hatenablog.git するとchat-hatenablogコマンドがインストールされる。

もう少し詳しい説明をする。

基本はsetup.pyを置いておくだけでGitHubからインストールできる

そもそもpip installがgit repositoryに対応しているため、setup.pyを置いてpipが認識できるようにしたら良い。meta情報系を色々書き、依存パッケージをinstall_requiresに入れておく。

CLIツールで重要なポイントは、entry_points.console_scriptsへの指定だ。ここに コマンド名 = (package名):(関数名) という指定を加えておくことで、そのコマンドを実行したときにどの関数を呼び出すか設定できる。chat-hatenablogの場合、chat_hatenablog/main.pyのdef mainの中にエントリーポイントとしての処理を書き、setup.pyからそこを指定した。

ちなみに最近の形式としてpyproject.tomlを使った方式もあるが、機能が多くていまいちよくわからず、一旦シンプルにsetup.py形式でやってみた。もちろん色々便利なグッズは揃っているはずなので、また試してみたい。 参考: https://qiita.com/ieiringoo/items/4bef4fc9975803b08671

extras_requireに開発向けの依存パッケージを置く

pytestなどはインストール時には不要で、開発向けに必要な依存パッケージである。このようなものもsetup.pyで管理ができる。具体的にはextras_requireにラベル付きで依存パッケージを書ける。

    extras_require={
        "dev": [
            "pytest==7.3.1",
            "mypy==1.2.0",
            "pytest-mypy-plugins==1.10.1",
            "types-tqdm==4.65.0.1",
        ],
    },

このようにしておくと次のように指定することで、editorialモードでdevも含めてインストールし、開発を始めることができる。

pip install -e '.[dev]'

version情報を一箇所にまとめる

ツール内でバージョン情報を使うために、現在のchat-hatenablogのバージョン情報をchat_hatenablog/__version__.py というファイル内に入れていた。setup.pyからもこれをimportして使えば良いだけと考えた。しかしこの状態でpip install -e '.[dev]'すると、次のようなエラーでうまくいかなかった。

ModuleNotFoundError: No module named 'numpy'

おそらくこれはfrom chat_hatenablog/__version__ import __version__したタイミングでいろんなモジュールの読み込みは走ってしまっており1、pip install時には依存モジュールは入っていないためエラーになってしまっているようだ。

これに関してはpipenvの例が参考になった。__version__.pyに定義されているデータだけ読み込むためにexecするという技。

here = os.path.abspath(os.path.dirname(__file__))

about = {}

with open(os.path.join(here, "pipenv", "__version__.py")) as f:
    exec(f.read(), about)

これを参考にしてversion情報は__version__.pyで管理し、setup.pyではそれを利用することができた。この辺りはもしかすると別ツールを使うと簡単にできるかもしれないので、また調べてみたい。

まとめ

今回はchat-hatenablogをpip installできるようにした時に調べたことをまとめてみた。こうするともっと便利という情報があれば知りたい。

参考資料


  1. もちろんchat_hatenablog/__version__.pyにはversion情報しか入れておらず、他のimport文はなかったが、ロードのなんらかの仕組みで__init__.pyなど別ファイルも読み込まれてしまっているのだろう