コマンドライン動作のツールを作成しパッケージングする

このガイド文書では、Python 仮想環境 を作成し管理するツールである pipx でインストールできるような、スタンドアローンのコマンドラインアプリケーションを作成しパッケージングすること、そして、パッケージの実行可能なスクリプト (及び利用可能な man ページ) を外部からコマンドラインで使えるようにすることについて、一通り案内します。

パッケージを作成する

まず最初に、 プロジェクト のためのソースツリーを作成しましょう。例示のために、コマンドラインの引数として与えられた人物名に挨拶 (文字列) を出力する単純なツールをビルドするものとしましょう。

課題

Python パッケージの最適な構造に関するアドバイスについて、他のガイド文書や議論、リンク集はこちら。

このプロジェクトは、 src-layout を厳守し、最後には、トップレベルのフォルダとパッケージの名称として greetings を伴った、このファイルツリーのようになります:

.
├── pyproject.toml
└── src
    └── greetings
        ├── cli.py
        ├── greet.py
        ├── __init__.py
        └── __main__.py

ツールの機能性に責任のある実際のソースコードは、メインのモジュールの名称から命名された greet.py の中に保存されるでしょう。

import typer
from typing_extensions import Annotated


def greet(
    name: Annotated[str, typer.Argument(help="The (last, if --title is given) name of the person to greet")] = "",
    title: Annotated[str, typer.Option(help="The preferred title of the person to greet")] = "",
    doctor: Annotated[bool, typer.Option(help="Whether the person is a doctor (MD or PhD)")] = False,
    count: Annotated[int, typer.Option(help="Number of times to greet the person")] = 1
):
    greeting = "Greetings, "
    if doctor and not title:
        title = "Dr."
    if not name:
        if title:
            name = title.lower().rstrip(".")
        else:
            name = "friend"
    if title:
        greeting += f"{title} "
    greeting += f"{name}!"
    for i in range(0, count):
        print(greeting)

上記の関数は、どのように出力すべき挨拶を組み立てるかを決定するいくつかのキーワード引数を受け取ります。さて、同様にそれを準備するためのコマンドラインインタフェースを cli.py の中に構築しましょう:

import typer

from .greet import greet


app = typer.Typer()
app.command()(greet)


if __name__ == "__main__":
    app()

コマンドラインインタフェースは、 Python の型品とを基礎とした使いやすい CLI パーサである typer とともにビルドされます。自動補完と申し分のないスタイルを持つコマンドラインヘルプが、最初から使えます。別のオプションは、 Python 標準ライブラリに含まれる argparse でしょう。これはほとんどの必要性に十分に応えるものですが、適切に機能させるためには、通常は cli.py に、ソースコードをたくさん書かなければなりません。さらに別の方法としては、 docopt を使うことで、 docstrings だけに基づいて CLI インタフェースを作成することができます; 熟練ユーザには、 click (typer はこれを基礎として使っています) の使用をお勧めします。

さて、プロジェクトを通常の インポートパッケージ として定義するために、空の __init__.py ファイルを追加しましょう。

__main__.py ファイルは、 runpy から走らせた (すなわち python -m greetings 、これはフラットレイアウトならすぐに動作しますが、 src レイアウトではパッケージのインストールを要求します) 時、アプリケーションのメインのエントリポイントを提示するものですので、コマンドラインインタフェースをここで初期化しましょう:

if __name__ == "__main__":
    from greetings.cli import app
    app()

注釈

例えば python src/greetings のように、ソースコードツリー から直接にコマンドラインインタフェースを呼び出すことができるようにするためには、このファイルの中にそれなりのハックが置かれていることでしょう; もっと読みたければ src レイアウト付きのソースコードからコマンドラインインターフェースを走らせる をどうぞ。

pyproject.toml

プロジェクトの メタデータpyproject.toml の中にあります。 typer (このチュートリアルではバージョン 0.1.2.3 を使います) への依存を追加しながら、 pyproject.toml を書く に記述されているように pyproject メタデータキー[build-system] テーブルに値が埋め込まれます。

コマンドラインツールとして認識されるプロジェクト向けには、さらに console_scripts エントリポイント ( 実行可能なスクリプトを作成する をご覧ください) が サブキー として追加されている必要があります:

[project.scripts]
greet = "greetings.cli:app"

これで、プロジェクトのソースコードツリーが、インストールすることができる 配布パッケージ へと変換される準備ができました。

pipx を使ってパッケージをインストールする

スタンドアローンのコマンドラインツールをインストールする に記述されているように pipx をインストールした後に、プロジェクトをインストールしましょう:

$ cd path/to/greetings/
$ pipx install .

これによって、エントリポイントとして定義した実行可能なスクリプトを露出し、コマンド greet を利用可能にします。試してみましょう:

$ greet
Greetings, friend!
$ greet --doctor Brennan
Greetings, Dr. Brennan!
$ greet --title Ms. Parks
Greetings, Ms. Parks!
$ greet --title Mr.
Greetings, Mr. mr!

この例では typer を使っていますので、今や --help オプションをつけて呼び出すことでプログラムの使用方法の概要を得たり、 --install-completion オプション経由でコマンドライン補完を得ることができるでしょう。

プログラムを永続的にインストールすることなしにただプログラムを走らせるためには、一時的な (しかしキャッシュされる) 仮想環境を作ってくれる pipx run を使いましょう。

$ pipx run --spec . greet --doctor

しかしながら、このシンタックスはちょっと実際的ではありません; 上で定義したエントリポイントの名称がパッケージ名称と一致しないので、 (たとえたったひとつだけしか実行スクリプトがないとしても) 明示的にどの実行スクリプトを走らせるのかを述べる必要があります。

しかしながら、この問題に対するもっと実際的な解決法で、 pipx run でエントリポイントを指定する形式があります。以下に述べる pyproject.toml 内で定義することで同様のことができるのです:

[project.entry-points."pipx.run"]
greetings = "greetings.cli:app"

このエントリポイント (その名称がパッケージ名称と 一致しなければなりません) のおかげで、 pipx は実行可能なスクリプトをデフォルトのものとして選択しそれを走らせるでしょう、そして、それはこのコマンドを実現可能にします:

$ pipx run . --doctor

結論

これまでに、 Python で書かれたコマンドラインアプリケーションをパッケージする方法がわかったものと思います。さらなる一歩としては、そのパッケージを配布すること、つまり、最も一般的には PyPI のような パッケージインデックス へとアップロードすることでしょう。それをするためには、 プロジェクトをパッケージする の指示に従ってください。そして、一旦アップロードができたなら、パッケージがどのように受け入れられたかについて いくらかの調査を行う ことを実行してください!