Is there a way in Flask to send the response to the client and then continue doing some processing? I have a few book-keeping tasks which are to be done, but I don’t want to keep the client waiting.
Note that these are actually really fast things I wish to do, thus creating a new thread, or using a queue, isn’t really appropriate here. (One of these fast things is actually adding something to a job queue.)
Sadly teardown callbacks do not execute after the response has been returned to the client:
import flask import time app = flask.Flask("after_response") def teardown(request): time.sleep(2) print("teardown_request") def home(): return "Success!n" if __name__ == "__main__": app.run()
When curling this you’ll note a 2s delay before the response displays, rather than the curl ending immediately and then a log 2s later. This is further confirmed by the logs:
teardown_request 127.0.0.1 - - [25/Jun/2018 15:41:51] "GET / HTTP/1.1" 200 -
The correct way to execute after a response is returned is to use WSGI middleware that adds a hook to the close method of the response iterator. This is not quite as simple as the
teardown_request decorator, but it’s still pretty straight-forward:
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, start_response): iterator = self.application(environ, start_response) try: return ClosingIterator(iterator, [self.after_response_ext.flush]) except Exception: traceback.print_exc() return iterator
Which you can then use like this:
def after(): time.sleep(2) print("after_response")
From the shell you will see the response return immediately and then 2 seconds later the
after_response will hit the logs:
127.0.0.1 - - [25/Jun/2018 15:41:51] "GET / HTTP/1.1" 200 - after_response
This is a summary of a previous answer provided here.
QUICK and EASY method.
We will use pythons Thread Library to acheive this.
Your API consumer has sent something to process and which is processed by my_task() function which takes 10 seconds to execute.
But the consumer of the API wants a response as soon as they hit your API which is return_status() function.
You tie the my_task to a thread and then return the quick response to the API consumer, while in the background the big process gets compelete.
Below is a simple POC.
import os from flask import Flask,jsonify import time from threading import Thread app = Flask(__name__) def main(): return "Welcome!" def return_status(): """Return first the response and tie the my_task to a thread""" Thread(target = my_task).start() return jsonify('Response asynchronosly') def my_task(): """Big function doing some job here I just put pandas dataframe to csv conversion""" time.sleep(10) import pandas as pd pd.DataFrame(['sameple data']).to_csv('./success.csv') return print('large function completed') if __name__ == "__main__": app.run(host="0.0.0.0", port=8080)
I had a similar problem with my blog. I wanted to send notification emails to those subscribed to comments when a new comment was posted, but I did not want to have the person posting the comment waiting for all the emails to be sent before he gets his response.
I used a
multiprocessing.Pool for this. I started a pool of one worker (that was enough, low traffic site) and then each time I need to send an email I prepare everything in the Flask view function, but pass the final
send_email call to the pool via
You can find an example on how to use celery from within Flask
The gist of the idea (pun intended) is to define the long, book-keeping tasks as @celery.task and use apply_async1 or delay to from within the view to start the task
You can do this with WSGI’s
close protocol, exposed from the Werkzeug Response object’s
call_on_close decorator. Explained in this other answer here: https://stackoverflow.com/a/63080968/78903