Python CLI Apps

Build production-ready command-line applications with modern Python tools and comprehensive CI/CD pipelines.

Modern CLI Development Stack

Essential Tools (2024):

  • uv - Ultra-fast package manager (10-100x faster than pip)

  • Typer - Modern CLI framework with type hints and auto-validation

  • Rich - Beautiful terminal output with colors and formatting

  • pytest - Comprehensive testing with fixtures and coverage

  • ruff - Fast linting and formatting (replaces flake8/black/isort)

  • mypy - Static type checking for catching bugs early

Production CLI Example

Complete Weather CLI Application:

Professional CLI with Typer, Rich, and proper error handling
#!/usr/bin/env python3
"""
Production-ready CLI application demonstrating modern Python practices.

Uses Typer for CLI framework, Rich for beautiful output, and follows
enterprise patterns for error handling, configuration, and testing.
"""
import os
import sys
from enum import Enum
from pathlib import Path
from typing import Optional, Dict, Any

import typer
import requests
from rich.console import Console
from rich.table import Table
from rich.progress import Progress, SpinnerColumn, TextColumn

# Initialize CLI app and console for beautiful output
app = typer.Typer(
    name="weather", 
    help="🌤️ Production-ready weather CLI application"
)
console = Console()


class TemperatureUnit(str, Enum):
    """Temperature unit enumeration for type safety."""
    celsius = "celsius"                               # Metric system
    fahrenheit = "fahrenheit"                         # Imperial system
    kelvin = "kelvin"                                 # Scientific scale


class WeatherError(Exception):
    """Custom exception for weather-related errors."""
    pass


class WeatherService:
    """
    Weather data service with proper error handling and caching.
    
    Demonstrates production patterns like configuration management,
    error handling, and external API integration.
    """
    
    def __init__(self, api_key: Optional[str] = None):
        self.api_key = api_key or os.getenv("OPENWEATHER_API_KEY")
        self.base_url = "http://api.openweathermap.org/data/2.5"

Project Setup:

# Initialize project with modern tooling
uv init weather-cli --python 3.12
cd weather-cli

# Add dependencies
uv add typer rich requests pydantic
uv add --dev pytest pytest-cov ruff mypy bandit

Modern Project Structure:

weather-cli/
├── src/weather_cli/          # Source code (src layout)
│   ├── __init__.py
│   ├── main.py              # CLI entry point
│   └── weather.py           # Core logic
├── tests/                   # Test suite
├── .github/workflows/       # CI/CD pipelines
├── pyproject.toml          # All configuration
└── uv.lock                 # Dependency lockfile

Key Implementation Features

Type-Safe CLI with Typer:

from enum import Enum
import typer
from rich.console import Console

class Units(str, Enum):
    celsius = "celsius"
    fahrenheit = "fahrenheit"

@app.command()
def current(
    city: str = typer.Argument(..., help="City name"),
    units: Units = typer.Option(Units.celsius),
    debug: bool = typer.Option(False)
) -> None:
    """Get current weather with type validation."""

Error Handling Pattern:

try:
    weather_data = service.get_current_weather(city)
except WeatherError as e:
    console.print(f"❌ {e}", style="red")
    raise typer.Exit(1)  # Proper exit codes

Comprehensive Testing

CLI Testing with Typer and pytest:

