lunes, 26 de enero de 2026

Aprendiendo Python de 0 a experto - Aplicaciones CLI

Aplicaciones CLI en Python
Python // Chapter 15

Aplicaciones CLI

Domina la creación de interfaces de línea de comandos profesionales en Python

Las aplicaciones de línea de comandos (CLI) son fundamentales en el ecosistema de desarrollo. Desde scripts de automatización hasta herramientas de DevOps, las CLIs permiten a los desarrolladores crear interfaces potentes y eficientes para interactuar con sus programas.

En este capítulo exploraremos desde los fundamentos con sys.argv hasta frameworks modernos como argparse, Click y Typer, aprendiendo a crear aplicaciones CLI robustas y profesionales.

MÓDULO_01

Fundamentos de CLI

Una aplicación CLI recibe entrada del usuario a través de argumentos de línea de comandos en lugar de una interfaz gráfica. Python proporciona varias formas de acceder a estos argumentos, desde la más básica con sys.argv hasta frameworks más sofisticados.

¿Qué es sys.argv?

El módulo sys proporciona acceso a argv, una lista que contiene los argumentos pasados al script de Python. El primer elemento siempre es el nombre del script.

Estructura de sys.argv
argv[0]
argv[1]
argv[2]
argv[n]
basic_cli.py
import sys

# sys.argv es una lista con todos los argumentos
print(f"Script name: {sys.argv[0]}")
print(f"Total arguments: {len(sys.argv)}")

# Verificar si se pasaron argumentos
if len(sys.argv) > 1:
    print(f"Arguments: {sys.argv[1:]}")
else:
    print("No arguments provided")
python basic_cli.py hello world 42
Script name: basic_cli.py
Total arguments: 4
Arguments: ['hello', 'world', '42']

Ejemplo Práctico: Calculadora Simple

calculator.py
import sys

def calculate(a: float, op: str, b: float) -> float:
    """Realiza operación matemática básica."""
    operations = {
        "+": lambda x, y: x + y,
        "-": lambda x, y: x - y,
        "*": lambda x, y: x * y,
        "/": lambda x, y: x / y if y != 0 else None,
    }
    return operations.get(op, lambda x, y: None)(a, b)

if __name__ == "__main__":
    if len(sys.argv) != 4:
        print("Usage: python calculator.py <num1> <op> <num2>")
        sys.exit(1)
    
    try:
        num1 = float(sys.argv[1])
        operator = sys.argv[2]
        num2 = float(sys.argv[3])
        
        result = calculate(num1, operator, num2)
        print(f"Result: {result}")
    except ValueError:
        print("Error: Invalid numbers")
        sys.exit(1)
python calculator.py 10 + 5
Result: 15.0
Usar sys.argv directamente es útil para scripts simples, pero para aplicaciones más complejas con múltiples opciones y flags, necesitamos herramientas más robustas.
MÓDULO_02

El Módulo argparse

argparse es el módulo estándar de Python para crear interfaces de línea de comandos profesionales. Proporciona parsing automático de argumentos, generación de mensajes de ayuda y validación de tipos.

Beneficios

AYUDA AUTOMÁTICA
Genera --help automáticamente con documentación
VALIDACIÓN
Verifica tipos y valores de argumentos
FLEXIBILIDAD
Soporta argumentos posicionales, opcionales y flags

ArgumentParser Básico

greet.py
import argparse

# Crear el parser
parser = argparse.ArgumentParser(
    description="A friendly greeting program"
)

# Agregar argumentos
parser.add_argument(
    "name",
    help="Name of the person to greet"
)
parser.add_argument(
    "-c", "--count",
    type=int,
    default=1,
    help="Number of times to greet (default: 1)"
)
parser.add_argument(
    "-u", "--uppercase",
    action="store_true",
    help="Convert greeting to uppercase"
)

# Parsear argumentos
args = parser.parse_args()

# Ejecutar lógica
greeting = f"Hello, {args.name}!"
if args.uppercase:
    greeting = greeting.upper()

for _ in range(args.count):
    print(greeting)
python greet.py World -c 3 --uppercase
HELLO, WORLD!
HELLO, WORLD!
HELLO, WORLD!

Tipos de Argumentos

Tipo Sintaxis Descripción
Posicional "name" Argumento obligatorio por posición
Opcional "-n", "--name" Argumento con prefijo - o --
Flag/Boolean action="store_true" True si está presente, False si no
Con valor type=int Convierte automáticamente al tipo
Choices choices=["a", "b"] Limita valores permitidos

Ejemplo Avanzado: File Processor

