コンテンツにスキップ

CLIオプションコールバックとコンテキスト

ターミナルから受信した値を使用して実行される、特定の_CLIパラメータ_(_CLIオプション_または_CLI引数_)のカスタムロジックが必要になる場合があります。

そのような場合は、_CLIパラメータ_コールバック関数を使用できます。

_CLIパラメータ_の検証

たとえば、残りのコードが実行される前に、いくつかの検証を行うことができます。

from typing import Optional

import typer
from typing_extensions import Annotated


def name_callback(value: str):
    if value != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return value


def main(name: Annotated[Optional[str], typer.Option(callback=name_callback)] = None):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

ヒント

可能な場合は、Annotatedバージョンを使用することをお勧めします。

from typing import Optional

import typer


def name_callback(value: str):
    if value != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return value


def main(name: Optional[str] = typer.Option(default=None, callback=name_callback)):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

ここでは、キーワード引数`callback`を使用して、`typer.Option()`または`typer.Argument()`に関数を渡します。

関数はコマンドラインから値を受け取ります。 それで何でもでき、値を返します。

この場合、`--name`が`Camila`でない場合、`typer.BadParameter()`例外を発生させます。

`BadParameter`例外は特殊で、それを生成したパラメータとともにエラーを表示します。

確認してください

$ python main.py --name Camila

Hello Camila

$ python main.py --name Rick

Usage: main.py [OPTIONS]

// We get the error from the callback
Error: Invalid value for '--name': Only Camila is allowed

補完の処理

コールバックと補完には、いくつかの特別な処理が必要な点に注意する必要があります。

しかし、最初にシェル(Bash、Zsh、Fish、またはPowerShell)で補完を使用してみましょう。

補完をインストールした後(独自のPythonパッケージの場合)、CLIプログラムを使用して_CLIオプション_を`--`で追加し始め、TABを押すと、シェルは使用可能な_CLIオプション_を表示します(_CLI引数_なども同様です)。

前のスクリプトで簡単に確認するには、`typer`コマンドを使用します

// Hit the TAB key in your keyboard below where you see the: [TAB]
$ typer ./main.py [TAB][TAB]

// Depending on your terminal/shell you will get some completion like this ✨
run    -- Run the provided Typer app.
utils  -- Extra utility commands for Typer apps.

// Then try with "run" and --help
$ typer ./main.py run --help

// You get a help text with your CLI options as you normally would
Usage: typer run [OPTIONS]

  Run the provided Typer app.

Options:
  --name TEXT  [required]
  --help       Show this message and exit.

// Then try completion with your program
$ typer ./main.py run --[TAB][TAB]

// You get completion for CLI options
--help  -- Show this message and exit.
--name

// And you can run it as if it was with Python directly
$ typer ./main.py run --name Camila

Hello Camila

シェルの補完の仕組み

内部的には、シェル/ターミナルは特別な環境変数(現在の_CLIパラメータ_などを保持)を使用してCLIプログラムを呼び出し、CLIプログラムはシェルが補完の提示に使用する特別な値を出力します。 このすべては、Typerによって舞台裏で処理されます。

しかし、主な**重要な点**は、すべてがシェルが読み取るプログラムによって出力された値に基づいていることです。

コールバックでの補完の停止

コールバックが実行されているときに、名前を検証していることを示すメッセージを表示したいとしましょう

from typing import Optional

import typer
from typing_extensions import Annotated


def name_callback(value: str):
    print("Validating name")
    if value != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return value


def main(name: Annotated[Optional[str], typer.Option(callback=name_callback)] = None):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

ヒント

可能な場合は、Annotatedバージョンを使用することをお勧めします。

from typing import Optional

import typer


def name_callback(value: str):
    print("Validating name")
    if value != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return value


def main(name: Optional[str] = typer.Option(default=None, callback=name_callback)):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

コールバックは、シェルが補完を求めてプログラムを呼び出すときに呼び出されるため、そのメッセージ「名前の検証中」が出力され、補完が中断されます。

次のようになります

// Run it normally
$ typer ./main.py run --name Camila

// See the extra message "Validating name"
Validating name
Hello Camila

$ typer ./main.py run --[TAB][TAB]

