web-dev-qa-db-ger.com

Führen Sie eine Funktion aus, nachdem Flask die Antwort zurückgegeben hat

Ich habe etwas Code, der ausgeführt werden muss after Flask gibt eine Antwort zurück. Ich denke nicht, dass es komplex genug ist, eine Task-Queue wie Celery dafür einzurichten. Die wichtigste Anforderung ist, dass Flask die Antwort an den Client zurückgeben muss, bevor diese Funktion ausgeführt wird. Es kann nicht warten, bis die Funktion ausgeführt wird.

Es gibt einige Fragen dazu, aber keine der Antworten scheint die Ausführung einer Aufgabe zu adressieren, nachdem die Antwort an den Client gesendet wurde. Sie werden dennoch synchron ausgeführt und anschließend zurückgesendet.

14
Brandon Wang

Kurz gesagt: Flask bietet keine speziellen Funktionen, um dies zu erreichen. Berücksichtigen Sie für einfache einmalige Aufgaben das Multithreading von Python wie unten gezeigt. Verwenden Sie für komplexere Konfigurationen eine Aufgabenwarteschlange wie RQ oder Sellerie.

Warum?

Es ist wichtig, die Funktionen zu verstehen Flask und warum sie nicht das beabsichtigte Ziel erreichen . All dies ist nützlich in anderen Fällen und sind gut zu lesen, aber nicht mit Hintergrundaufgaben helfen.

Flask's after_request Handler

Flask 's after_request - Handler, wie in dieses Muster für verzögerte Rückrufe von Anfragen und dieses Snippet zum Anhängen verschiedener Funktionen pro Anfrage beschrieben, übergibt die Anfrage an die Rückruffunktion . Der beabsichtigte Anwendungsfall besteht darin, die Anforderung zu ändern , z. B. um ein Cookie anzufügen.

Auf diese Weise wartet die Anforderung, bis die Ausführung dieser Handler abgeschlossen ist, da erwartet wird, dass sich die Anforderung selbst als Ergebnis ändert.

Flask's teardown_request Handler

Dies ähnelt after_request, Aber teardown_request Erhält das Objekt request nicht. Das heißt also, es wird nicht auf die Anfrage warten, oder?

Dies scheint die Lösung zu sein, wie diese Antwort auf eine ähnliche Stapelüberlauf-Frage nahelegt. Und da in der Dokumentation von Flask erklärt wird, dass Teardown-Rückrufe unabhängig von der tatsächlichen Anforderung sind und der Anforderungskontext nicht erhalten wird, haben Sie guten Grund, dies zu glauben.

Leider ist teardown_request Immer noch synchron. Dies geschieht erst zu einem späteren Zeitpunkt in der Verarbeitung von Flask-Anfragen, wenn die Anfrage nicht mehr geändert werden kann. Flask wartet noch, bis die Teardown-Funktionen abgeschlossen sind, bevor die Antwort als diese Liste von =) zurückgegeben wird Flask Rückrufe und Fehler diktiert.

Flask's Streaming-Antworten

Flask kann Antworten streamen, indem ein Generator an Response() übergeben wird, wie diese Stapelüberlauf-Antwort auf eine ähnliche Frage nahelegt.

Beim Streaming empfängt der Client die Antwort , bevor die Anforderung abgeschlossen wird. Die Anforderung wird jedoch weiterhin synchron ausgeführt, sodass der Worker, der die Anforderung verarbeitet, beschäftigt ist, bis der Stream beendet ist.

Dieses Flask Muster für das Streaming enthält eine Dokumentation zur Verwendung von stream_with_context(), die erforderlich ist, um den Anforderungskontext einzuschließen.

Also, was ist die Lösung?

Flask bietet keine Lösung, um Funktionen im Hintergrund auszuführen, da dies nicht in der Verantwortung von Flask liegt.

In den meisten Fällen können Sie dieses Problem am besten lösen, indem Sie eine Task-Warteschlange wie RQ oder Sellerie verwenden. Diese verwalten knifflige Dinge wie Konfiguration, Planung und Verteilung von Mitarbeitern für Sie. Dies ist die häufigste Antwort auf diese Art von Frage, da sie am zutreffendsten ist und Sie dazu zwingt, die Dinge so einzurichten, wie Sie den Kontext usw. berücksichtigen. korrekt.

Wenn Sie eine Funktion im Hintergrund ausführen müssen und keine Warteschlange einrichten möchten, um dies zu verwalten, können Sie die in Python integrierte Funktion threading oder multiprocessing um einen Background Worker zu erzeugen.

Sie können über Hintergrundaufgaben nicht auf request oder andere Thread-Locals von Flask zugreifen, da die Anforderung dort nicht aktiv ist. Übergeben Sie stattdessen die benötigten Daten aus der Ansicht an den Hintergrund-Thread, wenn Sie ihn erstellen.

