Saltar al contenido
src / articulos / 08-manejo-de-excepciones.md

Manejo de Excepciones en Python

Emilio Castro //
// NARRACIÓN 0:00 / 0:00

La premisa básica en SRE es esta: todo falla. Los discos se llenan, las APIs se ahogan con sus propias cuotas, los archivos de configuración llegan corruptos y los tokens expiran en el peor momento posible.

Un script que asume el camino feliz es un script que va a destruirte la automatización nocturna. Para construir herramientas que sobrevivan al mundo real, necesitas excepciones.

Control Básico: try y except

try y except envuelven el código propenso a fallar, operaciones de red, E/S de disco, parseos, y redirigen el flujo cuando algo explota. Sin ellos, cualquier error interrumpe el proceso. Con ellos, decides qué pasa después.

# Simulamos la lectura de una configuración crítica de despliegue
try:
    with open('/etc/app/config.json', 'r') as file:
        config = file.read()
        print("Configuración cargada correctamente.")
except FileNotFoundError:
    # Capturamos el error específico sin abortar el script
    print("[ERROR] Archivo config.json no encontrado. Inicializando con valores por defecto.")
    config = "{}" # Valor de respaldo

Filtrado de Excepciones Específicas

Una petición HTTP a una API interna puede fallar de varias formas: el host no resuelve, la conexión expira, la respuesta llega pero el JSON está malformado. Cada fallo tiene causa distinta y merece tratamiento distinto. Por eso los bloques except se apilan, uno por tipo de error.

Lo que no debes hacer es capturar todo con un except Exception genérico y loguear “algo salió mal”. Eso es lo que hace que los post-mortems sean una pesadilla: nadie sabe qué falló ni por qué.

import json

# Manejo multi-excepción para la evaluación de una carga JSON simulada
payload_recibido = "{ nombre: servidor-1 " # JSON malformado (falta comilla y cierre)

try:
    # Intentamos parsear la cadena
    datos = json.loads(payload_recibido)
except TypeError:
    print("[ALERTA] Se recibió un tipo de dato inválido en lugar de una cadena.")
except json.JSONDecodeError as error:
    # Asignamos el error a la variable 'error' para analizar la traza
    print(f"[CRÍTICO] Falló el parseo de la configuración: {error}")
    # Salida esperada: Falló el parseo... Expecting property name enclosed in double quotes...

Flujos de Limpieza: else y finally

else solo se ejecuta si el bloque try terminó sin levantar ninguna excepción. Sirve para separar la lógica de éxito de la de error sin que ambas queden mezcladas en el mismo bloque. Los principiantes suelen poner todo dentro del try y luego no saben cuál línea falló.

finally se ejecuta siempre, haya fallo o no. Ahí cierras lo que abriste: puertos TCP, sesiones de base de datos, manejadores de archivos. Si no liberas esos recursos en finally, se acumulan. El proceso aguanta un rato y luego colapsa en producción, no en tu laptop.

# Intento de conexión a un socket
try:
    print("Estableciendo túnel SSH hacia la instancia bastion...")
    # Simulamos un fallo (ej. llave privada denegada)
    conexion = 10 / 0
except ZeroDivisionError:
    print("[ERROR] Fallo de autenticación. Conexión rechazada.")
else:
    print("Túnel establecido exitosamente. Ejecutando comandos...")
finally:
    print("[LIMPIEZA] Liberando socket local TCP y terminando sesión SSH.")

Disparar Excepciones Manuales (raise)

A veces el fallo no viene del sistema operativo ni de la red. Viene de tus propias entradas. Si un script recibe una variable de entorno con un formato inseguro o fuera de rango, lo correcto no es seguir procesando y ver qué pasa. Es abortar de inmediato con raise.

He visto scripts que reciben una versión de motor inválida, la ignoran silenciosamente, y despliegan igual. El resultado es un sistema en producción corriendo sobre una versión no soportada que nadie detectó hasta semanas después. Un raise explícito corta ese camino antes de que empiece.

def validar_version_motor(version):
    """Comprueba que el motor cumpla los requisitos corporativos antes de desplegar."""
    if float(version) < 14.0:
        # Forzamos una excepción (Value Error) interrumpiendo el flujo
        raise ValueError(f"Versión de motor no soportada ({version}). Mínima requerida: 14.0.")
    return True

try:
    # Intentamos desplegar una base de datos legada
    validar_version_motor("12.5")
except ValueError as e:
    print(f"[RECHAZO PIPELINE CI/CD] {e}")

Capturar excepciones no es burocracia defensiva. Es la diferencia entre un script que te da contexto accionable a las 3 AM y uno que simplemente muere sin decirte nada.