// Some weird broken error message ⛔️
(eval):1: command not found: Validating
rutyper ./main.pyed Typer app.

補完の修正 - Contextの使用

Typerアプリケーションを作成すると、背後ではClickが使用されます。

すべてのClickアプリケーションには、「コンテキスト」と呼ばれる特別なオブジェクトがあり、通常は非表示になっています。 "コンテキスト"

ただし、`typer.Context`型の関数パラメータを宣言することで、コンテキストにアクセスできます。

「コンテキスト」には、プログラムの現在の実行に関する追加データが含まれています

from typing import Optional

import typer
from typing_extensions import Annotated


def name_callback(ctx: typer.Context, value: str):
    if ctx.resilient_parsing:
        return
    print("Validating name")
    if value != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return value


def main(name: Annotated[Optional[str], typer.Option(callback=name_callback)] = None):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

ヒント

可能な場合は、Annotatedバージョンを使用することをお勧めします。

from typing import Optional

import typer


def name_callback(ctx: typer.Context, value: str):
    if ctx.resilient_parsing:
        return
    print("Validating name")
    if value != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return value


def main(name: Optional[str] = typer.Option(default=None, callback=name_callback)):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

補完を処理する場合、`ctx.resilient_parsing`は`True`になります。そのため、他に何も出力せずに返すことができます。

ただし、プログラムを通常どおり呼び出すと、`False`になります。 したがって、前のコードの実行を続行できます。

これで補完を修正するために必要なすべてです。🚀

確認してください

$ typer ./main.py run --[TAB][TAB]

// Now it works correctly 🎉
--help  -- Show this message and exit.
--name

// And you can call it normally
$ typer ./main.py run --name Camila

Validating name
Hello Camila

`CallbackParam`オブジェクトの使用

値を持つ関数パラメータを宣言することで`typer.Context`にアクセスできるのと同様に、`typer.CallbackParam`型の別の関数パラメータを宣言して、特定のClick `Parameter`オブジェクトを取得できます。

from typing import Optional

import typer
from typing_extensions import Annotated


def name_callback(ctx: typer.Context, param: typer.CallbackParam, value: str):
    if ctx.resilient_parsing:
        return
    print(f"Validating param: {param.name}")
    if value != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return value


def main(name: Annotated[Optional[str], typer.Option(callback=name_callback)] = None):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

ヒント

可能な場合は、Annotatedバージョンを使用することをお勧めします。

from typing import Optional

import typer


def name_callback(ctx: typer.Context, param: typer.CallbackParam, value: str):
    if ctx.resilient_parsing:
        return
    print(f"Validating param: {param.name}")
    if value != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return value


def main(name: Optional[str] = typer.Option(default=None, callback=name_callback)):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

あまり一般的ではないかもしれませんが、必要に応じて行うことができます。

たとえば、複数の_CLIパラメータ_で使用できるコールバックがある場合、コールバックは毎回どのパラメータであるかを知ることができます。

確認してください

$ python main.py --name Camila

Validating param: name
Hello Camila

技術的な詳細

標準のPython型アノテーションに基づいてコールバック関数で関連データを取得するため、エディターで型チェックと自動補完を無料で行うことができます。

そして、**Typer**はあなたが望む関数パラメータを取得することを保証します。

それらの名前、順序などを気にする必要はありません。

標準のPython型に基づいているため、「**ただ動作します**」。 ✨

Clickの`Parameter`

`typer.CallbackParam`は実際にはClickの`Parameter`のサブクラスであるため、エディターですべての正しい補完を取得できます。

型アノテーション付きコールバック

各型の関数パラメータを宣言するだけで、`typer.Context`と`typer.CallbackParam`を取得できます。

順序は関係ありません。関数パラメータの名前は関係ありません。

`typer.Context`を取得せずに`typer.CallbackParam`のみを取得することも、その逆も可能です。それでも機能します。

`value`関数パラメータ

コールバックの`value`関数パラメータは、任意の名前(例:`lastname`)と任意の型を持つことができますが、メイン関数と同じ型アノテーションを持つ必要があります。それが受け取るものだからです。

型を宣言しないことも可能です。それでも機能します。

`value`パラメータをまったく宣言せずに、たとえば`typer.Context`のみを取得することも可能です。それも機能します。