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}")
Annotated Syntax (Recommended)
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
| Issue | Solution |
|---|---|
--help shows wrong types | Verify type hints on function parameters |
| Optional not working | Use Optional[str] = None pattern |
| Bool flags inverted | Use --flag / --no-flag or explicit Option |
| Completion not working | Run typer utils install-completion |
| Rich not rendering | Install 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