تخطَّ إلى المحتوى

Typer Cheat Sheet

Overview

Typer is a Python library for building CLI applications based on Python type hints. Created by Sebastian Ramirez (the author of FastAPI), Typer uses the same principles of leveraging standard Python type annotations to automatically generate CLI argument parsing, validation, help text, and shell completion. It is built on top of Click.

Typer converts Python functions into CLI commands automatically. Function parameters become CLI arguments or options based on their type hints and default values. It provides automatic help generation, input validation, error messages, and tab completion with minimal boilerplate code.

Installation

pip install typer

# With all extras (rich output, shell completion)
pip install "typer[all]"

# Verify
python -c "import typer; print(typer.__version__)"

Basic Usage

Simple CLI

# main.py
import typer

app = typer.Typer()

@app.command()
def hello(name: str):
    """Greet someone by name."""
    print(f"Hello {name}!")

@app.command()
def goodbye(name: str, formal: bool = False):
    """Say goodbye."""
    if formal:
        print(f"Goodbye, {name}. Have a great day.")
    else:
        print(f"Bye {name}!")

if __name__ == "__main__":
    app()
python main.py hello World
python main.py goodbye Alice --formal
python main.py --help

Single Command App

import typer

def main(name: str, age: int = 25, verbose: bool = False):
    """Process user information."""
    if verbose:
        print(f"Processing: {name}, age {age}")
    print(f"Hello {name}, you are {age} years old!")

if __name__ == "__main__":
    typer.run(main)

Arguments and Options

Type-Based Parameters

import typer
from typing import Optional
from pathlib import Path

app = typer.Typer()

@app.command()
def process(
    # Required argument
    name: str,
    # Optional argument with default
    count: int = 1,
    # Boolean flag (--verbose / --no-verbose)
    verbose: bool = False,
    # Optional value
    nickname: Optional[str] = None,
    # Path argument
    config: Path = Path("config.yaml"),
    # Float with range
    score: float = typer.Option(0.0, min=0.0, max=100.0),
):
    """Process data with various parameters."""
    for i in range(count):
        print(f"[{i+1}] Processing {name}")
    if nickname:
        print(f"AKA: {nickname}")
import typer
from typing import Annotated

app = typer.Typer()

@app.command()
def deploy(
    env: Annotated[str, typer.Argument(help="Target environment")],
    tag: Annotated[str, typer.Option("--tag", "-t", help="Image tag")] = "latest",
    dry_run: Annotated[bool, typer.Option("--dry-run", "-n", help="Preview only")] = False,
    replicas: Annotated[int, typer.Option(min=1, max=10)] = 3,
):
    """Deploy application to target environment."""
    action = "Would deploy" if dry_run else "Deploying"
    print(f"{action} {tag} to {env} with {replicas} replicas")

if __name__ == "__main__":
    app()

Enums and Choices

from enum import Enum
import typer

class Environment(str, Enum):
    dev = "dev"
    staging = "staging"
    production = "production"

class LogLevel(str, Enum):
    debug = "debug"
    info = "info"
    warning = "warning"
    error = "error"

app = typer.Typer()

@app.command()
def deploy(
    env: Environment = Environment.dev,
    log_level: LogLevel = LogLevel.info,
):
    print(f"Deploying to {env.value} with log level {log_level.value}")

Subcommands and Groups

import typer

app = typer.Typer(help="My application CLI")
users_app = typer.Typer(help="User management")
db_app = typer.Typer(help="Database operations")

app.add_typer(users_app, name="users")
app.add_typer(db_app, name="db")

@users_app.command("list")
def list_users():
    """List all users."""
    print("User list...")

@users_app.command("create")
def create_user(name: str, email: str):
    """Create a new user."""
    print(f"Created user: {name} ({email})")

@db_app.command("migrate")
def migrate(revision: str = "head"):
    """Run database migrations."""
    print(f"Migrating to {revision}")

@db_app.command("seed")
def seed(count: int = 100):
    """Seed database with test data."""
    print(f"Seeding {count} records")

if __name__ == "__main__":
    app()
python main.py users list
python main.py users create --name Alice --email alice@example.com
python main.py db migrate --revision abc123

Configuration

Callbacks and Context

import typer

app = typer.Typer()
state = {"verbose": False}

@app.callback()
def main(verbose: bool = False):
    """My CLI application."""
    if verbose:
        print("Verbose mode enabled")
    state["verbose"] = verbose

@app.command()
def run(name: str):
    if state["verbose"]:
        print(f"Debug: processing {name}")
    print(f"Running {name}")

Prompts and Confirmation

import typer

@app.command()
def delete(
    name: str,
    force: bool = typer.Option(False, "--force", "-f"),
):
    """Delete a resource."""
    if not force:
        confirm = typer.confirm(f"Delete {name}?")
        if not confirm:
            print("Cancelled")
            raise typer.Abort()
    print(f"Deleted {name}")

@app.command()
def login():
    username = typer.prompt("Username")
    password = typer.prompt("Password", hide_input=True)
    print(f"Logged in as {username}")

Rich Output

import typer
from rich.console import Console
from rich.table import Table
from rich.progress import track

console = Console()

@app.command()
def status():
    """Show service status."""
    table = Table(title="Services")
    table.add_column("Name", style="cyan")
    table.add_column("Status", style="green")
    table.add_column("Port")

    table.add_row("API", "Running", "8080")
    table.add_row("Worker", "Running", "N/A")
    table.add_row("DB", "Running", "5432")

    console.print(table)

@app.command()
def process(count: int = 100):
    """Process items with progress bar."""
    for item in track(range(count), description="Processing..."):
        pass  # Do work
    console.print("[bold green]Complete![/bold green]")

Advanced Usage

Shell Completion

# Generate completion script
typer main.py utils install-completion

# Bash
typer main.py utils install-completion --shell bash

# Zsh
typer main.py utils install-completion --shell zsh

Testing Typer Apps

from typer.testing import CliRunner
from myapp import app

runner = CliRunner()

def test_hello():
    result = runner.invoke(app, ["hello", "World"])
    assert result.exit_code == 0
    assert "Hello World" in result.output

def test_deploy():
    result = runner.invoke(app, ["deploy", "staging", "--tag", "v2"])
    assert result.exit_code == 0

def test_deploy_dry_run():
    result = runner.invoke(app, ["deploy", "prod", "--dry-run"])
    assert "Would deploy" in result.output

File Arguments

import typer
from pathlib import Path

@app.command()
def read(
    file: Path = typer.Argument(..., exists=True, readable=True),
    output: Path = typer.Option("output.txt", writable=True),
):
    content = file.read_text()
    output.write_text(content.upper())
    print(f"Processed {file} -> {output}")

Troubleshooting

IssueSolution
--help shows wrong typesVerify type hints on function parameters
Optional not workingUse Optional[str] = None pattern
Bool flags invertedUse --flag / --no-flag or explicit Option
Completion not workingRun typer utils install-completion
Rich not renderingInstall with pip install "typer[all]"
# Show help
python main.py --help
python main.py deploy --help

# Shell completion
python main.py --install-completion

# Run with typer CLI
typer main.py run deploy staging