Introducción
El desarrollo de aplicaciones Python robustas requiere tres mecanismos recurrentes: la interacción por línea de comandos, el registro de eventos y la notificación automatizada. A continuación se presenta una implementación práctica de cada uno de estos componentes, con ejemplos reutilizables y listos para integrar en proyectos reales.
Construcción de interfaces de línea de comandos con argparse
El módulo argparse permite definir argumentos, opciones, valores por defecto y mensajes de ayuda sin dependencias externas. Su uso correcto mejora la usabilidad de scripts y facilita la automatización.
Ejemplo de un CLI para procesar archivos de datos:
import argparse
def crear_parser():
parser = argparse.ArgumentParser(
description='Procesa archivos de datos en distintos formatos'
)
parser.add_argument(
'operacion',
type=str,
choices=['convertir', 'analizar', 'exportar'],
help='Acción principal que ejecutará el programa'
)
parser.add_argument(
'--formato',
type=str,
choices=['json', 'csv', 'xml'],
default='json',
help='Formato de salida deseado'
)
parser.add_argument(
'--verbose',
action='store_true',
help='Muestra información detallada durante la ejecución'
)
return parser.parse_args()
if __name__ == '__main__':
args = crear_parser()
print(args)
En este caso, operacion es un argumento posicional obligatorio, mientras que --formato y --verbose son opcionales. La validación de valores permitidos se realiza automáticamente.
Gestión de logs con logging
Un buen sistema de logging debe permitir distintos niveles de severidad, salida múltiple y formato configurable. La siguiente utilidad crea un logger que escribe tanto en consola como en archivo:
import logging
import sys
def crear_logger(nombre, ruta_log=None, nivel='INFO'):
logger = logging.getLogger(nombre)
logger.setLevel(getattr(logging, nivel.upper(), logging.INFO))
formato = logging.Formatter(
'%(asctime)s | %(name)s | %(levelname)s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
consola = logging.StreamHandler(sys.stdout)
consola.setFormatter(formato)
logger.addHandler(consola)
if ruta_log:
archivo = logging.FileHandler(ruta_log, encoding='utf-8')
archivo.setFormatter(formato)
logger.addHandler(archivo)
return logger
Uso típico del logger configurado:
logger = crear_logger(__name__, ruta_log='aplicacion.log', nivel='DEBUG')
logger.debug('Inicio de depuración')
logger.info('Proceso iniciado correctamente')
logger.warning('Recurso cercano al límite')
logger.error('Error al acceder a la base de datos')
Al separar la configuración del logger de la lógica de negocio se mantiene el código limpio y se facilita el mantenimiento.
Envío automatizado de correos electrónicos
Para notificaciones por correo se puede utilizar smtplib junto con los módulos de email.mime. La siguiente función soporta cuerpo en texto plano y archivos adjuntos:
import os
import smtplib
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
def enviar_correo(destinatario, asunto, cuerpo, remitente, contrasena, adjunto=None):
mensaje = MIMEMultipart()
mensaje['From'] = remitente
mensaje['To'] = destinatario
mensaje['Subject'] = asunto
mensaje.attach(MIMEText(cuerpo, 'plain', 'utf-8'))
if adjunto and os.path.exists(adjunto):
parte = MIMEBase('application', 'octet-stream')
with open(adjunto, 'rb') as archivo:
parte.set_payload(archivo.read())
encoders.encode_base64(parte)
parte.add_header(
'Content-Disposition',
f'attachment; filename={os.path.basename(adjunto)}'
)
mensaje.attach(parte)
with smtplib.SMTP_SSL('smtp.ejemplo.com', 465) as servidor:
servidor.login(remitente, contrasena)
servidor.send_message(mensaje)
Para ejecutar el envío de forma periódica, se puede combinar con el módulo schedule:
import schedule
import time
def tarea_diaria():
enviar_correo(
destinatario='usuario@ejemplo.com',
asunto='Reporte diario',
cuerpo='Se adjunta el reporte correspondiente.',
remitente='sistema@ejemplo.com',
contrasena='secreto',
adjunto='reporte.xlsx'
)
schedule.every().day.at('09:00').do(tarea_diaria)
while True:
schedule.run_pending()
time.sleep(60)