angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Umleitung bei einer Ausnahme in Flask unter Verwendung einer decorator

Python Exception Handling decorators sind ein leistungsfähiges Mittel zur Reduzierung von Try-Except-Code.

7 Mai 2022
In Flask
post main image
https://unsplash.com/@thiagojapyassu

In einer Flask -Anwendung implementieren Sie in der Regel globale Exception-Handler. In vielen Fällen ist dies ausreichend. Was aber, wenn Sie mehr Kontrolle wünschen?

In einem Projekt stellte ich eine Verbindung zu einem API her und wollte, dass eine Reihe von Routen, die den API verwenden, im Falle eines API -Fehlers auf eine 'Start'-Seite umleiten, natürlich mit einer entsprechenden Meldung. Ich habe dies mit einem 'redirect_decorator' Exception Handler implementiert, der auch einen Parameter hat, der den Endpunkt angibt. Der decorator wird verwendet, um zu vermeiden, dass jeder Route try-except-Code gegeben werden muss.

Projekt einrichten

Wie immer erstellen Sie ein virtual environment und Verzeichnisse, zum Beispiel durch Eingabe:

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

Im Projektverzeichnis befindet sich die Datei zum Ausführen der Anwendung:

# run.py
from app.factory import create_app

app = create_app()

if __name__ == '__main__':
    app.run(
        host='localhost',
        debug=True,
    )

Flask mit einem Error-Handler

Dies ist mehr oder weniger eine Kopie dessen, was auf der Website Flask zu finden ist.

# 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

Im Projektverzeichnis starten wir die Anwendung:

python run.py

Dann kann man im Browser die Endpunkte überprüfen:

http://127.0.0.1:5000
http://127.0.0.1:5000/error

Wenn Sie die Meldung der Funktion internal_server_error() sehen wollen, setzen Sie debug=False in run.py. In diesem Fall wird der Server bei Änderungen nicht neu gestartet!

Fügen Sie eine API hinzu, die bei Fehlern Ausnahmen erzeugt

In Python erzeugen die meisten externen Pakete Ausnahmen, wenn Fehler auftreten. Genau das werden wir hier simulieren. Unser Paket API hat eine Funktion und erzeugt seine eigenen Ausnahmen.

# 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',
        )

Wenn ein Fehler auftritt, löst die Funktion einen ServiceAPIError mit einem Grund und einer Beschreibung aus.

Die Weiterleitung decorator

Für maximale Flexibilität möchte ich in der Lage sein, einen Endpunkt in der decorator zu übergeben. Dies bedeutet, dass die decorator erneut umgeschlagen werden muss.

# 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

Hinzufügen der API und Umleitung der decorator

Unsere Anwendung hat eine oder mehrere Routen zur Ausführung von Aufgaben. Wenn eine Aufgabe aufgrund eines API -Fehlers fehlschlägt, möchten wir zur Route 'start' umgeleitet werden. Ich habe Druckanweisungen hinzugefügt, um alle in unserer decorator verfügbaren Informationen anzuzeigen:

# 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

Rufen Sie nun Ihren Browser auf:

http://127.0.0.1:5000/tasks/mytask

und beobachten Sie, dass wir zu 'start' umgeleitet werden. Schön. In der Konsole wird das Folgende ausgegeben:

Exception: ServiceAPIError, e = 
- function = tasks, args = (), kwargs = {'task': 'mytask'}
- endpoint = start
- e.args = (), e.kwargs = {'reason': 'Connection problem', 'description': 'Remote system not responding'}

Aber wir können es besser machen: Blinkende Meldung

Wenn ein API -Fehler auftritt und wir zu 'start' umgeleitet werden, wollen wir auch zeigen, was passiert ist. Dies können wir mit der Funktion flash() message in Flask erreichen. Vor der Weiterleitung in unserem decorator blinken() wir die Nachricht:

flash("There was a problem in '{}' with task  '{}': {}".\
    format(f.__name__, kwargs['task'], e.kwargs['reason']))

Ich werde dies nicht in Vorlagen implementieren, sondern stattdessen die Funktion get_flashed_messages() in der 'start' Route verwenden:

    @app.route('/start')
    def start():
        return 'Start<br>' + ', '.join(get_flashed_messages())

Um flash() zu verwenden, müssen wir auch den secret_key zu unserer Anwendung hinzufügen.

Unsere endgültige Anwendung:

# 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

Rufen Sie nun Ihren Browser auf:

http://127.0.0.1:5000/tasks/mytask

und wir werden zu 'start' weitergeleitet, aber auch die Nachricht wird angezeigt. Jetzt wissen wir, was passiert ist!

Start
There was a problem in 'tasks_task' with task 'mytask': Connection problem

Zusammenfassung

Im Vergleich zu try-except haben wir in jeder Route viel weniger Code. Und in unserem Redirect decorator Exception Handler haben wir alle Informationen, die wir brauchen, um eine anständige Meldung anzuzeigen.

Links / Impressum

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

Einen Kommentar hinterlassen

Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.

Kommentare

Eine Antwort hinterlassen

Antworten Sie anonym oder melden Sie sich an, um zu antworten.