@app.route('/start_task')
def start_task():
    def do_work(value):
        # do something that takes a long time
        import time
        time.sleep(value)

    thread = Thread(target=do_work, kwargs={'value': request.args.get('value', 20))
    thread.start()
    return 'started'
33
Brandon Wang

Flask ist eine WSGI-App und kann daher nach der Antwort grundsätzlich nichts verarbeiten. Aus diesem Grund gibt es keinen solchen Handler. Die WSGI-App selbst ist nur für das Erstellen des Antwort-Iterator-Objekts für den WSGI-Server verantwortlich. 

Ein WSGI-Server (wie gunicorn ) kann diese Funktionalität jedoch sehr leicht bereitstellen, aber die Anbindung der Anwendung an den Server ist aus verschiedenen Gründen eine sehr schlechte Idee.

Aus genau diesem Grund bietet WSGI eine Spezifikation für Middleware an, und Werkzeug stellt eine Reihe von Helfern zur Verfügung, um die allgemeinen Middleware-Funktionen zu vereinfachen. Darunter befindet sich eine ClosingIterator -Klasse, mit der Sie Methoden an die close-Methode des Antwort-Iterators anhängen können, die nach dem Schließen der Anforderung ausgeführt wird.

Hier ist ein Beispiel einer naiven after_response-Implementierung, die als Flask-Erweiterung ausgeführt wird:

import traceback
from werkzeug.wsgi import ClosingIterator

class AfterResponse:
    def __init__(self, app=None):
        self.callbacks = []
        if app:
            self.init_app(app)

    def __call__(self, callback):
        self.callbacks.append(callback)
        return callback

    def init_app(self, app):
        # install extension
        app.after_response = self

        # install middleware
        app.wsgi_app = AfterResponseMiddleware(app.wsgi_app, self)

    def flush(self):
        for fn in self.callbacks:
            try:
                fn()
            except Exception:
                traceback.print_exc()

class AfterResponseMiddleware:
    def __init__(self, application, after_response_ext):
        self.application = application
        self.after_response_ext = after_response_ext

    def __call__(self, environ, after_response):
        iterator = self.application(environ, after_response)
        try:
            return ClosingIterator(iterator, [self.after_response_ext.flush])
        except Exception:
            traceback.print_exc()
            return iterator

Sie können diese Erweiterung folgendermaßen verwenden:

import flask
app = flask.Flask("after_response")
AfterResponse(app)

@app.after_response
def say_hi():
    print("hi")

@app.route("/")
def home():
    return "Success!\n"

Wenn Sie "/" kräuseln, sehen Sie Folgendes in Ihren Protokollen:

127.0.0.1 - - [24/Jun/2018 19:30:48] "GET / HTTP/1.1" 200 -
hi

Dadurch wird das Problem einfach gelöst, ohne dass Threads (GIL ??) eingeführt werden müssen oder eine Task-Warteschlange und eine Clientsoftware installiert und verwaltet werden müssen.

9
Matthew Story

Middleware-Lösung für Flask Blueprints

Dies ist die gleiche von Matthew Story vorgeschlagene Lösung (die IMHO die perfekte Lösung ist - danke Matthew), angepasst für Flask Blueprints. Die geheime Sauce hier ist, den App-Kontext mit der current_app zu erreichen Proxy. Lesen Sie hier für weitere Informationen ( http://flask.pocoo.org/docs/1.0/appcontext/ )

Angenommen, die Klassen AfterThisResponse und AfterThisResponseMiddleware befinden sich in einem Modul unter .utils.after_this_response.py

Wo dann die Flask Objekterstellung stattfindet, haben Sie vielleicht zB ...

__init__.py

from api.routes import my_blueprint
from .utils.after_this_response import AfterThisResponse

app = Flask( __name__ )
AfterThisResponse( app )
app.register_blueprint( my_blueprint.mod )

Und dann in Ihrem Blueprint-Modul ...

a_blueprint.py

from flask import Blueprint, current_app

mod = Blueprint( 'a_blueprint', __name__, url_prefix=URL_PREFIX )

@mod.route( "/some_resource", methods=['GET', 'POST'] )
def some_resource():
    # do some stuff here if you want

    @current_app.after_this_response
    def post_process():
        # this will occur after you finish processing the route & return (below):
        time.sleep(2)
        print("after_response")

    # do more stuff here if you like & then return like so:
    return "Success!\n"
0
Paul Brackin

Sie können diesen Code verwenden, den ich ausprobiert habe. Es funktioniert.

dieser Code gibt den String "message" aus. nach den 3 Sekunden ab der Zeitplanung. Sie können die Zeit selbst nach Ihren Anforderungen ändern.

import time, traceback
import threading

def every(delay,message, task):
  next_time = time.time() + delay
  time.sleep(max(0, next_time - time.time()))
  task(message)

def foo(message):
  print(message+"  :foo", time.time())



def main(message):
    threading.Thread(target=lambda: every(3,message, foo)).start()

main("message")
0
Muhammad Usman