Redirection sur une exception dans Flask en utilisant un decorator
La gestion des exceptions Python decorators est un moyen puissant de réduire le code try-except.
Dans une application Flask , vous implémentez généralement des gestionnaires d'exceptions globaux. Dans de nombreux cas, cela est suffisant. Mais que faire si vous voulez plus de contrôle ?
Dans un projet, je me connectais à une API et je voulais qu'un certain nombre de routes utilisant la API redirige vers une page de démarrage en cas d'erreur de la API , avec un message approprié bien sûr. J'ai implémenté ceci en utilisant un gestionnaire d'exception 'redirect_decorator' qui a également un paramètre spécifiant le point de terminaison. Le decorator est utilisé pour éviter d'avoir à donner à chaque route un code try-except.
Configuration du projet
Comme toujours, créez un virtual environment, et des répertoires, par exemple en tapant :
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
Dans le répertoire du projet se trouve le fichier permettant d'exécuter l'application :
# run.py
from app.factory import create_app
app = create_app()
if __name__ == '__main__':
app.run(
host='localhost',
debug=True,
)
Flask avec un gestionnaire d'erreur.
Il s'agit plus ou moins d'une copie de ce qui se trouve sur le site 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
Dans le répertoire du projet, nous démarrons l'application :
python run.py
Puis dans le navigateur vous pouvez vérifier les endpoints :
http://127.0.0.1:5000
http://127.0.0.1:5000/error
Si vous voulez voir le message de la fonction internal_server_error() alors mettez debug=False dans run.py. Dans ce cas, le serveur n'est pas redémarré lors des modifications !
Ajouter une API qui génère des exceptions sur les erreurs
Dans la Python, la plupart des paquets externes génèrent des exceptions lorsque des erreurs se produisent. C'est ce que nous allons simuler ici. Notre paquetage API possède une fonction et génère ses propres exceptions.
# 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 une erreur se produit, la fonction lève un ServiceAPIError avec une raison et une description.
La redirection decorator
Pour un maximum de flexibilité, je veux pouvoir passer un point final dans la decorator. Cela signifie qu'il faut envelopper à nouveau la decorator .
# 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
Ajout de la API et redirection de la decorator
Notre application possède une ou plusieurs routes pour exécuter des tâches. Lorsqu'une tâche échoue à cause d'une erreur API , nous voulons être redirigés vers la route 'start'. J'ai ajouté des instructions d'impression pour voir toutes les informations disponibles dans notre 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
Dirigez maintenant votre navigateur vers :
http://127.0.0.1:5000/tasks/mytask
et observez que nous sommes redirigés vers 'start'. Joli. Dans la console, le texte suivant est imprimé :
Exception: ServiceAPIError, e =
- function = tasks, args = (), kwargs = {'task': 'mytask'}
- endpoint = start
- e.args = (), e.kwargs = {'reason': 'Connection problem', 'description': 'Remote system not responding'}
Mais nous pouvons faire mieux : Message clignotant
Lorsqu'il y a une erreur API et que nous sommes redirigés vers 'start', nous voulons aussi montrer ce qui s'est passé. Nous pouvons le faire en utilisant la fonction de message flash() dans Flask. Avant de rediriger dans notre decorator, nous flashons() le message :
flash("There was a problem in '{}' with task '{}': {}".\
format(f.__name__, kwargs['task'], e.kwargs['reason']))
Je ne vais pas implémenter cela dans les templates mais plutôt utiliser la fonction get_flashed_messages() dans la route 'start' :
@app.route('/start')
def start():
return 'Start<br>' + ', '.join(get_flashed_messages())
Pour utiliser flash(), nous devons également ajouter la secret_key à notre application.
Notre application finale :
# 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
Maintenant, pointez votre navigateur sur :
http://127.0.0.1:5000/tasks/mytask
et nous sommes redirigés vers 'start', mais aussi le message est affiché. Maintenant nous savons ce qui s'est passé !
Start
There was a problem in 'tasks_task' with task 'mytask': Connection problem
Résumé
Comparé à try-except dans chaque route, nous avons beaucoup moins de code. Et dans notre gestionnaire d'exception de redirection decorator , nous avons toutes les informations dont nous avons besoin pour afficher un message correct.
Liens / crédits
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
En savoir plus...
Decorator Exceptions Flask
Récent
- Masquer les clés primaires de la base de données UUID de votre application web
- Don't Repeat Yourself (DRY) avec Jinja2
- SQLAlchemy, PostgreSQL, nombre maximal de lignes par user
- Afficher les valeurs des filtres dynamiques SQLAlchemy
- Transfert de données sécurisé grâce au cryptage à Public Key et à pyNaCl
- rqlite : une alternative à haute disponibilité et dist distribuée SQLite
Les plus consultés
- Utilisation des Python's pyOpenSSL pour vérifier les certificats SSL téléchargés d'un hôte
- Utiliser UUIDs au lieu de Integer Autoincrement Primary Keys avec SQLAlchemy et MariaDb
- Utiliser PyInstaller et Cython pour créer un exécutable Python
- Connexion à un service sur un hôte Docker à partir d'un conteneur Docker
- SQLAlchemy : Utilisation de Cascade Deletes pour supprimer des objets connexes
- Flask RESTful API validation des paramètres de la requête avec les schémas Marshmallow