file_processor.py
import argparse
from pathlib import Path

def create_parser():
    parser = argparse.ArgumentParser(
        prog="fileproc",
        description="Process files with various operations"
    )
    
    # Argumento posicional requerido
    parser.add_argument(
        "input_file",
        type=Path,
        help="Input file path"
    )
    
    # Argumento opcional con valor por defecto
    parser.add_argument(
        "-o", "--output",
        type=Path,
        default=None,
        help="Output file (default: stdout)"
    )
    
    # Argumento con opciones limitadas
    parser.add_argument(
        "-m", "--mode",
        choices=["upper", "lower", "title", "reverse"],
        default="upper",
        help="Transformation mode (default: upper)"
    )
    
    # Flag booleano
    parser.add_argument(
        "-v", "--verbose",
        action="store_true",
        help="Enable verbose output"
    )
    
    return parser

def process_file(args):
    """Procesa el archivo según los argumentos."""
    if args.verbose:
        print(f"Processing: {args.input_file}")
    
    # Leer contenido
    content = args.input_file.read_text()
    
    # Aplicar transformación
    transforms = {
        "upper": str.upper,
        "lower": str.lower,
        "title": str.title,
        "reverse": lambda s: s[::-1],
    }
    result = transforms[args.mode](content)
    
    # Escribir salida
    if args.output:
        args.output.write_text(result)
        if args.verbose:
            print(f"Written to: {args.output}")
    else:
        print(result)

if __name__ == "__main__":
    parser = create_parser()
    args = parser.parse_args()
    process_file(args)
El uso de type=Path convierte automáticamente las cadenas en objetos pathlib.Path, facilitando las operaciones con archivos.
MÓDULO_03

Subcomandos con argparse

Los subcomandos permiten crear aplicaciones CLI con múltiples comandos, similar a herramientas como git o docker. Cada subcomando puede tener sus propios argumentos específicos.

Arquitectura de Subcomandos
Main CLI
init
add
remove
list

Implementando Subcomandos

task_manager.py
import argparse
import json
from pathlib import Path

TASKS_FILE = Path("tasks.json")

def load_tasks():
    if TASKS_FILE.exists():
        return json.loads(TASKS_FILE.read_text())
    return []

def save_tasks(tasks):
    TASKS_FILE.write_text(json.dumps(tasks, indent=2))

# Funciones para cada subcomando
def cmd_add(args):
    """Añade una nueva tarea."""
    tasks = load_tasks()
    task = {
        "id": len(tasks) + 1,
        "title": args.title,
        "priority": args.priority,
        "done": False
    }
    tasks.append(task)
    save_tasks(tasks)
    print(f"✓ Task added: {task['title']}")

def cmd_list(args):
    """Lista todas las tareas."""
    tasks = load_tasks()
    if not tasks:
        print("No tasks found.")
        return
    
    for task in tasks:
        status = "✓" if task["done"] else "○"
        priority = task["priority"].upper()
        print(f"{status} [{task['id']}] {task['title']} ({priority})")

def main():
    # Parser principal
    parser = argparse.ArgumentParser(
        prog="tasks",
        description="Simple task manager CLI"
    )
    
    # Crear subparsers
    subparsers = parser.add_subparsers(
        dest="command",
        help="Available commands"
    )
    
    # Subcomando: add
    add_parser = subparsers.add_parser("add", help="Add a new task")
    add_parser.add_argument("title", help="Task title")
    add_parser.add_argument(
        "-p", "--priority",
        choices=["low", "medium", "high"],
        default="medium",
        help="Task priority"
    )
    add_parser.set_defaults(func=cmd_add)
    
    # Subcomando: list
    list_parser = subparsers.add_parser("list", help="List all tasks")
    list_parser.set_defaults(func=cmd_list)
    
    # Parsear y ejecutar
    args = parser.parse_args()
    if args.command:
        args.func(args)
    else:
        parser.print_help()

if __name__ == "__main__":
    main()
python task_manager.py add "Learn Python" -p high
✓ Task added: Learn Python
MÓDULO_04

Click: CLIs con Decoradores

Click es una biblioteca de terceros que simplifica la creación de CLIs usando decoradores. Es más conciso que argparse y ofrece características avanzadas como composición de comandos, prompts interactivos y colores.

Instalación

terminal
pip install click

Comparación de Frameworks

argparse
Biblioteca estándar. Imperativo, más verboso pero sin dependencias externas.
Click
Decoradores, composable, colores y prompts. Ideal para CLIs complejas.
Typer
Basado en Click + type hints. Sintaxis más moderna y auto-documentado.

CLI Básica con Click

