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.
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.
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")
Total arguments: 4
Arguments: ['hello', 'world', '42']
Ejemplo Práctico: Calculadora Simple
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)
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.
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
ArgumentParser Básico
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)
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
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)
type=Path convierte automáticamente las cadenas en objetos pathlib.Path, facilitando las operaciones con archivos.
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.
Implementando Subcomandos
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()
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
pip install click
Comparación de Frameworks
CLI Básica con Click
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()
HELLO, WORLD!
Grupos de Comandos
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.echo() en lugar de print() para mejor compatibilidad entre plataformas y soporte de colores.
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
pip install typer[all]
CLI Simple con Typer
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()
HELLO, WORLD!
name: str se convierte en un argumento posicional, mientras que los que tienen valores por defecto se convierten en opciones.
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
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()
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.