Redirigir una excepción en Flask utilizando un decorator
Python el manejo de excepciones decorators son una poderosa forma de reducir el código try-except.
En una aplicación Flask , normalmente se implementan manejadores de excepción globales. En muchos casos, esto es suficiente. ¿Pero qué pasa si quieres más control?
En un proyecto, me estaba conectando a un API y quería una serie de rutas que utilizaran el API para redirigir a una página de 'inicio' en caso de un error del API , con un mensaje apropiado, por supuesto. He implementado esto usando un manejador de excepciones 'redirect_decorator' que también tiene un parámetro que especifica el punto final. El decorator se utiliza para evitar tener que dar a cada ruta un código try-except.
Configuración del proyecto
Como siempre, crear un virtual environment, y directorios, por ejemplo, escribiendo:
Translated with www.DeepL.com/Translator (free version)
python3 -m venv flask_except
source flask_except/bin/activate
cd flask_except
mkdir project
cd project
mkdir app
pip install flask
En el directorio del proyecto se encuentra el archivo para ejecutar la aplicación:
# run.py
from app.factory import create_app
app = create_app()
if __name__ == '__main__':
app.run(
host='localhost',
debug=True,
)
Flask con un manejador de errores
Esto es más o menos una copia de lo que hay en la web Flask .
# factory.py
from flask import Flask
def internal_server_error(e):
return 'An internal server error occurred', 500
def create_app():
app = Flask(__name__)
@app.route('/')
def welcome():
return 'Welcome'
@app.route('/error')
def error():
a = 2/0
return 'Error'
app.register_error_handler(500, internal_server_error)
return app
En el directorio del proyecto iniciamos la aplicación:
python run.py
Luego en el navegador puedes comprobar los endpoints:
http://127.0.0.1:5000
http://127.0.0.1:5000/error
Si quieres ver el mensaje de la función internal_server_error() entonces pon debug=False en run.py. En este caso, el servidor no se reinicia con los cambios.
Añadir un API que genere excepciones en caso de error
En Python, la mayoría de los paquetes externos generan excepciones cuando se producen errores. Eso es lo que vamos a simular aquí. Nuestro paquete API tiene una función y genera sus propias excepciones.
# api: custom exception
class ServiceAPIError(Exception):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
# api: function
def service_api_function():
if True:
raise ServiceAPIError(
reason='Connection problem',
description='Remote system not responding',
)
Si se produce un error, la función lanza un ServiceAPIError con un motivo y una descripción.
La redirección decorator
Para obtener la máxima flexibilidad quiero poder pasar un endpoint en el decorator. Esto significa envolver el decorator de nuevo.
# our redirect decorator
def redirect_on_service_api_error(endpoint):
def decorator(f):
@functools.wraps(f)
def func(*args, **kwargs):
try:
return f(*args, **kwargs)
except ServiceAPIError as e:
return redirect(url_for(endpoint))
return func
return decorator
Añadir el API y redirigir el decorator
Nuestra aplicación tiene una o más rutas para ejecutar tareas. Cuando una tarea falla debido a un error API , queremos ser redirigidos a la ruta 'start'. He añadido declaraciones de impresión para ver toda la información disponible en nuestro decorator:
# factory.py (with api, redirect decorator)
from flask import Flask, redirect, url_for
import functools
# api: custom exception
class ServiceAPIError(Exception):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
# api: function
def service_api_function():
if True:
raise ServiceAPIError(
reason='Connection problem',
description='Remote system not responding',
)
# our redirect decorator
def redirect_on_service_api_error(endpoint):
def decorator(f):
@functools.wraps(f)
def func(*args, **kwargs):
try:
return f(*args, **kwargs)
except ServiceAPIError as e:
# debugging
print('Exception: {}, e = {}'.format(type(e).__name__, e))
print('- function = {}, args = {}, kwargs = {}'.format(f.__name__, args, kwargs))
print('- endpoint = {}'.format(endpoint))
print('- e.args = {}, e.kwargs = {}'.format(e.args, e.kwargs))
# redirect
return redirect(url_for(endpoint))
return func
return decorator
def internal_server_error(e):
return 'An internal server error occurred', 500
def create_app():
app = Flask(__name__)
@app.route('/')
def welcome():
return 'Welcome'
@app.route('/error')
def error():
a = 2/0
return 'Error'
@app.route('/start')
def start():
return 'Start'
@app.route('/tasks/<task>')
@redirect_on_service_api_error('start')
def tasks(task):
print('tasks: task = {}'.format(task))
service_api_function()
return 'Started task'
app.register_error_handler(500, internal_server_error)
return app
Ahora apunte su navegador a:
http://127.0.0.1:5000/tasks/mytask
y observa que somos redirigidos a 'start'. Bien. En la consola se imprime lo siguiente:
Exception: ServiceAPIError, e =
- function = tasks, args = (), kwargs = {'task': 'mytask'}
- endpoint = start
- e.args = (), e.kwargs = {'reason': 'Connection problem', 'description': 'Remote system not responding'}
Pero podemos hacerlo mejor: Mensaje intermitente
Cuando hay un error en API y somos redirigidos a 'start', también queremos mostrar lo que ha pasado. Podemos hacer esto usando la función de mensaje flash() en Flask. Antes de redirigir en nuestro decorator, flash() el mensaje:
flash("There was a problem in '{}' with task '{}': {}".\
format(f.__name__, kwargs['task'], e.kwargs['reason']))
No implementaré esto en las plantillas sino que utilizaré la función get_flashed_messages() en la ruta 'start':
@app.route('/start')
def start():
return 'Start<br>' + ', '.join(get_flashed_messages())
Para usar flash() también debemos añadir el secret_key a nuestra aplicación.
Nuestra aplicación final:
# factory.py (with api, redirect decorator, flashed messages)
from flask import Flask, redirect, url_for, flash, get_flashed_messages
import functools
# api: custom exception
class ServiceAPIError(Exception):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
# api: function
def service_api_function():
if True:
raise ServiceAPIError(
reason='Connection problem',
description='Remote system not responding',
)
# our redirect decorator
def redirect_on_service_api_error(endpoint):
def decorator(f):
@functools.wraps(f)
def func(*args, **kwargs):
try:
return f(*args, **kwargs)
except ServiceAPIError as e:
# debugging
print('Exception: {}, e = {}'.format(type(e).__name__, e))
print('- function = {}, args = {}, kwargs = {}'.format(f.__name__, args, kwargs))
print('- endpoint = {}'.format(endpoint))
print('- e.args = {}, e.kwargs = {}'.format(e.args, e.kwargs))
# flash
flash("There was a problem in '{}' with task '{}': {}".\
format(f.__name__, kwargs['task'], e.kwargs['reason']))
# redirect
return redirect(url_for(endpoint))
return func
return decorator
def internal_server_error(e):
return 'An internal server error occurred', 500
def create_app():
app = Flask(__name__)
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@app.route('/')
def welcome():
return 'Welcome'
@app.route('/error')
def error():
a = 2/0
return 'Error'
@app.route('/start')
def start():
return 'Start<br>' + ', '.join(get_flashed_messages())
@app.route('/tasks/<task>')
@redirect_on_service_api_error('start')
def tasks_task(task):
print('tasks, task = {}'.format(task))
service_api_function()
return 'Started task'
app.register_error_handler(500, internal_server_error)
return app
Ahora apunta tu navegador a:
http://127.0.0.1:5000/tasks/mytask
y somos redirigidos a 'start', pero también se muestra el mensaje. ¡Ahora ya sabemos lo que ha pasado!
Start
There was a problem in 'tasks_task' with task 'mytask': Connection problem
Resumen
Comparado con try-except en cada ruta tenemos mucho menos código. Y en nuestro manejador de excepciones decorator tenemos toda la información que necesitamos para mostrar un mensaje decente.
Enlaces / créditos
Flask - Handling Application Errors
https://flask.palletsprojects.com/en/2.1.x/errorhandling
Flask - Message Flashing
https://flask.palletsprojects.com/en/2.1.x/patterns/flashing
How do I pass extra arguments to a Python decorator?
https://stackoverflow.com/questions/10176226/how-do-i-pass-extra-arguments-to-a-python-decorator
Leer más
Decorator Exceptions Flask
Recientes
- Cómo ocultar las claves primarias de la base de datos UUID de su aplicación web
- Don't Repeat Yourself (DRY) con Jinja2
- SQLAlchemy, PostgreSQL, número máximo de filas por user
- Mostrar los valores en filtros dinámicos SQLAlchemy
- Transferencia de datos segura con cifrado de Public Key y pyNaCl
- rqlite: una alternativa de alta disponibilidad y dist distribuida SQLite
Más vistos
- Usando Python's pyOpenSSL para verificar los certificados SSL descargados de un host
- Usando UUIDs en lugar de Integer Autoincrement Primary Keys con SQLAlchemy y MariaDb
- Conectarse a un servicio en un host Docker desde un contenedor Docker
- Usando PyInstaller y Cython para crear un ejecutable de Python
- SQLAlchemy: Uso de Cascade Deletes para eliminar objetos relacionados
- Flask RESTful API validación de parámetros de solicitud con esquemas Marshmallow