コマンド入門
複数の *CLIオプション* と *CLI引数* を持つCLIプログラムを作成する方法を見てきました。
しかし、**Typer** を使用すると、複数のコマンド(サブコマンドとも呼ばれます)を持つCLIプログラムを作成できます。
たとえば、プログラム `git` には複数のコマンドがあります。
`git` の1つのコマンドは `git push` です。そして、`git push` は、独自の *CLI引数* と *CLIオプション* を取ります。
例えば
// The push command with no parameters
$ git push
---> 100%
// The push command with one CLI option --set-upstream and 2 CLI arguments
$ git push --set-upstream origin master
---> 100%
`git` の別のコマンドは `git pull` で、これもいくつかの *CLIパラメータ* を持ちます。
同じ大きなプログラム `git` の中に、いくつかの小さなプログラムがあるかのようです。
ヒント
コマンドは *CLI引数* と同じように見えます。先行する `--` がない名前です。しかし、コマンドは定義済みの名前を持ち、異なる機能セットを同じCLIアプリケーションにグループ化するために使用されます。
コマンドまたはサブコマンド¶
CLIプログラムを「コマンド」と呼ぶのが一般的です。
しかし、これらのプログラムの1つにサブコマンドがある場合、それらのサブコマンドも単に「コマンド」と呼ばれることがよくあります。
混乱しないように、それを覚えておいてください。
ここでは、PythonでTyperを使用して構築しているプログラムを指すために **CLIアプリケーション** または **プログラム** を使用し、プログラムのこれらの「サブコマンド」の1つを指すために **コマンド** を使用します。
明示的なアプリケーション¶
複数のコマンド/サブコマンドを持つCLIアプリケーションを作成する前に、明示的な `typer.Typer()` アプリケーションを作成する方法を理解する必要があります。
*CLIオプション* と *CLI引数* のチュートリアルでは、単一の関数を作成し、その関数を `typer.run()` に渡す方法を見てきました。
例えば
import typer
def main(name: str):
print(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)
しかし、それは実際にはショートカットです。内部的には、**Typer** はそれを `typer.Typer()` を使用したCLIアプリケーションに変換して実行します。すべて `typer.run()` の内部で行われます。
同じことを実現する、より明示的な方法もあります
import typer
app = typer.Typer()
@app.command()
def main(name: str):
print(f"Hello {name}")
if __name__ == "__main__":
app()
`typer.run()` を使用する場合、**Typer** は上記とほぼ同じことを行います。
- 新しい `typer.Typer()`「アプリケーション」を作成します。
- 関数を使用して新しい「`command`」を作成します。
- 「`app()`」のように、同じ「アプリケーション」を関数として呼び出します。
`@decorator` 情報
Pythonの `@something` 構文は「デコレータ」と呼ばれます。
関数の先頭に配置します。きれいな装飾的な帽子のように(おそらくこれが用語の由来でしょう)。
「デコレータ」は下の関数を取り、それで何かをします。
この場合、このデコレータは **Typer** に、下の関数が「`command`」であることを伝えます。
`typer.run()` を使用する方法と明示的なアプリケーションを作成する方法の両方で、ほぼ同じことが実現できます。
ヒント
ユースケースが `typer.run()` のみで解決される場合は、それで問題ありません。明示的な `app` を作成して `@app.command()` などを使用する必要はありません。
アプリに追加機能が必要になった場合は、後でそうすることができますが、まだ必要ない場合は問題ありません。
明示的な `app` を使用した2番目の例を実行すると、まったく同じように動作します
// Without a CLI argument
$ python main.py
Usage: main.py [OPTIONS] NAME
Try "main.py --help" for help.
Error: Missing argument 'NAME'.
// With the NAME CLI argument
$ python main.py Camila
Hello Camila
// Asking for help
$ python main.py --help
Usage: main.py [OPTIONS] NAME
Options:
--install-completion Install completion for the current shell.
--show-completion Show completion for the current shell, to copy it or customize the installation.
--help Show this message and exit.
CLIアプリケーションの補完¶
ここで注意すべき小さな詳細があります。
これで、ヘルプに2つの新しい *CLIオプション* が表示されます
--install-completion
--show-completion
シェル/タブ補完を取得するには、ユーザーがインストールして **直接呼び出す** ことのできるパッケージをビルドする必要があります。
そのため、次のようなPythonスクリプトを実行する代わりに
$ python main.py
✨ Some magic here ✨
...次のように呼び出されます
$ magic-app
✨ Some magic here ✨
そのようなスタンドアロンプログラムを使用すると、シェル/タブ補完を設定できます。
そのようなインストール可能なパッケージを作成できるようにするための最初のステップは、明示的な `typer.Typer()` アプリを使用することです。
後で、スタンドアロンCLIアプリケーションを作成し、パッケージをビルドするためのすべてのプロセスを学ぶことができます。
しかし今のところ、あなたがその道にいることを知っていれば十分です。 😎
複数のコマンドを持つCLIアプリケーション¶
複数のコマンド/サブコマンドを持つCLIアプリケーションに戻ると、**Typer** を使用すると、複数のコマンドを持つCLIアプリケーションを作成できます。
明示的な `typer.Typer()` アプリケーションを作成して1つのコマンドを追加する方法がわかったので、複数のコマンドを追加する方法を見てみましょう。
ユーザーを管理するためのCLIアプリケーションがあるとしましょう。
ユーザーを `create` するコマンドと、ユーザーを `delete` するコマンドがあります。
最初に、1つの定義済みユーザーのみを作成および削除できるとしましょう
import typer
app = typer.Typer()
@app.command()
def create():
print("Creating user: Hiro Hamada")
@app.command()
def delete():
print("Deleting user: Hiro Hamada")
if __name__ == "__main__":
app()
これで、`create` と `delete` の2つのコマンドを持つCLIアプリケーションができました
// Check the help
$ python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
Options:
--install-completion Install completion for the current shell.
--show-completion Show completion for the current shell, to copy it or customize the installation.
--help Show this message and exit.
Commands:
create
delete
// Test them
$ python main.py create
Creating user: Hiro Hamada
$ python main.py delete
Deleting user: Hiro Hamada
// Now we have 2 commands! 🎉
ヘルプテキストには、2つのコマンド `create` と `delete` が表示されることに注意してください。
ヒント
デフォルトでは、コマンドの名前は関数名から生成されます。
コマンドが指定されていない場合はヘルプメッセージを表示する¶
デフォルトでは、コマンドのヘルプページを取得するには `--help` を指定する必要があります。
ただし、`typer.Typer()` アプリケーションを定義するときに `no_args_is_help=True` を設定することにより、引数が指定されていない場合は常にヘルプ関数が表示されます
import typer
app = typer.Typer(no_args_is_help=True)
@app.command()
def create():
print("Creating user: Hiro Hamada")
@app.command()
def delete():
print("Deleting user: Hiro Hamada")
if __name__ == "__main__":
app()
これで、これを実行できます
// Check the help without having to type --help
$ python main.py
Usage: main.py [OPTIONS] COMMAND [ARGS]...
Options:
--install-completion Install completion for the current shell.
--show-completion Show completion for the current shell, to copy it or customize the installation.
--help Show this message and exit.
Commands:
create
delete
Clickグループ¶
Clickから来た場合、サブコマンドを持つ `typer.Typer` アプリは、Clickグループとほぼ同等です。
技術的な詳細
`typer.Typer` アプリはClickグループ *ではありません* が、同等の機能を提供します。そして、それを呼び出すときにClickグループを作成します。
**Typer** はコード内の関数を変更して別のタイプのオブジェクトに変換しないため、直接グループではありません。関数を登録するだけです。
デコレータの技術的な詳細¶
`@app.command()` を使用すると、デコレータの下にある関数が **Typer** アプリケーションに登録され、後でアプリケーションによって使用されます。
しかし、Typerはその関数自体を変更しません。関数はそのまま残されます。
つまり、関数が `typer.Option()` や `typer.Argument()` を使用せずに作成できるほど単純な場合は、両方のデコレータを上に配置するか、同様のトリックを使用して、**Typer** アプリケーションと **FastAPI** アプリケーションに同じ関数を使用できます。
Clickの技術的な詳細
この動作はClickとの設計の違いです。
Clickでは、`@click.command()` デコレータを追加すると、実際には下の関数が変更され、オブジェクトに置き換えられます。