Typerの使い方

この記事ではTyperというPythonモジュールを紹介します。TyperはCLIアプリケーションを簡単に作るための便利なライブラリです。

Typerを使うとargparseなどで実装するよりも効率的にCLIアプリケーションを作成することができます。例えば以下の例では、自分で実装した関数をTyperでコマンド化しました。Typerにコマンド化したい関数を与えるだけで、関数の引数に対応するコマンドライン引数NAMEやヘルプ機能が自動生成されました。

$ python3 sample1.py
Usage: sample1.py [OPTIONS] NAME
Try 'sample1.py --help' for help.

Error: Missing argument 'NAME'.

$ python3 sample1.py World
Hello World!
目次

インストール

pipコマンドでインストール可能です。この記事の執筆時点では、ライセンスはMITです。

$ pip install typer

基本的な使い方

typer.runメソッドに関数を与えるとコマンドが自動生成されます。

import typer

def hello(name: str):
    typer.echo(f"Hello {name}!")

if __name__ == "__main__":
    typer.run(hello)
$ python3 sample1.py
Usage: sample1.py [OPTIONS] NAME
Try 'sample1.py --help' for help.

Error: Missing argument 'NAME'.

$ python3 sample1.py --help
Usage: sample1.py [OPTIONS] NAME

Arguments:
  NAME  [required]

Options:
  --install-completion [bash|zsh|fish|powershell|pwsh]
                                  Install completion for the specified shell.
  --show-completion [bash|zsh|fish|powershell|pwsh]
                                  Show completion for the specified shell, to
                                  copy it or customize the installation.
  --help                          Show this message and exit.

$ python3 sample1.py World
Hello World!

または、デコレータを使って複数のサブコマンドを登録することも可能です。

import typer

app = typer.Typer()

@app.command()  # hello関数をhelloコマンドとして登録
def hello(name: str):
    typer.echo(f"Hello {name}!")

@app.command("bye")  # goodbye関数をbyeコマンドとして登録
def goodbye(name: str):
    typer.echo(f"Bye {name}!")

if __name__ == "__main__":
    app()
$ python3 sample2.py
Usage: sample2.py [OPTIONS] COMMAND [ARGS]...
Try 'sample2.py --help' for help.

Error: Missing command.

$ python3 sample2.py --help
Usage: sample2.py [OPTIONS] COMMAND [ARGS]...

Options:
  --install-completion [bash|zsh|fish|powershell|pwsh]
                                  Install completion for the specified shell.
  --show-completion [bash|zsh|fish|powershell|pwsh]
                                  Show completion for the specified shell, to
                                  copy it or customize the installation.
  --help                          Show this message and exit.

Commands:
  bye
  hello

$ python3 sample2.py bye --help
Usage: sample2.py bye [OPTIONS] NAME

Arguments:
  NAME  [required]

Options:
  --help  Show this message and exit.

$ python3 sample2.py hello --help
Usage: sample2.py hello [OPTIONS] NAME

Arguments:
  NAME  [required]

Options:
  --help  Show this message and exit.

$ python3 sample2.py hello World
Hello World!

詳細な使い方

関数の引数とCLI引数の対応

関数の引数に対応するそれぞれのCLI引数が、位置引数かオプション引数か、また必須引数か任意引数かは、関数の引数のデフォルト値によって制御します。

デフォルト値位置かオプションか必須か任意か
typer.Argument(...)位置必須
typer.Argument(デフォルト値)位置任意
typer.Option(...)オプション必須
typer.Option(デフォルト値)キーワード任意
なし位置必須
typer.Argument typer.Option以外の値オプション任意

以上を踏まえたサンプルコードを以下に例示します。

from typing import Optional

import typer


def main(
    first_name: str,  # 位置・必須
    last_name: Optional[str] = typer.Argument(None, help="the last name"),  # 位置・任意
    formal: bool = False,  # オプション・任意
    age: int = typer.Option(...),  # オプション・必須
    title: Optional[str] = typer.Option(None),  # オプション・任意
):
    """Say hello!"""
    if formal:
        full_name = first_name
        if last_name is not None:
            full_name = full_name + f" {last_name}"
        if title is not None:
            full_name = f"{title} " + full_name
        typer.echo(
            f"Hello {full_name} ({age})."
        )
    else:
        typer.echo(f"Hi {first_name}!")


if __name__ == "__main__":
    typer.run(main)
$ python3 sample3.py
Usage: sample3.py [OPTIONS] FIRST_NAME [LAST_NAME]
Try 'sample3.py --help' for help.

Error: Missing argument 'FIRST_NAME'.

$ python3 sample3.py --help
Usage: sample3.py [OPTIONS] FIRST_NAME [LAST_NAME]

  Say hello!

