Build Modern Python CLI Tools with Typer [Complete Guide]

Python Terminal Command Line Interface Tool Typer Rich

Command Line Interfaces (CLIs) are the primary way software developers and DevOps engineers interact with build automation scripts, deployment pipelines, database management tools, and server utilities. While Python's built-in argparse module is powerful, constructing complex multi-command CLIs requires dozens of lines of repetitive boilerplate code.

Typer, created by the author of FastAPI, lets you build professional, user-friendly command-line tools using standard Python type hints. It automatically generates help menus, validates arguments, supports automatic shell completion (Bash, Zsh, PowerShell), and pairs with Rich for terminal tables and progress bars.


Python CLI Frameworks Comparison


Prerequisites & Installation

Installing typer[all] includes Click and Rich for colored terminal output and automatic shell completion.

pip install "typer[all]" rich
FrameworkSyntax ParadigmType Annotation SupportAuto Shell CompletionBest Use Case
TyperType Hints + Function DecoratorsNative (Python 3.10+)Built-in (Bash, Zsh, Fish, PowerShell)Modern developer tools, deployment utilities
ClickExplicit Parameter DecoratorsManual decoratorsBuilt-in via shell scriptsEnterprise CLI suites requiring legacy support
argparse (Built-in)Imperative parser configurationManual type parsingRequires external plugins (argcomplete)Zero-dependency standard library scripts

Building a Multi-Command CLI Application with Typer and Rich

In Typer, commands are defined as standard Python functions decorated with @app.command(). Function arguments automatically become CLI positional arguments, while default values become command-line flags.

Integrating Rich adds colored output, styled ASCII tables, and status spinners.


Complete Typer CLI Application Script

import typer
from rich.console import Console
from rich.table import Table
import time

app = typer.Typer(help="iLab Academy DevOps Automation Suite")
console = Console()

@app.command()
def scan(
    host: str = typer.Argument("localhost", help="Host IP or domain name to scan"),
    port_range: str = typer.Option("1-100", "--ports", "-p", help="Port range in format 'start-end'"),
    verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable detailed debug logs")
):
    """Scan a target server for open ports and render a styled report."""
    console.print(f"[bold blue]Initiating network scan on:[/bold blue] {host} (Ports: {port_range})")
    
    if verbose:
        console.print("[dim]Debug: Resolving DNS records and testing socket reachability...[/dim]")
        
    table = Table(title=f"Port Security Status - {host}")
    table.add_column("Port", justify="right", style="cyan")
    table.add_column("Service Protocol", style="magenta")
    table.add_column("Status", style="green")

    table.add_row("80", "HTTP Web Server", "OPEN")
    table.add_row("443", "HTTPS Secure Web", "OPEN")
    table.add_row("22", "SSH Remote Access", "FILTERED")
    table.add_row("3306", "MySQL Database", "CLOSED")

    console.print(table)
    console.print("[bold green]Scan completed successfully.[/bold green]")

@app.command()
def deploy(
    environment: str = typer.Option("staging", "--env", "-e", help="Target deployment environment (staging/production)"),
    dry_run: bool = typer.Option(False, "--dry-run", help="Simulate deployment without modifying cloud resources")
):
    """Deploy application container images to target cloud cluster."""
    console.print(f"[bold yellow]Preparing deployment for environment:[/bold yellow] {environment.upper()}")
    if dry_run:
        console.print("[italic cyan]Dry run mode active: Zero cloud resources altered.[/italic cyan]")
    else:
        with console.status("[bold green]Deploying Kubernetes pods...", spinner="dots"):
            time.sleep(1.5)
        console.print("[bold green]Deployment verified and healthy![/bold green]")

if __name__ == "__main__":
    app()

Packaging and Installing CLI Tools Globally

  • Register Console Scripts: Define entry points in your pyproject.toml ([project.scripts] ilab-cli = 'my_package.main:app') so users can run your tool directly from the terminal without typing python -m.
  • Shell Autocompletion: Run ilab-cli --install-completion in your shell to enable Tab-completion for all arguments and flags.
  • Error Exit Codes: Raise typer.Exit(code=1) when an operation fails to signal error states properly to CI/CD bash pipelines.

Frequently Asked Questions

Q: How do I prompt users for interactive input or passwords in Typer?
A: Use typer.prompt('Enter your username') or typer.prompt('Enter password', hide_input=True) to securely capture user input in the terminal.

Q: Can Typer handle nested subcommands?
A: Yes. You can nest multiple Typer instances using app.add_typer(sub_app, name='users') to build complex multi-level CLI applications like git or docker.

Post a Comment

0 Comments