Flask end response and continue processing

You can find an example on how to use celery from within Flask here https://gist.github.com/jzempel/3201722

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


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__)

@app.route("/")
def main():
    return "Welcome!"

@app.route('/add_')
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 apply_async.


Sadly teardown callbacks do not execute after the response has been returned to the client:

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

@app.teardown_request
def teardown(request):
    time.sleep(2)
    print("teardown_request")

@app.route("/")
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:

@app.after_response
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.

Tags:

Python

Flask