click_hello.py
import click

@click.command()
@click.argument("name")
@click.option(
    "--count", "-c",
    default=1,
    help="Number of greetings"
)
@click.option(
    "--shout", "-s",
    is_flag=True,
    help="Shout the greeting"
)
def hello(name, count, shout):
    """Simple program that greets NAME."""
    greeting = f"Hello, {name}!"
    if shout:
        greeting = greeting.upper()
    
    for _ in range(count):
        click.echo(greeting)

if __name__ == "__main__":
    hello()
python click_hello.py World -c 2 --shout
HELLO, WORLD!
HELLO, WORLD!

Grupos de Comandos

click_cli.py
import click

@click.group()
@click.version_option(version="1.0.0")
def cli():
    """A CLI tool for managing projects."""
    pass

@cli.command()
@click.argument("name")
@click.option("--template", "-t", default="basic")
def init(name, template):
    """Initialize a new project."""
    click.echo(f"Creating project '{name}' with template '{template}'")

@cli.command()
@click.argument("package")
@click.option("--dev", is_flag=True, help="Install as dev dependency")
def install(package, dev):
    """Install a package."""
    dep_type = "dev" if dev else "production"
    click.echo(f"Installing {package} as {dep_type} dependency")

if __name__ == "__main__":
    cli()
Click usa click.echo() en lugar de print() para mejor compatibilidad entre plataformas y soporte de colores.
MÓDULO_05

Typer: CLIs Modernas con Type Hints

Typer es la evolución moderna de Click, creada por el mismo autor de FastAPI. Utiliza type hints de Python para definir argumentos automáticamente, reduciendo significativamente el código necesario.

Instalación

terminal
pip install typer[all]

CLI Simple con Typer

typer_hello.py
import typer

app = typer.Typer()

@app.command()
def hello(
    name: str,
    count: int = 1,
    uppercase: bool = False
):
    """Say hello to NAME."""
    greeting = f"Hello, {name}!"
    if uppercase:
        greeting = greeting.upper()
    
    for _ in range(count):
        print(greeting)

if __name__ == "__main__":
    app()
python typer_hello.py World --count 2 --uppercase
HELLO, WORLD!
HELLO, WORLD!
Typer utiliza los type hints de Python para inferir automáticamente el tipo de cada argumento. El parámetro name: str se convierte en un argumento posicional, mientras que los que tienen valores por defecto se convierten en opciones.
MÓDULO_06

Validación y Códigos de Salida

Una CLI robusta debe validar la entrada del usuario y manejar errores de forma elegante, proporcionando mensajes claros y códigos de salida apropiados.

Códigos de Salida Estándar

Código Significado Uso
0 Éxito Operación completada correctamente
1 Error general Error genérico o desconocido
2 Uso incorrecto Argumentos inválidos o faltantes
126 Sin permisos No se puede ejecutar el comando
127 No encontrado Comando o archivo no existe

Validación Personalizada

validation.py
import argparse
import sys
from pathlib import Path

def validate_port(value: str) -> int:
    """Valida que el puerto esté en rango válido."""
    port = int(value)
    if not 1 <= port <= 65535:
        raise argparse.ArgumentTypeError(
            f"Port must be between 1-65535, got {port}"
        )
    return port

def validate_file(value: str) -> Path:
    """Valida que el archivo exista."""
    path = Path(value)
    if not path.exists():
        raise argparse.ArgumentTypeError(
            f"File not found: {value}"
        )
    return path

parser = argparse.ArgumentParser()
parser.add_argument("config", type=validate_file)
parser.add_argument("-p", "--port", type=validate_port, default=8080)

args = parser.parse_args()
Siempre envía mensajes de error a stderr usando file=sys.stderr o funciones equivalentes. Esto permite que la salida normal se redirija sin contaminarla con errores.

Resumen del Capítulo

01 // sys.argv

Acceso básico a argumentos de línea de comandos. Útil para scripts simples sin dependencias.

02 // argparse

Módulo estándar para CLIs profesionales con validación automática y mensajes de ayuda.

03 // Subcomandos

Organiza CLIs complejas con múltiples comandos como git o docker usando subparsers.

04 // Click

Framework basado en decoradores para CLIs elegantes con colores, prompts y composición.

05 // Typer

CLIs modernas con type hints. Auto-documentación y sintaxis minimalista basada en Click.

06 // Validación

Valida entrada, maneja errores elegantemente y usa códigos de salida estándar.

Basado en "Learn Python Programming, 4th Edition" // Fabrizio Romano & Heinrich Kruger

Chapter 15: CLI Applications