Arguments:
  FIRST_NAME   [required]
  [LAST_NAME]  the last name

Options:
  --formal / --no-formal          [default: no-formal]
  --age INTEGER                   [required]
  --title TEXT
  --install-completion [bash|zsh|fish|powershell|pwsh]
                                  Install completion for the specified shell.
  --show-completion [bash|zsh|fish|powershell|pwsh]
                                  Show completion for the specified shell, to
                                  copy it or customize the installation.
  --help                          Show this message and exit.

$ python3 sample3.py John
Usage: sample3.py [OPTIONS] FIRST_NAME [LAST_NAME]
Try 'sample3.py --help' for help.

Error: Missing option '--age'.

$ python3 sample3.py John --age 20
Hi John!

$ python3 sample3.py John --age 20 --formal
Hello John (20).

$ python3 sample3.py John Doe --age 20 --formal
Hello John Doe (20).

$ python3 sample3.py John --age 20 --formal Doe
Hello John Doe (20).

$ python3 sample3.py John --age 20 --formal Doe --title Mr.
Hello Mr. John Doe (20).

オプションは位置引数の合間や後ろに挿入しても正しくパースしてくれるようです。

なお、bool型のオプション引数はTrueのフラグ(--formal)とFalseのフラグ(--no-formal)が両方追加されます。関数のdocstringはヘルプメッセージにコマンドの説明として追加されます。引数のヘルプメッセージはtyper.Argumenttyper.Optionhelp引数で設定できます。

サブコマンドの階層化

typer.Typer.add_typerメソッドでサブコマンドを階層化することができます。

import typer

app_greet = typer.Typer()

@app_greet.command()
def hello(name: str):
    typer.echo(f"Hello {name}!")

@app_greet.command()
def goodbye(name: str):
    typer.echo(f"Bye {name}!")

app_calc = typer.Typer()

@app_calc.command()
def add(a: float, b: float):
    typer.echo(a + b)

@app_calc.command()
def sub(a: float, b: float):
    typer.echo(a - b)

app = typer.Typer()
app.add_typer(app_greet, name="greet")
app.add_typer(app_calc, name="calc")

if __name__ == "__main__":
    app()
$ python3 sample4.py --help
Usage: sample4.py [OPTIONS] COMMAND [ARGS]...

Options:
  --install-completion [bash|zsh|fish|powershell|pwsh]
                                  Install completion for the specified shell.
  --show-completion [bash|zsh|fish|powershell|pwsh]
                                  Show completion for the specified shell, to
                                  copy it or customize the installation.
  --help                          Show this message and exit.

Commands:
  calc
  greet

