Doorverwijzen naar een uitzondering in Flask met behulp van een decorator
Python except handling decorators zijn een krachtige manier om try-except code te verminderen.
In een Flask toepassing, implementeert u typisch globale uitzonderingshandlers. In veel gevallen is dit voldoende. Maar wat als u meer controle wilt?
In een project maakte ik verbinding met een API en ik wilde een aantal routes die gebruik maakten van de API om te redirecten naar een 'start' pagina in geval van een API fout, met een toepasselijke boodschap natuurlijk. Ik heb dit geïmplementeerd met behulp van een 'redirect_decorator' exception handler die ook een parameter heeft die het eindpunt specificeert. De decorator wordt gebruikt om te voorkomen dat elke route try-except code moet krijgen.
Project opzet
Maak, zoals altijd, een virtual environment, en mappen, bijvoorbeeld door te typen:
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
In de project directory staat het bestand om de applicatie te draaien:
# run.py
from app.factory import create_app
app = create_app()
if __name__ == '__main__':
app.run(
host='localhost',
debug=True,
)
Flask met een error handler
Dit is min of meer een kopie van wat er op de Flask website staat.
# 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
In de project directory starten we de applicatie:
python run.py
Dan kun je in de browser de endpoints bekijken:
http://127.0.0.1:5000
http://127.0.0.1:5000/error
Wilt u de melding van de internal_server_error() functie zien dan stelt u debug=False in run.py in. In dit geval wordt de server niet herstart bij wijzigingen!
Voeg een API toe die excepties genereert bij fouten
In Python genereren de meeste externe pakketten exceptions als er fouten optreden. Dat is wat we hier gaan simuleren. Ons API pakket heeft één functie en genereert zijn eigen excepties.
# 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',
)
Als er een fout optreedt, roept de functie een ServiceAPIError op met een reden en beschrijving.
De omleiding decorator
Voor maximale flexibiliteit wil ik een endpoint kunnen doorgeven in de decorator. Dit betekent dat ik de decorator opnieuw moet wrappen.
# 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
Toevoegen van de API en omleiden decorator
Onze applicatie heeft een of meer routes om taken uit te voeren. Wanneer een taak mislukt vanwege een API fout, willen we omgeleid worden naar de route 'start'. Ik heb print statements toegevoegd om alle beschikbare informatie in onze decorator te zien:
# 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
Wijs nu uw browser naar:
http://127.0.0.1:5000/tasks/mytask
en zie dat we worden doorgestuurd naar 'start'. Nice. In de console wordt het volgende afgedrukt:
Exception: ServiceAPIError, e =
- function = tasks, args = (), kwargs = {'task': 'mytask'}
- endpoint = start
- e.args = (), e.kwargs = {'reason': 'Connection problem', 'description': 'Remote system not responding'}
Maar we kunnen beter: Bericht knippert
Als er een API fout optreedt en we worden omgeleid naar 'start', willen we ook laten zien wat er gebeurd is. Dit kunnen we doen met de flash() message functie in Flask. Voordat we in onze decorator doorverwijzen, flash() we het bericht:
flash("There was a problem in '{}' with task '{}': {}".\
format(f.__name__, kwargs['task'], e.kwargs['reason']))
Ik zal dit niet implementeren in templates maar in plaats daarvan de get_flashed_messages() functie gebruiken in de 'start' route:
@app.route('/start')
def start():
return 'Start<br>' + ', '.join(get_flashed_messages())
Om flash() te gebruiken moeten we ook de secret_key toevoegen aan onze app.
Onze uiteindelijke applicatie:
# 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
Richt nu je browser op:
http://127.0.0.1:5000/tasks/mytask
en we worden doorgestuurd naar 'start', maar ook het bericht wordt getoond. Nu weten we wat er gebeurd is!
Start
There was a problem in 'tasks_task' with task 'mytask': Connection problem
Samenvatting
Vergeleken met try-except in elke route hebben we veel minder code. En in onze redirect decorator exception handler hebben we alle informatie die we nodig hebben om een fatsoenlijke boodschap te tonen.
Links / credits
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
Lees meer
Decorator Exceptions Flask
Recent
- Database UUID primaire sleutels van je webapplicatie verbergen
- Don't Repeat Yourself (DRY) met Jinja2
- SQLAlchemy, PostgreSQL, maximum aantal rijen per user
- Toon de waarden in SQLAlchemy dynamische filters
- Veilige gegevensoverdracht met Public Key versleuteling en pyNaCl
- rqlite: een alternatief voor SQLite met hoge beschikbaarheid en distributed
Meest bekeken
- Met behulp van Python's pyOpenSSL om SSL-certificaten die van een host zijn gedownload te controleren
- Gebruik van UUIDs in plaats van Integer Autoincrement Primary Keys met SQLAlchemy en MariaDb
- Maak verbinding met een dienst op een Docker host vanaf een Docker container
- PyInstaller en Cython gebruiken om een Python executable te maken
- SQLAlchemy: Gebruik van Cascade Deletes om verwante objecten te verwijderen
- Flask RESTful API verzoekparametervalidatie met Marshmallow-schema's