34

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

edA-qa mort-ora-y
  • 30,295
  • 39
  • 137
  • 267
  • 1
    Look at `celery` or another questions http://stackoverflow.com/q/16870858/880326 and http://stackoverflow.com/q/15969213/880326. – tbicr Jun 25 '13 at 10:37

6 Answers6

15

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)
Danish Xavier
  • 1,225
  • 1
  • 10
  • 21
  • 9
    Just want to point out that this would not work if you're using the processes option in flask to serve parallel requests. The process would exit as soon as you return the response, killing the thread started to perform the task. – Syed Saad Dec 30 '20 at 06:03
  • 1
    `return print('large function completed')` makes no sense, `print()` always returns `None`. Furthermore, you're not doing anything with the return value of `my_task()` – R. dV Jul 08 '21 at 10:11
  • 1
    Right. Thread didnt work for me – Upasana Mittal Dec 24 '21 at 10:26
  • If this doesn't work, why the hell has 14 upvotes ? – m3nda May 09 '23 at 14:10
11

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.

naught101
  • 18,687
  • 19
  • 90
  • 138
Matthew Story
  • 3,573
  • 15
  • 26
5

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.

Miguel Grinberg
  • 65,299
  • 14
  • 133
  • 152
2

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

PuercoPop
  • 6,707
  • 4
  • 30
  • 40
2

Sounds like Teardown Callbacks would support what you want. And you might want to combine it with the pattern from Per-Request After-Request Callbacks to help with organizing the code.

Tommi Komulainen
  • 2,830
  • 1
  • 19
  • 16
  • This looks closest to what I want. It's not path specific but that pattern shows how it can be easily done. – edA-qa mort-ora-y Jun 26 '13 at 06:41
  • 13
    Just an update-- this does *not* accomplish what the asker wants because Flask still waits on the teardown functions to finish before releasing the response. – Brandon Wang Feb 26 '18 at 17:24
0

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

Kiran Jonnalagadda
  • 2,606
  • 1
  • 30
  • 29