$ python3 sample4.py calc --help
Usage: sample4.py calc [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  add
  sub

$ python3 sample4.py calc add 1 2
3.0

CLI引数のデフォルト値に環境変数を参照

Argumentに環境変数名を指定すると、デフォルト値として環境変数の値を参照させることができます。

import typer


def main(
    username: str = typer.Argument(..., envvar="LOGNAME"),
):
    typer.echo(f"Hello {username}!")


if __name__ == "__main__":
    typer.run(main)
$ python3 sample5.py --help
Usage: sample5.py [OPTIONS] USERNAME

Arguments:
  USERNAME  [env var: LOGNAME;required]

Options:
  --install-completion [bash|zsh|fish|powershell|pwsh]
                                  Install completion for the specified shell.
  --show-completion [bash|zsh|fish|powershell|pwsh]
                                  Show completion for the specified shell, to
                                  copy it or customize the installation.
  --help                          Show this message and exit.

$ printenv | grep LOGNAME
LOGNAME=author

$ python3 sample5.py
Hello author!

$ python3 sample5.py John
Hello John!

なお、環境変数が定義されていない場合はCLI引数の不足で実行が中断されます。環境変数が未定義で、かつコマンドで引数が与えられていない場合のデフォルト値を設定したい場合は、環境変数を参照しない場合と同じくtyper.Argumentの第一引数に何らかの値を指定します。

サブコマンド共通の前処理と引数・オプション

callbackデコレータを利用すると、各サブコマンドに共通の前処理や引数・オプションを追加することができます。

import typer

app = typer.Typer()

state = {"verbose": False, "name": None}

@app.command()
def create():
    if state["verbose"]:
        name = state["name"]
        typer.echo(f"Creating user: {name}")


@app.command()
def delete():
    if state["verbose"]:
        name = state["name"]
        typer.echo(f"Deleting user: {name}")


@app.callback()
def callback(name: str, verbose: bool = False):
    state["name"] = name
    state["verbose"] = verbose
    if state["verbose"]:
        typer.echo("Verbose output ON")


if __name__ == "__main__":
    app()
$ python3 sample6.py --help
Usage: sample6.py [OPTIONS] NAME COMMAND [ARGS]...

Arguments:
  NAME  [required]

Options:
  --verbose / --no-verbose        [default: no-verbose]
  --install-completion [bash|zsh|fish|powershell|pwsh]
                                  Install completion for the specified shell.
  --show-completion [bash|zsh|fish|powershell|pwsh]
                                  Show completion for the specified shell, to
                                  copy it or customize the installation.
  --help                          Show this message and exit.

Commands:
  create
  delete

$ python3 sample6.py John create
$ python3 sample6.py --verbose John create
Verbose output ON
Creating user: John

$ python3 sample6.py --verbose John delete
Verbose output ON
Deleting user: John

引数のコールバック

ArgumentOptioncallback引数にコールバック関数を指定することで、引数ごとにコールバックを設定することができます。引数のバリデーションなどに使用することができます。

import typer


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


def main(name: str = typer.Argument(..., callback=name_callback)):
    typer.echo(f"Hello {name}.")


if __name__ == "__main__":
    typer.run(main)
$ python3 sample7.py
Usage: sample7.py [OPTIONS] NAME
Try 'sample7.py --help' for help.

Error: Missing argument 'NAME'.

$ python3 sample7.py John
Validating param: name
Usage: sample7.py [OPTIONS] NAME
Try 'sample7.py --help' for help.

Error: Invalid value for 'NAME': Only Camila is allowed

$ python3 sample7.py Camila
Validating param: name
Hello Camila.

Click由来の機能

この節ではClickから流用されている機能をいくつか紹介します。Typer独自の関数でラップされているわけではなく、typerモジュールの直下にClickの関数が直接インポートされています。

文字列の着色や装飾

typer.style関数を利用すると文字を装飾することができます。

なおWindows環境ではcoloramaモジュールのインストールが必要とのことです。著者のmacOS環境では不要でした。

import typer

def hello(name: str):
    name_enc = typer.style(
        name,
        fg=typer.colors.GREEN,  # 文字色
        bg=typer.colors.WHITE,  # 背景色
        bold=True,  # 太字にするか
        blink=True,  # 点滅するか
        underline=True,  # 下線を引くか
    )

    print([name_enc])
    #> ['\x1b[32m\x1b[1m\x1b[4m\x1b[5mWorld\x1b[0m']

    typer.echo(f"Hello {name_enc}!")

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

(文字だけではスタイルの変化が表せないため実行結果は省略させてください)

第一引数にエコーする文字列を取り、第二引数以降にstyleと同様の装飾を指定できるtyper.sechoメソッドというものもあります。

標準エラー出力

echo関数のerr引数にTrueを指定すると出力先を標準エラー出力に切り替えられます。

エディタ・ブラウザの呼び出し

typer.edit関数でエディタを呼び出すことができます。

また、typer.launch関数にURLを与えるとブラウザでWebページを開くことができます。

バージョン情報

この記事のコードは以下のバージョンのライブラリで検証しました。

  • typer==0.4.0
  • click==8.0.4(typer==0.4.0から自動でインストールされたバージョン)

類似のライブラリ

Typerの類似ライブラリにはClickPython Fireなどが存在します。各ライブラリの違いは(筆者の認識では)以下の通りです。

  • Click:CLIアプリケーションを開発するためのライブラリ。コマンドライン引数のパース、文字の装飾、プログレスバーの利用、エディタやページャーの呼び出しなど、様々な機能を包含します。
  • Typer:Clickをラップしたライブラリ。FastAPIと同じ作者のライブラリで、pydanticやFastAPIのように型アノテーション機能を利用した効率化が図られています。
  • Python Fire:CLIコマンドを作るためのライブラリ。チェイン可能なコマンドを効率的に作るなどの場合はこちらの方が便利です。一方このライブラリ自体には文字の装飾などの出力側の機能は含まれません。

公式によるTyperの類似ツールの説明記事も参考になります。また、Python Fireについては以下の記事でも紹介しています。こちらも是非ご覧ください。

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

ITベンチャーでデータ分析、AI開発、システム設計、提案、営業、組織管理、公演、採用などなど多数の役割に従事してきました。

様々な職業や背景の方々と交流するうちに、幅広い分野で問題を解決したり価値を生み出したりするためには、個別の知識だけでなく、汎用的に物事を考える力を伸ばしていく必要があると考えるようになりました。

更に、自分自身の考える力だけでなく、より多くの人々の考える力のトレーニングを応援することで、社会全体を良くしていけるのではないかと考えて、このサイトを作りました。

目次
閉じる