آنچه در این مقاله میخوانید [پنهانسازی]
اگر میخواهید در کوتاهترین زمان یک ابزار خط فرمان قابل اتکا بسازید، آموزش Typer در پایتون دقیقا همان چیزی است که نیاز دارید: Typer با تایپهینتها (type hints) کار میکند، سینتکس سادهای دارد و به شکل طبیعی گزینهها و آرگومانها، راهنما، اعتبارسنجی و تکمیل خودکار را فراهم میکند. در این راهنما با یک مثال عملی از نصب تا ساخت چندفرمانی، تست، بستهبندی و نصب به صورت دستور سیستمی پیش میرویم.
پیش نیازها و نصب
برای شروع فقط به پایتون و pip نیاز دارید. پیشنهاد میشود در یک محیط مجازی کار کنید.
python -m venv .venv
source .venv/bin/activate # در ویندوز: .venv\Scripts\activate
pip install "typer[all]"بسته "[all]" امکان تکمیل خودکار و امکانات تعاملی را فعال میکند.
اولین CLI در 5 دقیقه
هدف: یک دستور ساده برای سلام کردن بسازیم که نام را بگیرد و بنا بر گزینهها خروجی را تنظیم کند.
import typer
app = typer.Typer(help="ابزار نمونه برای نمایش Typer")
@app.command()
def hello(
name: str = typer.Argument(..., help="نام مخاطب"),
formal: bool = typer.Option(False, "--formal", "-f", help="سلام رسمی"),
):
"""نمایش پیام خوشامدگویی."""
if formal:
typer.echo(f"درود بر شما، {name}. خوش آمدید.")
else:
typer.echo(f"سلام {name}!")
if __name__ == "__main__":
app()اجرا و بررسی راهنما:
python app.py --help
python app.py hello --help
python app.py hello سارا
python app.py hello سارا --formalآرگومانها، گزینهها و تایپها
Typer تایپها را از annotation های پایتون میخواند و اعتبارسنجی میکند. مثال زیر متریکها را جمع میکند و خروجی را به شکل مناسب نمایش میدهد.
from typing import Optional
import typer
app = typer.Typer()
@app.command()
def add(
a: float = typer.Argument(..., help="شماره اول"),
b: float = typer.Argument(..., help="شماره دوم"),
round_: Optional[int] = typer.Option(None, "--round", "-r", min=0, help="گرد کردن تا n رقم اعشار"),
):
result = a + b
if round_ is not None:
result = round(result, round_)
typer.secho(f"نتیجه: {result}", fg=typer.colors.GREEN)
if __name__ == "__main__":
app()نکات مهم:
- typer.Argument برای مقادیر ضروری موقعیتدار استفاده میشود.
- typer.Option برای گزینههای اختیاری با نامهای بلند/کوتاه استفاده میشود.
- با تعیین نوع (float، int، bool و…) تبدیل و پیام خطای مناسب به طور خودکار انجام میشود.
- برای گزینههای بولی میتوانید از flag استفاده کنید:
debug: bool = typer.Option(False, "--debug/--no-debug")
ساخت چند فرمانی و گروهبندی
برای پروژههای واقعی، بهتر است فرمانها را گروهبندی کنید. در Typer میتوانید چند Typer بسازید و آنها را به برنامه اصلی اضافه کنید.
import typer
app = typer.Typer(help="مدیریت کارها (Todo)")
todo_app = typer.Typer(help="فرمانهای مربوط به کارها")
user_app = typer.Typer(help="فرمانهای مربوط به کاربر")
@todo_app.command("add")
def add_task(title: str = typer.Argument(..., help="عنوان کار"), priority: int = typer.Option(3, "--priority", "-p", min=1, max=5)):
typer.echo(f"کار '{title}' با اولویت {priority} افزوده شد.")
@todo_app.command("list")
def list_tasks(done: bool = typer.Option(False, "--done/--pending", help="فقط کارهای انجامشده یا انجامنشده")):
status = "انجامشده" if done else "باز"
typer.echo(f"فهرست کارهای {status}... (اینجا از دیتابیس/فایل میخوانید)")
@user_app.command("login")
def login(username: str = typer.Option(..., prompt=True), password: str = typer.Option(..., prompt=True, hide_input=True)):
typer.echo(f"ورود کاربر {username} موفق بود.")
app.add_typer(todo_app, name="todo")
app.add_typer(user_app, name="user")
if __name__ == "__main__":
app()نمونه اجرا:
python cli.py todo add "مطالعه Typer" --priority 4
python cli.py todo list --done
python cli.py user loginورودی تعاملی، محیط و مقادیر پیشفرض
برای دریافت ورودی در زمان اجرا، از prompt ها استفاده کنید. برای خواندن از متغیر محیطی، envvar را تعیین کنید.
import os
import typer
app = typer.Typer()
@app.command()
def config(
api_key: str = typer.Option(
...,
"--api-key",
prompt=True,
hide_input=True,
help="کلید API",
envvar="MYAPP_API_KEY",
),
):
# اگر کاربر تعیین نکند، از MYAPP_API_KEY خوانده میشود.
typer.echo(f"کلید تنظیم شد: {'*' * len(api_key)}")
if __name__ == "__main__":
app()مدیریت خطا و خروجی مناسب
برای خطاهای قابل پیشبینی از BadParameter، Abort یا Exit استفاده کنید تا پیام دوستانه و کد خروج درست برگردانده شود.
import typer
app = typer.Typer()
def ensure_even(value: int):
if value % 2 != 0:
raise typer.BadParameter("عدد باید زوج باشد.")
return value
@app.command()
def process(n: int = typer.Argument(..., callback=ensure_even)):
try:
typer.echo(f"در حال پردازش مقدار زوج: {n}")
# منطق پردازش...
except Exception as e:
typer.secho(f"خطای غیرمنتظره: {e}", fg=typer.colors.RED)
raise typer.Exit(code=1)
if __name__ == "__main__":
app()تست کردن CLI با Typer
برای اطمینان از رفتار درست، از تستر داخلی Typer (بر پایه Click) استفاده کنید.
# test_cli.py
from typer.testing import CliRunner
import app # ماژولی که در آن app = typer.Typer() تعریف شده
runner = CliRunner()
def test_hello():
result = runner.invoke(app.app, ["hello", "سارا"])
assert result.exit_code == 0
assert "سلام سارا!" in result.stdout
def test_even_required():
result = runner.invoke(app.app, ["process", "3"])
assert result.exit_code != 0
assert "عدد باید زوج باشد" in result.stdoutاجرا:
pytest -qبستهبندی و ساخت دستور سیستمی
برای اینکه CLI شما بدون python app.py اجرا شود، آن را در قالب پکیج نصبپذیر منتشر کنید. با pyproject.toml میتوانید اسکریپت سیستمی بسازید.
# pyproject.toml (بخشهای مرتبط)
[project]
name = "mycli"
version = "0.1.0"
description = "A demo CLI with Typer"
readme = "README.md"
requires-python = ">=3.8"
dependencies = ["typer[all]"]
[project.scripts]
mycli = "mycli.cli:app"ساختار فایلها:
mycli/
mycli/
__init__.py
cli.py # شامل app = typer.Typer() و دستورات
pyproject.tomlنصب محلی:
pip install -e . # نصب editable در حالت توسعه
mycli --help # حالا دستور در سیستم قابل اجراستتکمیل خودکار (Autocomplete)
اگر Typer را با extras نصب کرده باشید، دستورات زیر معمولا در برنامه شما فعال هستند:
mycli --install-completion # نصب اسکریپت تکمیل خودکار برای شل جاری
mycli --show-completion # نمایش اسکریپت تکمیل برای افزودن دستیپس از نصب، با زدن Tab هنگام نوشتن گزینهها و زیرفرمانها، پیشنهادها نمایش داده میشود.
یک سناریوی کامل: Todo CLI کوچک
در این بخش یک Todo CLI ساده با فایل JSON میسازیم. هدف: نمایش ذخیرهسازی، افزودن، لیست و اتمام کار.
# file: todo.py
import json
from pathlib import Path
from typing import List
import typer
app = typer.Typer(help="مدیریت ساده کارها")
DB_PATH = Path("todos.json")
def load() -> List[dict]:
if not DB_PATH.exists():
return []
return json.loads(DB_PATH.read_text(encoding="utf-8"))
def save(items: List[dict]) -> None:
DB_PATH.write_text(json.dumps(items, ensure_ascii=False, indent=2), encoding="utf-8")
@app.command()
def add(title: str = typer.Argument(..., help="عنوان کار")):
items = load()
items.append({"title": title, "done": False})
save(items)
typer.secho("کار افزوده شد.", fg=typer.colors.GREEN)
@app.command("list")
def list_():
items = load()
if not items:
typer.echo("فهرست خالی است.")
raise typer.Exit()
for i, it in enumerate(items, start=1):
status = "✓" if it["done"] else "•"
typer.echo(f"{i}. {status} {it['title']}")
@app.command()
def done(index: int = typer.Argument(..., min=1, help="شماره کار (از 1 شروع میشود)")):
items = load()
if index <= 0 or index > len(items):
raise typer.BadParameter("شماره کار معتبر نیست.")
items[index - 1]["done"] = True
save(items)
typer.secho("به عنوان انجامشده علامت خورد.", fg=typer.colors.GREEN)
if __name__ == "__main__":
app()بررسی سریع:
python todo.py add "مرور PRها"
python todo.py add "نوشتن تستها"
python todo.py list
python todo.py done 1
python todo.py listنکات کاربردی و خطاهای رایج
- فراموشی تایپهینت: بدون تعیین نوع، اعتبارسنجی خودکار ضعیف میشود. همیشه نوع پارامترها را مشخص کنید.
- نامگذاری گزینهها: برای گزینههای پرکاربرد نام کوتاه هم تعیین کنید (مثل -p). نامهای طولانی توصیفی باشند.
- پیامهای خطا: از typer.BadParameter برای خطاهای ورودی استفاده کنید تا خروجی –help تمیز بماند و کد خروج مناسب برگردد.
- تست CLI: از typer.testing.CliRunner استفاده کنید؛ تست کردن فقط منطق نیست، قرارداد رابط کاربری را نیز پایدار نگه میدارد.
- ساختار پروژه: برای توسعهپذیری، هر گروه فرمان را در ماژول جداگانه نگه دارید و با app.add_typer جمع کنید.
- عملیات طولانی: برای تجربه بهتر از نوار پیشرفت استفاده کنید:
with typer.progressbar(items) as bar: ...
چه زمانی گزینه دیگری مناسب است؟
اگر به کمترین وابستگی و کنترل دستی روی پارسینگ نیاز دارید، argparse کفایت میکند. اما اگر میخواهید با حداقل کد، راهنمای خودکار، تایپهینت، زیرگروهها و تجربه کاربری بهتر داشته باشید، Typer انتخاب مناسبی است.
گام بعدی چیست؟
یک CLI کوچک واقعی در پروژهتان اضافه کنید: یکی از اسکریپتهای تکراری را به Typer منتقل کنید، برای آن –help کامل بنویسید، چند تست پایه اضافه کنید و سپس با pyproject آن را به یک دستور سیستمی تبدیل کنید. همین چرخه کوچک، استاندارد توسعه ابزارهای خط فرمان شما را بالا میبرد.







