1.Typer とは?(CLI フレームワーク)
(1)何をするためのもの?
python app.py … みたいな「コマンドラインツール」を作るための高レベルフレームワーク
- python app.py run –config conf.yml のような「コマンドラインツール」を作るためのもの
- click ベース
- 型ヒント(type hints)で宣言的に書けるのが特徴
(2)導入(インストール)
pip install typer[all]
(3)最小限の実例
import typer
app = typer.Typer() #アプリ本体のオブジェクトを生成
@app.command() #アプリに対してデコレータで関数を追加していく
def hello(name: str = "World"): #CLIコマンドとなり、引数はコマンド引数になる
typer.echo(f"Hello {name}")
if __name__ == "__main__":
app() #appを呼び出す。エントリポイント
実行:
python app.py hello # Hello World
python app.py hello --name Bob # Hello Bob
- @app.command() を付けると「サブコマンド」になる
- 引数・オプションは関数の引数として書く
(4)本格的な Typer
import typer
from pathlib import Path #ファイルパスを扱いやすくする Python 標準ライブラリ
#Typer(help=)で--helpで表示される説明文を設定
app = typer.Typer(help="サンプル CLI ツールです")
@app.command()
def run(
config: Path = typer.Option(..., "--config", "-c", help="設定ファイルのパス"),
debug: bool = typer.Option(False, "--debug", help="デバッグモード"),):
#↑ここまでがrunの引数。Optionで呼び出し時のオプションを設定
#「...」で必須オプション。「config: Path = typer.Option」のように型を明確にすることで、Typerの方で型を整えてくれる
#typer.echoはtyperで使えるprintの代わりの関数
typer.echo(f"config = {config}")
typer.echo(f"debug = {debug}")
@app.command()
def version():
typer.echo("my-tool 1.0.0")
if __name__ == "__main__":
app()
#実行例
python app.py run --config settings.yaml
python app.py version
- 自動で –help が生成される
- 型(Path, bool, int 等)を変えるだけで自動パースしてくれる
2.Typer での設計の考え方
(1)コマンドと「実処理」を分ける
# cli.py
@app.command()
def run(...):
run_core(...)
# core.py
def run_core(...):
# 実際にやりたいこと
Typer は「引数を解析して core 関数に渡す係」に徹させるとキレイです。
(2)コマンドをファイルごとに分割
以下のようにファイルを分割することで巨大なCLIアプリを作るのに有効。
myapp/
cli/
__init__.py
main.py # app = Typer()
run.py # @app.command()
analyze.py # @app.command()
① myapp/
プロジェクトのルート(アプリ全体のフォルダ)。
② cli/
CLI(コマンドラインインタフェース)関連ファイルをまとめるフォルダ。
CLI の「入り口」「コマンド定義」「サブモジュール」をひとまとめにするイメージ。
③ __init__.py
cli パッケージであることを示すファイル
Python に「ここはパッケージ(モジュールの集合)ですよ」と知らせる印。
無くても Python 3.3 以降は動く場合があるがTyperやimportの安定性のために置くのが一般的。
④ main.py(アプリ本体)
- アプリ全体を管理する Typer インスタンスを定義
- 他ファイル(run.py, analyze.py)がここに「コマンドを追加」していく
Typer の「司令塔」だけを書く場所。
# main.py
import typer
app = typer.Typer()
⑤ run.py(1つ目のコマンド)
@app.command() を使ってrun コマンドを main.py の app に登録する。
実際の処理はここに書く。
<例>
# run.py
from .main import app
@app.command()
def run(config: str):
print(f"Running with config: {config}")
⑥ analyze.py(2つ目のコマンド)
⑤と同じ。
<例>
# analyze.py
from .main import app
@app.command()
def analyze(data: str):
print(f"Analyzing {data}")
⑦ この構造のメリット
| メリット | 説明 |
|---|---|
| 大規模化に強い | 1つのファイルにコマンドが集中しない |
| コマンドごとにファイルを分けられる | run.py, analyze.py, export.py など増やし放題 |
| 読みやすく保守しやすい | 実処理とコマンド定義が整理される |
| テストがしやすい | 各コマンドごとに import してテスト可能 |
大体のアプリはこの構造。
