MANEJADORES DE ERROR, INTROSPECCIÓN Y LOGGING EN FLASK¶
Estos apuntes son la segunda parte de la sesión 2 de UT4. Asumen que ya has leído
poo_ut4_2_rutas_dinamicas_excepciones.md(tipos en URL, códigos HTTP, redirect, estado en el servidor, parámetros URL vs formularios).
Manejadores globales de error¶
El problema que resuelven¶
Hasta ahora, cada route captura sus propias excepciones de dominio con try/except. Pero hay errores que no ocurren dentro de ningún route: URLs que no coinciden con ninguna ruta registrada, divisiones por cero accidentales, variables no definidas o ficheros que no se pueden abrir.
Sin manejo, Flask muestra una página genérica (o, en modo debug, el traceback completo). Con un manejador de error podemos controlar qué ve el usuario en esos casos.
El decorador @app.errorhandler¶
Un manejador de error es una función registrada con @app.errorhandler(codigo) que Flask ejecuta cuando se genera un error con ese código. Funciona como un route especial que captura por código HTTP en vez de por URL (URL = /producto/A1; código HTTP = 404).
El parámetro
erecibe el objeto de excepción que Flask ha generado. Podemos interpolarlo en el mensaje, loguearlo o ignorarlo según convenga.
¿Qué excepción recibimos en e?
Internamente Flask lanza werkzeug.exceptions.NotFound cuando ninguna URL coincide, InternalServerError cuando una excepción se escapa de un route, etc. No tenemos que importarlas ni instanciarlas: el manejador las recibe ya construidas en el parámetro e.
Dónde colocar los manejadores en app.py
Coloca los manejadores junto al resto de routes. Suelen ir al final del fichero, separados con un comentario tipo # --- Manejadores globales ---, para que se vean de un golpe y no se confundan con los routes normales.
Interacción con los try/except de los routes¶
Los manejadores globales no sustituyen al try/except dentro de los routes: lo complementan. Cada uno captura cosas distintas:
| Situación | ¿Quién responde? |
|---|---|
URL no registrada (p.ej. /foo) |
@app.errorhandler(404) global |
| Producto no encontrado capturado en route | try/except del route (devuelve str(e), 404) |
| Excepción no capturada en un route | @app.errorhandler(500) global |
Converter de tipo que rechaza el valor (p.ej. <int:cantidad> con tres; ver apuntes UT4-2) |
@app.errorhandler(404) global |
Sin manejador 404, una URL inexistente muestra una página técnica de Flask en inglés. Con manejador, ves tu página personalizada y coherente con el resto de la app. Los manejadores globales son una red de seguridad: atrapan lo que se escapa de los
try/except, no lo que ya está bien manejado.
Manejadores globales para experiencia de usuario coherente
Sin manejadores globales, un 404 por URL inexistente muestra una página Flask genérica, mientras que un 404 por producto inexistente muestra tu mensaje. El usuario ve dos experiencias distintas para el mismo código de error. Con manejadores globales unificas la apariencia.
Introspección: la app conoce sus rutas¶
Qué es la introspección¶
Introspección es la capacidad de un programa de observar su propio estado en tiempo de ejecución. Flask permite consultar qué rutas tiene registradas sin leer el código fuente: la información está viva dentro del objeto app.
Ya has visto introspección antes en Python:
dir(objeto)te dice qué métodos tiene un objeto, ytype(x)te dice de qué clase es. Aquí hacemos lo mismo con la app: le preguntamos qué rutas tiene.
El objeto app.url_map¶
app.url_map es el mapa de rutas que Flask usa internamente para decidir qué función de vista llamar ante una URL. Cada vez que escribimos @app.route("/algo"), Flask añade esa ruta a app.url_map por dentro. Por eso no tenemos que crear el mapa: ya está construido cuando arranca la app. Desde fuera, lo podemos iterar con iter_rules():
Cada regla tiene dos atributos importantes:
| Atributo | Qué contiene | Ejemplo |
|---|---|---|
regla.rule |
El patrón de la URL tal como se declaró | /producto/<codigo> |
regla.endpoint |
El nombre de la función de vista | ver_producto |
Fíjate:
regla.rulees el patrón, no una URL concreta. Muestra<codigo>literal, noA1oB2. Para generar URLs concretas necesitaríasurl_forcon valores.
Patrón lista + append + join
El patrón lineas = [...]; lineas.append(x); "\n".join(lineas) es la forma habitual en Python de construir un texto largo a partir de muchas piezas. Es preferible a concatenar con + en un bucle: más rápido y más legible. Lo verás muchas veces a lo largo del curso.
La regla static¶
Flask registra automáticamente una ruta /static/<path:filename> para servir ficheros estáticos (CSS, imágenes, JavaScript). Conviene filtrarla en páginas de ayuda porque no forma parte de tu lógica de aplicación.
<path:filename>usa el converterpath, otro converter de Flask que acepta cualquier cosa hasta el final de la URL, incluyendo barras (a diferencia de<int:>o<float:>que vimos en los apuntes anteriores). No tendremos que escribirlo en este lab — solo aparece aquí para que entendamos qué estamos filtrando conregla.endpoint != "static":
Usos típicos¶
| Caso de uso | Qué hacemos |
|---|---|
Página /ayuda |
Listar todas las rutas para el usuario. |
| Documentación automática | Generar un esquema de la API en JSON. |
| Depuración | Comprobar que un route se registró (útil cuando 404 aparece y no sabes por qué). |
Una página de ayuda siempre al día
Generar /ayuda por introspección garantiza que nunca queda desactualizada. Añades un route nuevo y automáticamente aparece.
Hooks y logging¶
Qué es un hook¶
Un hook (literalmente gancho) es un punto del flujo de un programa en el que puedes "enganchar" código propio para que se ejecute automáticamente, sin modificar el flujo original. Flask ofrece varios hooks:
| Hook | Cuándo se ejecuta |
|---|---|
@app.before_request |
Antes de cada petición, pase por el route que pase. |
@app.after_request |
Después de cada petición que ha terminado bien. |
@app.teardown_request |
Al final de cada petición, haya ido bien o mal (sirve para liberar recursos). |
En este apuntes solo usaremos before_request. Los hooks permiten añadir comportamientos transversales (logging, autenticación, medición de tiempos) sin tocar los routes uno por uno.
El módulo logging¶
logging es el módulo estándar de Python para registrar mensajes durante la ejecución.
Imagina que cierras la terminal y mañana alguien te pregunta "¿qué hizo tu app ayer a las 17h?". Con print no queda nada — los mensajes se fueron con la consola. Con logging queda escrito en un fichero, con timestamp, y puedes filtrar por nivel de gravedad.
A diferencia de print, permite:
- Escribir a un fichero en vez de a la consola.
- Clasificar mensajes por nivel (
DEBUG,INFO,WARNING,ERROR,CRITICAL). - Añadir timestamp y contexto automáticamente.
Configuración mínima:
Dónde colocar basicConfig en app.py
Esta configuración se pone una sola vez, al inicio del fichero, justo después de los import y antes de instanciar app = Flask(__name__). Si la ponemos después del primer app.logger.info(...) no tendrá efecto sobre las llamadas anteriores.
La sintaxis
%(nombre)sdel parámetroformates la del módulologging— no es f-string ni.format(). Para este lab basta con copiar el formato; no tenemos que dominarla.
| Parámetro | Qué hace |
|---|---|
filename |
Fichero donde se escriben los mensajes. Si no existe, se crea. |
level |
Listón mínimo. Si ponemos INFO, registra INFO/WARNING/ERROR/CRITICAL y descarta DEBUG. Como un termómetro: lo que está por encima del listón pasa; lo de abajo no. |
format |
Plantilla del mensaje. %(asctime)s es el timestamp, %(levelname)s el nivel, %(message)s el texto. |
@app.before_request y el objeto request¶
El hook @app.before_request decora una función que Flask llama antes de cualquier route. Dentro de ella tienes acceso a request, un objeto global que describe la petición actual:
Propiedades más útiles de request (ahora solo conviene conocer estas dos):
| Propiedad | Qué contiene | Ejemplo |
|---|---|---|
request.method |
Método HTTP | "GET" |
request.path |
Ruta pedida (sin el dominio) | "/producto/A1" |
app.logger vs logging.info directo
logging.info(...) y app.logger.info(...) son equivalentes para nuestro caso, pero app.logger es la forma idiomática en Flask: usa la configuración de la app y permite distinguir tus logs de los de Flask cuando depures. Tómalo como costumbre desde el principio.
Dónde colocar el hook en app.py
El hook se registra junto al resto de configuración global, al principio del fichero, después de la creación de app. El orden de declaración no importa: Flask los descubre todos al cargar el módulo, no al ejecutar línea por línea.
Cómo verificar que el logging funciona
Tras configurarlo, arranca el servidor (python -m expendedora.presentation.app), visita varias URLs en el navegador y comprueba:
- En la carpeta desde la que arrancaste la app aparece el fichero
expendedora.log. - Cada petición ha generado una línea con timestamp, nivel y método/ruta.
Si no aparece el fichero: revisa que basicConfig esté antes de cualquier app.logger.info(...). Si aparece pero no se rellena: revisa que el level no sea más alto que el nivel del mensaje (level=logging.INFO registra info(); level=logging.WARNING lo descarta).
Qué se versiona y qué no¶
Los ficheros de log son generados en ejecución: cambian cada vez que corres la app, dependen de qué URLs visites, contienen información de depuración. No se versionan en Git: se añaden al .gitignore. El código que genera el log sí se versiona; el log en sí no.