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:
#!/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:
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:
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:
Quality checks - Fast linting, formatting, type checking (<2 minutes)
Matrix testing - Python 3.11, 3.12, 3.13 across platforms
Security scanning - Vulnerability detection with bandit and safety
Package building - Wheel and source distribution creation
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 implementationsource_code/pipelines/templates/pyproject.toml- Modern project configurationsource_code/pipelines/examples/test_comprehensive.py- Testing patternssource_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)