Pythonのclickライブラリを使って、ターミナルから使えるTODO管理CLIツールを作ってみました。add・list・done・deleteの4つのサブコマンドでタスクを登録・確認・完了・削除でき、データはtodo.jsonに保存するシンプルな構成です。実際にコードを書いて動かした結果とあわせて、初心者の方でも追体験できるように解説します。
動画の内容をテキストで確認する
オープニング。clickを使った実践内容を約34秒で紹介します。概要紹介。
この動画では、Pythonのclickで、タスクの追加・一覧・完了・削除ができるTODO管理CLIツールを作ります。具体的にやること。具体的には、clickの@click.groupで4つのサブコマンドを定義し、タスクをtodo.jsonに保存して実行をまたいで状態を保持します。
実装環境・必須アプリ。実装環境と必須アプリは、Windows 11 Pro / Python 3.13.3 / clickです。実際のキャプチャ動画。
実際の画面で操作を確認します。検証の結果、CLIツールを4回のコマンドで実行し、全て正常(終了コード0)に動作しました。エンディング。
研修ならCodeCampで検索。
PythonとclickでTODO管理CLIを作る方針
今回作るのは、ターミナルでtodo add "牛乳を買う"のように打つとタスクを登録できるTODO管理ツールです。追加(add)・一覧(list)・完了(done)・削除(delete)の4つのサブコマンドを用意します。
CLIの骨組みにはclickを採用しました。デコレータを付けるだけで関数がそのままコマンドになるため、標準ライブラリのargparseより記述量が少なく、サブコマンドを持つツールを直感的に組み立てられるのが選定理由です。
採用の裏づけとして、公式ドキュメントも「必要最小限のコードで組み立てられること」をclickの設計思想として掲げています。
データの保存先はカレントディレクトリのtodo.jsonにしました。コマンドを実行するたびに「読み込み→更新→保存」を繰り返す素直な構成なら、データベースを使わなくても状態を持つCLIを最小限のコードで実現できます。
このセクションの用語
- CLI
- Command Line Interfaceの略で、ターミナルに文字でコマンドを打って操作するタイプのプログラムのこと。
- click
- PythonでCLIツールを作るためのライブラリ。関数にデコレータを付けるだけでコマンド化できる。
- サブコマンド
-
git addのaddのように、メインコマンドの後ろに続けて指定する個別の機能名。 - JSON
- データを人間にも読める文字列として保存する形式。Pythonの辞書やリストと相性が良い。
さらに詳しく学べるPython研修の詳細はこちら参考:
©click公式ドキュメント(Pallets Projects)Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary.
Pythonでclickのコードを生成したプロンプトと作り方
この記事のコードはClaude Codeに書かせたもので。以下は、その際に使った指示の内容です。
clickはデコレータでコマンドを定義できる反面、指示があいまいだとLLMは期限や優先度など頼んでいない機能まで盛り込みがちです。そこでサブコマンドを4つに限定し、「だけ実装する」と機能範囲を先に固定しました。
保存形式は動作確認のしやすさから逆算して指定しています。todo.jsonへの保存とensure_ascii=Falseを明記すれば、実行後にファイルを開くだけで日本語タスクの状態変化を目で追えます。
さらにID採番とエラー時の振る舞いも決め打ちにしました。ここを曖昧にすると生成のたびに仕様が揺れて、記事の実行例が再現できなくなるためです。
生成プロンプトで工夫したポイント
-
サブコマンドは次の4つだけ実装する:機能範囲を「だけ」で固定すると、LLMが期限や優先度など頼んでいない機能を足すのを防げます。 -
IDは既存の最大IDに1を足して自動で振る:採番ルールを決め打ちにすることで、doneやdeleteの実行例が何度生成しても同じ結果で再現できるようになります。 -
存在しないIDを指定されたときはエラーで落とさず:異常系の方針を先に与えておくと、例外で止まらない実用的なCLIが最初の生成から出てくるのがポイントです。
同じコードを作れる生成プロンプト(コピペ用・全文)
以下のプロンプトは、そのままコピペして同じものが作れるように整えた全文です。
Python・clickの環境構築(Windows 11/PowerShellで検証)
この記事のセットアップ手順と掲載コードは、Windows 11 Pro、PowerShell 5.1、Python 3.13.3で動作確認しています。仮想環境を有効化せず、その中のPythonを直接指定するため、以下のコマンドはPowerShellとコマンドプロンプト(cmd)の両方で使えます。
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install click
macOS・Linuxでは仮想環境内のPythonパスが異なります。今回の動作確認環境とは異なるため、以下は環境差分を補う参考手順です。
python3 -m venv .venv
./.venv/bin/python -m pip install click
- todo.jsonは実行したフォルダに作成されるため、同じフォルダでコマンドを実行すれば状態が引き継がれます。
- 「No module named click」と出る場合は、仮想環境のPython(.\.venv\Scripts\python.exe)で実行しているか確認してください。
Python clickの@click.groupで4コマンドを実装する
コード全体は約80行で、構成は大きく3つに分かれます。JSONの読み書きを担うload_todos/save_todos、コマンドの入り口になるcliグループ、そして4つのサブコマンド関数です。
どのコマンドも「load_todos()で読み込み→リストを操作→save_todos()で保存→click.echo()で結果表示」という同じ型で書かれています。この型さえ押さえれば、あとから新しいコマンドを追加するのも簡単です。
全行を暗記する必要はありません。次の6か所だけ理解すれば、このツールの仕組みは把握できます。
このセクションの用語
- デコレータ
- 関数定義の上に
@名前と書いて機能を後付けする仕組み。clickではこれでコマンド登録を行う。 - docstring
- 関数の先頭に書く説明用の文字列。clickでは
--help実行時の説明文として利用される。 - 内包表記
-
[t for t in todos if ...]のように、条件を満たす要素からリストを1行で作るPythonの書き方。
"""clickで作るTODO管理CLIツール"""
import json
from pathlib import Path
import click
# 保存先ファイルの設定
TODO_FILE = Path("todo.json")
# TODOデータの読み込み
def load_todos():
if TODO_FILE.exists():
return json.loads(TODO_FILE.read_text(encoding="utf-8"))
return []
# TODOデータの保存
def save_todos(todos):
TODO_FILE.write_text(
json.dumps(todos, ensure_ascii=False, indent=2), encoding="utf-8"
)
# コマンドグループの定義
@click.group()
def cli():
"""clickで作るシンプルなTODO管理CLIツール"""
# タスクの追加(add)
@cli.command()
@click.argument("task")
def add(task):
"""タスクを追加する"""
todos = load_todos()
next_id = max((t["id"] for t in todos), default=0) + 1
todos.append({"id": next_id, "task": task, "done": False})
save_todos(todos)
click.echo(f"追加しました: [{next_id}] {task}")
# タスクの一覧表示(list)
@cli.command("list")
def list_todos():
"""タスクを一覧表示する"""
todos = load_todos()
if not todos:
click.echo("タスクはありません。addコマンドで追加してください。")
return
for t in todos:
mark = "x" if t["done"] else " "
click.echo(f"[{mark}] {t['id']}. {t['task']}")
# タスクの完了(done)
@cli.command()
@click.argument("todo_id", type=int)
def done(todo_id):
"""指定したIDのタスクを完了にする"""
todos = load_todos()
for t in todos:
if t["id"] == todo_id:
t["done"] = True
save_todos(todos)
click.echo(f"完了しました: [{todo_id}] {t['task']}")
return
click.echo(f"ID {todo_id} のタスクが見つかりません。")
# タスクの削除(delete)
@cli.command()
@click.argument("todo_id", type=int)
def delete(todo_id):
"""指定したIDのタスクを削除する"""
todos = load_todos()
remaining = [t for t in todos if t["id"] != todo_id]
if len(remaining) == len(todos):
click.echo(f"ID {todo_id} のタスクが見つかりません。")
return
save_todos(remaining)
click.echo(f"削除しました: ID {todo_id}")
# エントリーポイント
def main():
cli()
if __name__ == "__main__":
main()
コード全文は上の折り畳みに入れてあるので、全部を上から読む必要はありません。ここでは特に重要な部分だけを抜き出して、何をしているのか順番に見ていきます。
load_todosとsave_todosでJSONを読み書き
def load_todos():
if TODO_FILE.exists():
return json.loads(TODO_FILE.read_text(encoding="utf-8"))
return []todo.jsonが存在すれば読み込み、無ければ空リストを返します。初回実行でファイルがまだ無くてもエラーにならないのがポイントです。保存側のsave_todosではensure_ascii=Falseを指定し、日本語がそのまま読める形で書き出しています。
@click.groupでコマンドの親を作る
@click.group()
def cli():
"""clickで作るシンプルなTODO管理CLIツール"""@click.group()を付けたcli関数が、4つのサブコマンドをぶら下げる親になります。中身は空でも動作し、docstringはそのまま--helpの説明文として表示されるので書いておくと親切です。
addコマンドと自動採番の仕組み
@cli.command()
@click.argument("task")
def add(task):
"""タスクを追加する"""
todos = load_todos()
next_id = max((t["id"] for t in todos), default=0) + 1
todos.append({"id": next_id, "task": task, "done": False})@click.argument("task")で受け取った文字列が、そのまま引数taskに渡ってきます。IDは既存の最大値に1を足す自動採番で、max()のdefault=0のおかげでタスクが0件のときも1番から始まります。
@cli.command("list")で名前の衝突を回避
@cli.command("list")
def list_todos():
"""タスクを一覧表示する"""関数名をlistにすると組み込み関数を上書きしてしまうため、関数名はlist_todosにしています。@cli.command("list")と名前を明示すれば、コマンド名だけをlistにできるのがclickの便利なところです。
doneコマンドはtype=intでIDを検証
@cli.command()
@click.argument("todo_id", type=int)
def done(todo_id):
"""指定したIDのタスクを完了にする"""type=intを指定すると、clickが入力を自動で整数に変換してくれます。数値以外を渡した場合は関数が呼ばれる前にclick側がエラーメッセージを出すため、自前の検証コードが不要になるのが利点です。
deleteは内包表記で残すタスクを絞り込む
remaining = [t for t in todos if t["id"] != todo_id]
if len(remaining) == len(todos):
click.echo(f"ID {todo_id} のタスクが見つかりません。")
return
save_todos(remaining)削除は「指定したID以外を残す」という内包表記で実現しています。削除前後で件数が変わらなければ該当IDが存在しなかったと判断でき、存在チェックを別途書かずに済むのが上手い点です。
PythonでTODO管理CLIを動かしてみた結果
実装したCLIツールを、実際に4回のコマンドで動かしてみました。addでタスクを追加し、listで一覧を確認、doneで完了にして、最後にdeleteで削除するという一連の流れです。
4回の実行はすべて終了コード0で正常に完了しています。下のキャプチャが実際のセッションで、コマンドを打つたびにclick.echoのメッセージが返ってくる様子が分かります。
引数の解析やエラーメッセージの整形はclickに任せているので、操作中に迷う場面はありませんでした。約80行のコードでも、日常づかいのTODO管理として十分に機能します。
このセクションの用語
- 終了コード
- プログラムが終了時にOSへ返す数値。0なら正常終了、0以外は何らかの異常を意味する。