CLI testing patterns with mocking and fixtures
    
    def test_version_flag(self, runner):
        """Test version information display."""
        result = runner.invoke(app, ["--version"])       # Check version flag
        
        assert result.exit_code == 0                     # Successful exit
        assert "Weather CLI v1.0.0" in result.stdout    # Version displayed
    
    @patch('weather_cli.main.WeatherService')
    def test_current_command_success(self, mock_service_class, runner):
        """Test successful weather retrieval via CLI."""
        # Mock the service instance and its method
        mock_service = Mock()                            # Create mock service
        mock_service.get_current_weather.return_value = { # Mock return data
            "city": "London",
            "country": "GB",
            "temperature": 20.0,
            "feels_like": 22.0,
            "humidity": 65,
            "pressure": 1013,
            "description": "Clear Sky",
            "unit_symbol": "°C"
        }
        mock_service_class.return_value = mock_service   # Return mock instance
        
        result = runner.invoke(app, ["current", "London"])  # Run current command
        
        assert result.exit_code == 0                     # Successful execution
        assert "London" in result.stdout                 # City in output
        assert "20.0°C" in result.stdout                 # Temperature displayed
        mock_service.get_current_weather.assert_called_once_with("London", "celsius")
    
    @patch('weather_cli.main.WeatherService')
    def test_current_command_with_units(self, mock_service_class, runner):
        """Test weather command with different temperature units."""
        mock_service = Mock()
        mock_service.get_current_weather.return_value = {
            "city": "New York", 
            "country": "US",
            "temperature": 68.0,
            "feels_like": 70.0,
            "humidity": 60,
            "pressure": 1015,
            "description": "Sunny",
            "unit_symbol": "°F"
        }
        mock_service_class.return_value = mock_service
        
        # Test fahrenheit unit
        result = runner.invoke(app, [
            "current", "New York", "--unit", "fahrenheit"

Key Testing Patterns:

  • CLI Runner - Simulate command execution without subprocess overhead

  • Mock external APIs - Isolate CLI logic from network dependencies

  • Parameterized tests - Test multiple scenarios efficiently

  • Fixture-based setup - Reusable test data and configurations

  • Output validation - Test both content and formatting

Production CI/CD Pipeline

Multi-Stage Pipeline with Security:

Complete pipeline with quality gates, testing, and security
env:
  PYTHON_VERSION: "3.12"                             # Default Python version

jobs:
  # Stage 1: Fast quality checks (fail fast principle)
  quality:
    runs-on: ubuntu-latest
    outputs:
      cache-hit: ${{ steps.cache.outputs.cache-hit }}
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4                     # Get repository code
      
      - name: Set up Python ${{ env.PYTHON_VERSION }}
        uses: actions/setup-python@v5                 # Install Python
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      
      - name: Install uv (modern package manager)
        uses: astral-sh/setup-uv@v3                   # Fast dependency management
        with:
          enable-cache: true                          # Cache for speed
          cache-dependency-glob: "pyproject.toml"
      
      - name: Cache dependencies
        id: cache                                     # Cache Python packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/uv
          key: ${{ runner.os }}-uv-${{ hashFiles('pyproject.toml') }}
          restore-keys: ${{ runner.os }}-uv-
      
      - name: Install dependencies
        run: |
          uv sync --dev                               # Install all dependencies
      
      - name: Code formatting check
        run: |
          uv run ruff format --check .               # Check code formatting
      
      - name: Linting with modern tools
        run: |
          uv run ruff check .                        # Fast linting
      
      - name: Type checking
        run: |
          uv run mypy src/ tests/                    # Static type analysis

  # Stage 2: Comprehensive testing across Python versions

Pipeline Stages:

  1. Quality checks - Fast linting, formatting, type checking (<2 minutes)

  2. Matrix testing - Python 3.11, 3.12, 3.13 across platforms

  3. Security scanning - Vulnerability detection with bandit and safety

  4. Package building - Wheel and source distribution creation

  5. Deployment - Staging and production environment promotion

Automated Distribution

PyPI Release Pipeline:

# Automated release on version tags
on:
  push:
    tags: ['v*']

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v3
      - run: uv build
      - uses: pypa/gh-action-pypi-publish@release/v1

User Installation:

# Install from PyPI
pip install weather-cli

# Use immediately
weather current London --units celsius

Complete Examples

Full Source Code Available:

  • source_code/pipelines/advanced/weather_cli.py - Complete CLI implementation

  • source_code/pipelines/templates/pyproject.toml - Modern project configuration

  • source_code/pipelines/examples/test_comprehensive.py - Testing patterns

  • source_code/pipelines/advanced/production_ci_cd.yml - Production pipeline

Key Patterns:

  • Type-safe CLI interfaces with Typer

  • Professional error handling and user experience

  • Comprehensive testing with mocking and fixtures

  • Multi-stage CI/CD with security and deployment

  • Modern Python tooling (uv, ruff, mypy, bandit)