この記事では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.Argument
やtyper.Option
のhelp
引数で設定できます。
サブコマンドの階層化
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
引数のコールバック
Argument
やOption
のcallback
引数にコールバック関数を指定することで、引数ごとにコールバックを設定することができます。引数のバリデーションなどに使用することができます。
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の類似ライブラリにはClickやPython Fireなどが存在します。各ライブラリの違いは(筆者の認識では)以下の通りです。
- Click:CLIアプリケーションを開発するためのライブラリ。コマンドライン引数のパース、文字の装飾、プログレスバーの利用、エディタやページャーの呼び出しなど、様々な機能を包含します。
- Typer:Clickをラップしたライブラリ。FastAPIと同じ作者のライブラリで、pydanticやFastAPIのように型アノテーション機能を利用した効率化が図られています。
- Python Fire:CLIコマンドを作るためのライブラリ。チェイン可能なコマンドを効率的に作るなどの場合はこちらの方が便利です。一方このライブラリ自体には文字の装飾などの出力側の機能は含まれません。
公式によるTyperの類似ツールの説明記事も参考になります。また、Python Fireについては以下の記事でも紹介しています。こちらも是非ご覧ください。