Python×clickでよくあるエラーと対処法
同じ構成のCLIを自分で作るときに、初心者が遭遇しやすいエラーを整理しました。clickは親切なエラーメッセージを出してくれるため、メッセージを読めば原因の見当が付くものがほとんどです。
特に多いのはライブラリの入れ忘れと引数まわりの間違いです。下の表の対処を上から順に確認すれば、たいていはすぐ解決できます。
このセクションの用語
- 仮想環境
- プロジェクトごとにライブラリを分けて管理するPythonの仕組み。venvなどのツールで作成する。
| エラー例 | 原因 | 対処 |
|---|---|---|
| ModuleNotFoundError: No module named 'click' | clickが未インストール、または別の仮想環境で実行している |
pip install clickを実行する。仮想環境を使っている場合は有効化してから入れ直す |
| Error: No such command 'lst' | サブコマンド名のタイプミス |
python todo.py --helpでコマンド一覧を確認し、正しい名前で実行し直す |
| Error: Missing argument 'TASK' |
addに引数を付けずに実行した |
add "タスク名"のように引数を渡す。スペースを含むタスクは引用符で囲む |
| Error: Invalid value for 'TODO_ID' |
doneやdeleteに数値以外を渡した |
type=intが入力を検証しているので、listで確認した整数のIDを指定する |
| json.decoder.JSONDecodeError | todo.jsonを手で編集して形式が壊れた | カンマや括弧の対応を直すか、todo.jsonを削除していったん作り直す |
Python click入門でつまずきやすい4つの落とし穴
文法エラーにはならないのに動きが期待と違う、という「静かなハマりどころ」がclickにはいくつかあります。今回の実装で意識した回避策とあわせて紹介します。
このセクションの用語
- 組み込み関数
-
list()やmax()のように、インポートせず最初から使えるPython標準の関数。 - シャドーイング
- 既存の名前と同じ名前の変数や関数を定義して、元の名前を隠してしまうこと。気づきにくいバグの原因になる。
関数名をlistにすると組み込み関数のlist()を上書き(シャドーイング)してしまいます。関数名はlist_todosのままにして、@cli.command("list")でコマンド名だけをlistにするのが安全です。
clickは関数名のアンダースコアをハイフンに変換するため、名前を指定しないとlist_todosのコマンド名はlist-todosになります。意図した名前で呼ばせたいときは@cli.command("名前")で明示しましょう。
todo.jsonはカレントディレクトリに作られるため、実行する場所が変わると別のTODOリストを見ることになります。常に同じデータを使いたい場合は、保存先をホームディレクトリなど固定のパスに変える改造が有効です。
@click.argumentは@cli.command()より下(関数に近い側)に書きます。デコレータの順番を逆にすると引数が正しく登録されず、原因の分かりにくいエラーにつながります。
TODO管理CLIツールが実務で活きる場面
「ターミナルから離れずにタスクを記録できる」「データがただのJSONファイル」という2つの性質は、実務でも応用が利きます。代表的な使い方を表にまとめました。
いずれも今回の約80行のコードをベースに、少しの改造で実現できる範囲です。まずは自分の開発フローに1つ組み込んでみるのがおすすめです。
このセクションの用語
- リポジトリ
- Gitでソースコードを管理する単位となるフォルダのこと。
- シェルスクリプト
- ターミナルで実行する一連のコマンドをファイルにまとめ、自動実行できるようにしたもの。
| 使える場面 | 具体的な使い方 |
|---|---|
| 開発中のメモ・積み残し管理 |
コーディング中に気づいた修正点をtodo addで即記録し、切りのいいタイミングでtodo listを見ながら消化していく |
| プロジェクト単位のTODO共有 | todo.jsonはカレントディレクトリに作られるため、リポジトリごとにタスクを分けて管理できる。Gitにコミットすればチームでの共有も可能 |
| シェルスクリプトとの連携 |
python todo.py add "デプロイ後の動作確認"をデプロイスクリプトの末尾に仕込み、やるべき確認作業を自動でタスク化する |
| 自作CLIツールの雛形 | 「読み込み→更新→保存」の型はそのまま流用できるので、ブックマーク管理や作業ログ記録など、別のCLIを作るときの土台にする |
Pythonのclick製TODO管理CLIまとめ
clickの@click.groupと@cli.commandを組み合わせるだけで、add・list・done・deleteの4コマンドを持つTODO管理CLIを約80行で作れました。実際に実行した4回のコマンドは、すべて終了コード0で正常に動作しています。
「JSONを読み込み→更新→保存」という単純な型と、click.echoに統一した出力のおかげで、初心者でも追いやすいコードになったと思います。組み込み関数との衝突を@cli.command("list")で回避するテクニックも、覚えておいて損はないポイントです。
次のステップとしては、期限や優先度フィールドの追加、完了済みだけを表示する--allのようなオプション対応などが考えられます。まずは本記事のコードを写経して、自分だけのコマンドを1つ足すところから始めてみてください。
参考にした一次情報
- ^ click公式ドキュメント. https://click.palletsprojects.com/, (参照26-07-17).
- ^ json --- JSONエンコーダおよびデコーダ — Python公式ドキュメント. https://docs.python.org/ja/3/library/json.html, (参照26-07-17).
- ^ pathlib --- オブジェクト指向のファイルシステムパス — Python公式ドキュメント. https://docs.python.org/ja/3/library/pathlib.html, (参照26-07-17).
※内容は執筆時点のものです。ライブラリやサイトの仕様は変わる可能性があるため、公式ドキュメントもあわせてご確認ください。
ITやプログラミングに関するコラム
【CSS】notで複数の件を除外する方法
【Git】remote設定を変更する方法
【VBA】コメントアウトを設定する方法
x86とx64の違いを分かりやすく解説
マークダウンで改行する方法
CapsLockキーを解除する方法
GitLabとGitHubの違いを解説
Linuxで環境変数を確認する方法
UbuntuのIPアドレスを確認する方法
パソコンのメモリの目安を用途別に選ぶ方法
ITやプログラミングに関するニュース
VercelがAI GatewayにSeedream 5.0 Proを追加、AI SDKのモデル指定で画像生成と編集が可能に
AWSがAmazon LocationのPlaces APIを強化、住所表記の指定と移動手段別の検索が可能に
VercelがトレースにTree・Waterfallビューを追加、ログ画面で処理の階層と所要時間を確認可能に
Googleがエージェント評価の再考を提唱、難易度を情報量で測るDiscovery Benchを解説
Google CloudがCloud Runサンドボックスを公開プレビューで提供、サービスヘルスは一般提供に
Google Cloud EMEAが英国金融の重要第三者に指定、イングランド銀行・PRA・FCAの直接監督下に
AWS DMS Schema ConversionがSQL Serverのオフライン変換に対応、ソースDBへ接続せずスキーマを変換可能に
EC2 G7インスタンスが米国東部(バージニア北部)で利用可能に、G6比でAI推論性能が最大4.6倍
SageMaker HyperPodが継続プロビジョニングでのAMIベース構成に対応、S3のスクリプト管理なしでSlurmクラスターを作成可能に
AWSがEMR on EKSでSparkトラブルシューティングエージェントに対応、失敗ジョブの原因分析を自然言語で依頼可能に
