Why Typer?
Typer is a modern library for building CLI applications, created by Sebastián Ramírez (the creator of FastAPI). It leverages Python type hints to automatically generate beautiful command-line interfaces with minimal code.
Key benefits:
- Intuitive: Uses Python type hints - no new syntax to learn
- Automatic help: Generates help messages and documentation automatically
- Scalable: Easy to organize commands into groups for complex applications
- Fast development: Less boilerplate, more functionality
Quick Setup
Install Typer with a single command:
pip install "typer[all]"The [all] option includes extras like rich formatting and shell completion.
Building a Task Manager CLI
Let’s build a practical example: a task manager called taskmaster that handles tasks and projects.
Project Structure
taskmaster/
├── __init__.py
├── __main__.py
├── task.py
└── project.pyStep 1: Main Entry Point
Create main.py to make your CLI runnable as a module:
import typer
app = typer.Typer(help="TaskMaster - Your CLI Task Manager")
# Import command groups
from . import task, project
app.add_typer(task.app, name="task")
app.add_typer(project.app, name="project")
if __name__ == "__main__":
app()Step 2: Task Commands
Create task.py with task-related commands:
import typer
from typing import Optional
from rich.console import Console
from rich.table import Table
app = typer.Typer(help="Manage your tasks")
console = Console()
# Simple in-memory storage (use a database in production)
tasks = []
@app.command()
def create(
title: str,
description: Optional[str] = None,
priority: str = typer.Option("medium", help="Task priority: low, medium, high")
):
"""Create a new task"""
task = {
"id": len(tasks) + 1,
"title": title,
"description": description,
"priority": priority,
"done": False
}
tasks.append(task)
console.print(f"✓ Task created: [bold]{title}[/bold]", style="green")
@app.command()
def list():
"""List all tasks"""
if not tasks:
console.print("No tasks found.", style="yellow")
return
table = Table(title="Your Tasks")
table.add_column("ID", justify="center")
table.add_column("Title", style="cyan")
table.add_column("Priority", justify="center")
table.add_column("Status", justify="center")
for task in tasks:
status = "✓" if task["done"] else "○"
table.add_row(
str(task["id"]),
task["title"],
task["priority"],
status
)
console.print(table)
@app.command()
def complete(task_id: int):
"""Mark a task as complete"""
for task in tasks:
if task["id"] == task_id:
task["done"] = True
console.print(f"✓ Task {task_id} marked complete!", style="green")
return
console.print(f"Task {task_id} not found", style="red")Step 3: Project Commands
Create project.py for project management:
import typer
from rich.console import Console
app = typer.Typer(help="Manage your projects")
console = Console()
projects = []
@app.command()
def create(name: str, description: str = ""):
"""Create a new project"""
project = {
"id": len(projects) + 1,
"name": name,
"description": description
}
projects.append(project)
console.print(f"✓ Project created: [bold]{name}[/bold]", style="green")
@app.command()
def list():
"""List all projects"""
if not projects:
console.print("No projects found.", style="yellow")
return
for project in projects:
console.print(f"[bold cyan]{project['name']}[/bold cyan]")
if project["description"]:
console.print(f" {project['description']}", style="dim")Using Your CLI
Run your CLI as a Python module:
# Create tasks
python -m taskmaster task create "Build the API" --priority high
python -m taskmaster task create "Write documentation"
# List tasks
python -m taskmaster task list
# Complete a task
python -m taskmaster task complete 1
# Create projects
python -m taskmaster project create "Web App" "Main website project"
python -m taskmaster project list
# Get help
python -m taskmaster --help
python -m taskmaster task --helpMaking It Even Better
1. Interactive Prompts
@app.command()
def create_interactive():
"""Create a task interactively"""
title = typer.prompt("Task title")
description = typer.prompt("Description (optional)", default="")
priority = typer.prompt("Priority", default="medium")
# ... create task2. Confirmation Dialogs
@app.command()
def delete(task_id: int):
"""Delete a task"""
if typer.confirm(f"Are you sure you want to delete task {task_id}?"):
# ... delete task
console.print("Task deleted", style="green")3. Progress Bars
from rich.progress import track
import time
@app.command()
def sync():
"""Sync tasks with server"""
for _ in track(range(100), description="Syncing..."):
time.sleep(0.02)
console.print("✓ Sync complete!", style="green")Scalability Best Practices
Tips for Growing Your CLI
- Organize by domain: Group related commands in separate files
- Use type hints: They provide automatic validation and help messages
- Keep commands focused: One command should do one thing well
- Add rich output: Use colors, tables, and progress bars for better UX
- Test your CLI: Typer works great with pytest
Conclusion
Typer makes building CLI applications enjoyable and productive. With its intuitive API based on type hints, you can quickly create professional command-line tools that scale from simple scripts to complex applications.
The key to scalability is organizing commands into logical groups using Typer’s sub-application feature. This keeps your code clean and maintainable as your CLI grows.
Next Steps
- Add persistent storage with SQLite or JSON files
- Implement shell completion for better UX
- Package your CLI with setuptools for easy installation
- Explore Typer’s documentation at typer.tiangolo.com