web-dev-qa-db-fra.com

Exécuter une fonction après que Flask ait renvoyé la réponse

J'ai du code qui doit être exécuté après que Flask renvoie une réponse. Je ne pense pas que ce soit assez complexe pour mettre en place une file d'attente comme Celery. La condition essentielle est que Flask doit renvoyer la réponse au client avant d'exécuter cette fonction. Il ne peut pas attendre que la fonction soit exécutée.

Il existe certaines questions à ce sujet, mais aucune des réponses ne semble traiter de l'exécution d'une tâche après l'envoi de la réponse au client. Elles sont toujours exécutées de manière synchrone et la réponse est ensuite renvoyée.

14
Brandon Wang

En résumé, Flask ne fournit aucune capacité spéciale pour accomplir cela. Pour les tâches simples ponctuelles, considérez le multithreading de Python comme indiqué ci-dessous. Pour des configurations plus complexes, utilisez une file d'attente de tâches telle que RQ ou Celery.

Pourquoi?

Il est important de comprendre les fonctions fournies par Flask et la raison pour laquelle elles ne permettent pas d’atteindre le but recherché. Tous ces éléments sont utiles dans d’autres cas et constituent une bonne lecture, mais n’aident pas les tâches en arrière-plan.

Le gestionnaire after_request du flacon

Le gestionnaire after_request du flacon, comme indiqué dans ce modèle pour les rappels de demande différée et cet extrait lors de l'attachement de fonctions différentes par requête , transmettra la demande à la fonction de rappel. Le cas d'utilisation prévu est de modifier la demande, par exemple, pour attacher un cookie.

Ainsi, la demande attendra que ces gestionnaires terminent l’exécution car on s’attend à ce que la demande elle-même change en conséquence.

Le gestionnaire teardown_request du flacon

Ceci est similaire à after_request, mais teardown_request ne reçoit pas l'objet request. Cela signifie donc qu'il n'attendra pas la demande, non?

Cela semble être la solution, comme le suggère cette réponse à une question similaire de Stack Overflow } _. Et puisque la documentation de Flask explique que les rappels de démontage sont indépendants de la demande réelle } et ne reçoivent pas le contexte de la demande, vous auriez de bonnes raisons de le croire.

Malheureusement, teardown_request est toujours synchrone, cela se produit à un stade ultérieur du traitement de la demande par Flask lorsque celle-ci n'est plus modifiable. Flask attendra toujours que les fonctions de démontage soient terminées avant de renvoyer la réponse, conformément aux instructions de cette liste des rappels et des erreurs Flask .

Les réponses en continu de Flask

Flask peut diffuser des réponses en passant un générateur à Response(), comme le suggère cette réponse de débordement de pile à une question similaire .

Avec la diffusion en continu, le client commence à recevoir la réponse avant la fin de la demande. Toutefois, la demande fonctionne toujours de manière synchrone, de sorte que le travailleur qui traite la demande est occupé jusqu'à la fin du flux.

_ { Ce modèle Flask pour la diffusion en continu } _ inclut une documentation sur l'utilisation de stream_with_context(), qui est nécessaire pour inclure le contexte de la demande.

Alors, quelle est la solution?

Flask n'offre pas de solution pour exécuter des fonctions en arrière-plan car ce n'est pas la responsabilité de Flask.

Dans la plupart des cas, le meilleur moyen de résoudre ce problème consiste à utiliser une file d'attente de tâches telle que RQ ou Celery. Celles-ci gèrent des tâches délicates telles que la configuration, la planification et la répartition des travailleurs pour vous. C’est la réponse la plus courante à ce type de question, car c’est la plus correcte, et vous oblige à définir le contexte de manière appropriée, etc. correctement.

Si vous avez besoin d'exécuter une fonction en arrière-plan et que vous ne souhaitez pas configurer de file d'attente, vous pouvez utiliser les fonctions intégrées de Python threading ou multiprocessing pour générer un agent d'arrière-plan .

Vous ne pouvez pas accéder à request ni à d'autres locataires de threads de Flask à partir de tâches en arrière-plan, car la requête n'y sera pas active. Au lieu de cela, transmettez les données dont vous avez besoin de la vue au fil d’arrière-plan lorsque vous les créez.

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

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

Flask est une application WSGI et, par conséquent, elle ne peut fondamentalement rien gérer après la réponse. C'est pourquoi aucun tel gestionnaire n'existe, l'application WSGI elle-même n'est responsable que de la construction de l'objet itérateur de réponse sur le serveur WSGI. 

Un serveur WSGI cependant (comme gunicorn ) peut très facilement fournir cette fonctionnalité, mais lier l'application au serveur est une très mauvaise idée pour un certain nombre de raisons.

Pour cette raison exacte, WSGI fournit une spécification pour Middleware , et Werkzeug fournit un certain nombre d’aides pour simplifier les fonctionnalités communes du middleware. Parmi eux se trouve un ClosingIterator class qui vous permet d’attacher des méthodes jusqu’à la méthode close de l’itérateur de réponse qui est exécuté après la fermeture de la demande.

Voici un exemple d'implémentation naive after_response réalisée en tant qu'extension Flask:

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

Vous pouvez utiliser cette extension comme ceci:

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

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

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

Lorsque vous écrivez "/", les informations suivantes apparaissent dans vos journaux:

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

Cela résout le problème simplement sans introduire de threads (GIL ??) ni avoir à installer et gérer une file d'attente de tâches et un logiciel client.

9
Matthew Story

Vous pouvez utiliser ce code, je l'ai essayé.Il fonctionne.

ce code imprimera la chaîne "message". après les 3 secondes, à partir du moment de la planification. Vous pouvez changer l'heure vous-même en fonction de vos besoins.

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