【Python中級】Typerの基礎と設計

Python中級

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 してテスト可能

大体のアプリはこの構造。

タイトルとURLをコピーしました