複雜的Typer CLI 範例
展示一個更複雜的 Typer CLI 範例,結合多個指令、參數、選項、Enum 型別、巢狀子指令,讓你看到 Typer 在大型工具設計上的彈性。
import typer
from pathlib import Path
from enum import Enum
app = typer.Typer(help="檔案管理工具")
class Mode(str, Enum):
copy = "copy"
move = "move"
delete = "delete"
@app.command()
def process(
input_file: Path = typer.Argument(..., help="輸入檔案路徑"),
output_file: Path = typer.Argument(None, help="輸出檔案路徑 (copy/move 模式需要)"),
mode: Mode = typer.Option(Mode.copy, "--mode", "-m", help="處理模式"),
overwrite: bool = typer.Option(False, "--overwrite", "-o", help="是否覆蓋輸出檔案"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="顯示詳細訊息")
):
"""
處理檔案:支援 copy/move/delete。
"""
if verbose:
typer.echo(f"Mode={mode}, Input={input_file}, Output={output_file}, Overwrite={overwrite}")
if not input_file.exists():
typer.echo("❌ Input file does not exist.")
raise typer.Exit(code=1)
if mode in [Mode.copy, Mode.move]:
if output_file is None:
typer.echo("❌ Output file required for copy/move.")
raise typer.Exit(code=1)
if output_file.exists() and not overwrite:
typer.echo("❌ Output file already exists. Use --overwrite.")
raise typer.Exit(code=1)
if mode == Mode.copy:
typer.echo(f"📂 Copying {input_file} → {output_file}")
elif mode == Mode.move:
typer.echo(f"📂 Moving {input_file} → {output_file}")
elif mode == Mode.delete:
typer.echo(f"🗑️ Deleting {input_file}")
# 子指令群組
user_app = typer.Typer(help="使用者管理")
app.add_typer(user_app, name="user")
@user_app.command()
def add(
username: str = typer.Argument(..., help="使用者名稱"),
admin: bool = typer.Option(False, "--admin", help="是否為管理員")
):
typer.echo(f"✅ Added user {username}, admin={admin}")
@user_app.command()
def remove(
username: str = typer.Argument(..., help="要刪除的使用者名稱")
):
typer.echo(f"🗑️ Removed user {username}")
if __name__ == "__main__":
app()
# 檔案處理
python main.py process input.txt output.txt --mode copy --verbose
python main.py process input.txt output.txt --mode move --overwrite
python main.py process input.txt --mode delete
# 使用者管理
python main.py user add Alice --admin
python main.py user remove Bob
Mode 限制選項只能是 copy/move/delete。user add/remove 作為子指令群組。python main.py --help 會顯示完整結構。這樣的結構已經接近「專案級 CLI 工具」的設計。
更完整的 Typer 專案架構範例,我來展示一個「模組化 CLI 專案」的設計方式,適合大型自動化工具或團隊協作。
project/
│── main.py # 入口點,註冊 Typer app
│── commands/
│ │── __init__.py # 匯入子指令
│ │── files.py # 檔案處理相關指令
│ │── users.py # 使用者管理相關指令
│── utils/
│ │── helpers.py # 共用工具函式
│── tests/
│ │── test_cli.py # CLI 測試
import typer
from commands import files, users
app = typer.Typer(help="專案級 CLI 工具")
# 加入子指令群組
app.add_typer(files.app, name="files")
app.add_typer(users.app, name="users")
if __name__ == "__main__":
app()
import typer
from pathlib import Path
app = typer.Typer(help="檔案相關操作")
@app.command()
def copy(
src: Path = typer.Argument(..., help="來源檔案"),
dst: Path = typer.Argument(..., help="目標檔案"),
overwrite: bool = typer.Option(False, "--overwrite", "-o", help="是否覆蓋")
):
if dst.exists() and not overwrite:
typer.echo("❌ 目標檔案已存在,請使用 --overwrite")
raise typer.Exit(code=1)
typer.echo(f"📂 Copy {src} → {dst}")
import typer
app = typer.Typer(help="使用者管理")
@app.command()
def add(
username: str = typer.Argument(..., help="使用者名稱"),
admin: bool = typer.Option(False, "--admin", help="是否為管理員")
):
typer.echo(f"✅ 新增使用者 {username}, admin={admin}")
@app.command()
def remove(username: str):
typer.echo(f"🗑️ 移除使用者 {username}")
from typer.testing import CliRunner
from main import app
runner = CliRunner()
def test_add_user():
result = runner.invoke(app, ["users", "add", "Alice", "--admin"])
assert result.exit_code == 0
assert "新增使用者 Alice" in result.stdout
CliRunner 撰寫 pytest 測試。commands/ 裡加檔案,再在 main.py 註冊即可。這樣的架構非常適合你在團隊裡推廣,因為它結合了 可維護性 與 可測試性。
太好了 🎉
這裡我幫你整理一份 Typer 常見設計模式速查表,讓你在設計 CLI 工具時可以快速套用。
@app.command()
def greet(name: str = typer.Argument(..., help="使用者名稱")):
typer.echo(f"Hello {name}")
... 表示必填@app.command()
def repeat(
text: str,
times: int = typer.Option(1, "--times", "-t", help="重複次數")
):
for _ in range(times):
typer.echo(text)
--times / -t 可選@app.command()
def build(debug: bool = typer.Option(False, "--debug", "-d", help="顯示除錯訊息")):
if debug:
typer.echo("🔍 Debug mode enabled")
--debug 即可from enum import Enum
class Mode(str, Enum):
fast = "fast"
slow = "slow"
@app.command()
def run(mode: Mode = typer.Option(Mode.fast, "--mode", help="執行模式")):
typer.echo(f"Running in {mode} mode")
files_app = typer.Typer(help="檔案操作")
app.add_typer(files_app, name="files")
@files_app.command()
def copy(src: str, dst: str):
typer.echo(f"Copy {src} → {dst}")
python main.py files copy a.txt b.txtimport os
@app.command()
def config(
api_key: str = typer.Option(os.getenv("API_KEY"), "--api-key", help="API 金鑰")
):
typer.echo(f"Using API key: {api_key}")
os.getenv() 讀取環境變數作為預設值from typer.testing import CliRunner
runner = CliRunner()
def test_greet():
result = runner.invoke(app, ["greet", "Alice"])
assert result.exit_code == 0
assert "Hello Alice" in result.stdout
CliRunner 搭配 pytest 測試 CLI把 Typer 設計模式速查表轉成一個 流程圖 / 決策樹,讓你在設計 CLI 工具時能快速判斷該用 Argument 還是 Option,以及何時用 Enum 或旗標。
開始設計 CLI 指令
│
├── 這個參數是否必填?
│ │
│ ├── 是 → 使用 Argument
│ │ └── 需要型別限制嗎?
│ │ ├── 是 → 使用 Enum
│ │ └── 否 → 一般 Argument
│ │
│ └── 否 → 使用 Option
│ └── 這是布林旗標嗎?
│ ├── 是 → Option(bool),只需加上 --flag
│ └── 否 → Option(型別, 預設值)
│
├── 是否需要子指令群組?
│ ├── 是 → 建立 Typer 子 app (app.add_typer)
│ └── 否 → 保持在主 app
│
├── 是否需要環境變數整合?
│ ├── 是 → Option(os.getenv("VAR"))
│ └── 否 → 一般 Option/Argument
│
└── 是否需要測試?
├── 是 → 使用 CliRunner 撰寫 pytest
└── 否 → 可直接執行
typer.Argument(...)typer.Option(default, "--flag")typer.Option(False, "--debug")typer.Option(Mode.fast, "--mode")app.add_typer(sub_app, name="sub")typer.Option(os.getenv("API_KEY"))這樣你在設計 CLI 工具時,只要沿著這個決策樹走,就能快速判斷該用哪種 Typer 語法。
| File7 | File | Last Modified |
|---|---|---|
| 如何在uv 的開發環境下使用Spyder工具 | 如何在uv 的開發環境下使用Spyder工具 | 1:25 AM - December 07, 2025 |
| Python 開發工具前五名詳細比較 | Python 開發工具前五名詳細比較 | 1:25 AM - December 07, 2025 |
| python 讀取某一個檔案日期,然後將檔案日期再設定回原本檔案 | python 讀取某一個檔案日期,然後將檔案日期再設定回原本檔案 | 1:25 AM - December 07, 2025 |
| Python 標準函式庫依照功能分類 | Python 標準函式庫依照功能分類 | 1:25 AM - December 07, 2025 |
| python 中,移除 list 中的某一個元素 | python 中,移除 list 中的某一個元素 | 1:25 AM - December 07, 2025 |
| python list 在最前面插入資料 | python list 在最前面插入資料 | 1:25 AM - December 07, 2025 |
| PyCharm 2025 的試用機制與試用過期後的差異 | PyCharm 2025 的試用機制與試用過期後的差異 | 1:25 AM - December 07, 2025 |