Python自動化 #7 自分だけのコマンドを作る — typerとrichでCLI化
スクリプトが 1〜2 個のうちは問題ありません。ところが第 6 回まで追いかけてきて、フォルダ整理、Excel レポート、スクレイピング、メール送信のスクリプトがたまってくると、新しい問題が生まれます。「あれ、どうやって実行するんだっけ」です。あるスクリプトは引数が 3 つで、あるスクリプトは特定のフォルダでしか動きません。シリーズ最終回では、この散らばったスクリプトたちを 1 つの CLI ツールにまとめ、ターミナルのどこからでも一言で呼べるコマンドにします。
argparseからtyperへ #
#1 では標準ライブラリの argparse で引数を受け取りました。parser.add_argument("folder") のように引数 1 つごとに宣言を 1 行ずつ積む方式で、ちゃんと動きますが、インターフェイスが大きくなるほど宣言部が長くなります。typer は同じ仕事を型ヒントで解決します。関数シグネチャがそのまま CLI 定義です。
from pathlib import Path
import typer
def organize(folder: Path, dry_run: bool = False):
"""ダウンロードフォルダを拡張子別に整理します。"""
print(f"{folder} 整理開始 (dry_run={dry_run})")
if __name__ == "__main__":
typer.run(organize)宣言コードが別にないのに、typer がシグネチャから全部読み取ります。folder: Path は必須引数になって入力値が Path オブジェクトへ自動変換され、dry_run: bool = False は --dry-run フラグになり、docstring はヘルプの説明文になります。
uv add typer rich で 2 つのライブラリをインストールした後、uv run cli.py ~/Downloads --dry-run のようにすぐ実行できます。
サブコマンドでまとめる #
本当の価値はここからです。typer.Typer() オブジェクトに関数を複数登録すると、git のようにサブコマンドを持つツールになります。#1〜#6 で作ったスクリプトたちを関数 1 つずつに移して 1 つのツールに集め、サブコマンドで呼びます。
from pathlib import Path
import typer
app = typer.Typer(help="自分の自動化ツール集")
@app.command()
def organize(folder: Path, dry_run: bool = False):
"""ダウンロードフォルダを拡張子別に整理します。"""
...
@app.command()
def scrape(url: str, output: Path = Path("result.csv")):
"""掲示板の記事一覧を収集して CSV に保存します。"""
...
@app.command()
def report(month: str, send: bool = False):
"""月間 Excel レポートを作り、--send ならメールで送信します。"""
...
if __name__ == "__main__":
app()uv run mytools/cli.py organize ~/Downloads --dry-run
uv run mytools/cli.py scrape https://example.com/board
uv run mytools/cli.py report 2026-07 --sendこれでコマンド体系ができます。スクリプトファイル 7 個を覚える代わりに、ツール 1 つとサブコマンド名だけ覚えればよくなります。
–helpがタダで付いてくるということ #
typer でまとめると、ヘルプを別に書く必要がありません。
自分の自動化ツール集
Commands:
organize ダウンロードフォルダを拡張子別に整理します。
scrape 掲示板の記事一覧を収集して CSV に保存します。
report 月間 Excel レポートを作り、--send ならメールで送信します。この画面の価値は 3 ヶ月後の自分が証明します。スクリプト時代はコードを開いて引数を読み直す必要がありましたが、これからは --help 1 回で、何があってどう使うのかをツール自身が教えてくれます。サブコマンドごとに organize --help も別に生成されます。
richで見やすく #
typer と一緒にインストールした rich はターミナル出力を担当します。自動化ツールで特に役立つのは進捗バーと表です。organize コマンドの本体をこう書き換えます。
from rich.console import Console
from rich.progress import track
from rich.table import Table
console = Console()
counts: dict[str, int] = {}
for file in track(list(folder.iterdir()), description="整理中..."):
ext = file.suffix.lstrip(".") or "その他"
counts[ext] = counts.get(ext, 0) + 1 # 移動ロジックは第1回のまま
table = Table(title="整理結果")
table.add_column("拡張子")
table.add_column("件数", justify="right")
for ext, count in sorted(counts.items()):
table.add_row(ext, str(count))
console.print(table)track() で包むだけでループに進捗バーが付き、Table は結果を整列した表で出力します。数百個のファイルを移す間、画面が固まったように見える問題が消え、終わったときに何をどれだけ処理したかが一目で分かります。console.print("[red]失敗[/red]") のように色を付けるのも 1 行です。
どこからでも呼べるコマンドとしてインストールする #
今はプロジェクトフォルダで uv run mytools/cli.py ... と呼ぶ必要がありますが、本物のツールならどのディレクトリからでも mytools の一言で起動するべきです。pyproject.toml の [project.scripts] がそのつなぎ役です。
[project]
name = "mytools"
version = "0.1.0"
dependencies = ["typer", "rich"]
[project.scripts]
mytools = "mytools.cli:app"
[build-system]
requires = ["uv_build"]
build-backend = "uv_build"mytools = "mytools.cli:app" は「mytools というコマンドが来たら mytools/cli.py の app オブジェクトを実行せよ」という宣言です。これでツールとしてインストールします。
uv tool install .
mytools organize ~/Downloadsuv tool install は隔離された専用環境にパッケージをインストールし、コマンドだけを PATH に公開します。ほかのプロジェクトの仮想環境と混ざらず、どのフォルダからでも呼び出せます。uv を使わない環境なら pipx install . が同じ役割を果たします。
チームに配るのも難しくありません。社内 PyPI サーバーがなくても git リポジトリさえあれば、同僚は uv tool install git+https://github.com/our-team/mytools.git の 1 行で同じツールをインストールし、uv tool upgrade mytools で更新します。スクリプトファイルをメッセンジャーでやり取りしていた方式と比べると、バージョン管理と依存関係のインストールが全部自動で解決されるわけです。
シリーズを終えて #
7 本を 1 行ずつ振り返ります。
- #1 ファイルを移す最初のスクリプトで、手作業をコードに移しました
- #2 openpyxl で Excel の繰り返し作業を終わらせました
- #3 httpx と BeautifulSoup で静的ページを収集しました
- #4 Playwright で JavaScript レンダリングのページまで扱いました
- #5 結果をメールとメッセンジャー通知で受け取れるようにしました
- #6 スケジューラに載せて人がいなくても回るようにしました
- #7 全部を 1 つの CLI ツールに包んで、コマンド 1 つにまとめました
振り返ると、ツールは脇役にすぎず、自動化の出発点は繰り返しを発見する目でした。毎週同じフォルダを整理していること、毎月同じ表をコピーしていることに気づく瞬間が始まりです。その繰り返しを 1 つずつコードに移していくうちに、いつの間にか今回のような自分だけのツールボックスができあがります。そしてツールが大きくなると、次の質問が付いてきます。「直したら、ほかのコマンドが壊れていないだろうか」です。そのとき必要になるのがテストで、Pythonテスト シリーズが pytest でその答えを扱います。自動化ツールを長く使うつもりなら、次の行き先としておすすめします。