I have some code that needs to execute after Flask returns a response. I don't think it's complex enough to set up a task queue like Celery for it. The key requirement is that Flask must return the response to the client before running this function. It can't wait for the function to execute.
There are some existing questions about this, but none of the answers seem to address running a task after the response is sent to the client, they still execute synchronously and then the response is returned.
答案 0 :(得分:29)
The long story short is that Flask does not provide any special capabilities to accomplish this. For simple one-off tasks, consider Python's multithreading as shown below. For more complex configurations, use a task queue like RQ or Celery.
It's important to understand the functions Flask provides and why they do not accomplish the intended goal. All of these are useful in other cases and are good reading, but don't help with background tasks.
after_request
handlerFlask's after_request
handler, as detailed in this pattern for deferred request callbacks and this snippet on attaching different functions per request, will pass the request to the callback function. The intended use case is to modify the request, such as to attach a cookie.
Thus the request will wait around for these handlers to finish executing because the expectation is that the request itself will change as a result.
teardown_request
handlerThis is similar to after_request
, but teardown_request
doesn't receive the request
object. So that means it won't wait for the request, right?
This seems like the solution, as this answer to a similar Stack Overflow question suggests. And since Flask's documentation explains that teardown callbacks are independent of the actual request and do not receive the request context, you'd have good reason to believe this.
Unfortunately, teardown_request
is still synchronous, it just happens at a later part of Flask's request handling when the request is no longer modifiable. Flask will still wait for teardown functions to complete before returning the response, as this list of Flask callbacks and errors dictates.
Flask can stream responses by passing a generator to Response()
, as this Stack Overflow answer to a similar question suggests.
With streaming, the client does begin receiving the response before the request concludes. However, the request still runs synchronously, so the worker handling the request is busy until the stream is finished.
This Flask pattern for streaming includes some documentation on using stream_with_context()
, which is necessary to include the request context.
Flask doesn't offer a solution to run functions in the background because this isn't Flask's responsibility.
In most cases, the best way to solve this problem is to use a task queue such as RQ or Celery. These manage tricky things like configuration, scheduling, and distributing workers for you.This is the most common answer to this type of question because it is the most correct, and forces you to set things up in a way where you consider context, etc. correctly.
If you need to run a function in the background and don't want to set up a queue to manage this, you can use Python's built in threading
or multiprocessing
to spawn a background worker.
You can't access request
or others of Flask's thread locals from background tasks, since the request will not be active there. Instead, pass the data you need from the view to the background thread when you create it.
@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'
答案 1 :(得分:11)
烧瓶是WSGI app,因此从根本上说,它在响应后无法处理任何事情。这就是为什么不存在这样的处理程序的原因,WSGI应用程序本身仅负责构造WSGI服务器的响应迭代器对象。
然而,WSGI server(例如gunicorn)可以很容易地提供此功能,但是由于多种原因,将应用程序与服务器绑定是一个非常糟糕的主意。
由于这个确切的原因,WSGI提供了Middleware的规范,而Werkzeug提供了许多帮助程序来简化常见的中间件功能。其中有一个ClosingIterator类,它使您可以将方法挂接到响应迭代器的close
方法上,该方法在关闭请求后执行。
这是一个简单的after_response
实现的示例,它是通过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
您可以使用以下扩展名:
import flask
app = flask.Flask("after_response")
AfterResponse(app)
@app.after_response
def say_hi():
print("hi")
@app.route("/")
def home():
return "Success!\n"
卷曲“ /”时,您会在日志中看到以下内容:
127.0.0.1 - - [24/Jun/2018 19:30:48] "GET / HTTP/1.1" 200 -
hi
这可以简单地解决此问题,而无需引入线程(GIL ??)或安装和管理任务队列和客户端软件。
答案 2 :(得分:3)
Flask现在(通过Werkzeug)支持响应对象上的call_on_close
回调装饰器。使用方法如下:
@app.after_request
def response_processor(response):
# Prepare all the local variables you need since the request context
# will be gone in the callback function
@response.call_on_close
def process_after_request():
# Do whatever is necessary here
pass
return response
优势:
call_on_close
使用close
方法的WSGI规范,设置了在返回响应后调用的函数。
没有线程,没有后台作业,没有复杂的设置。它在同一线程中运行,而不会阻止请求返回。
缺点:
session.add
或session.merge
将其添加到新会话中;这不是缺点!)答案 3 :(得分:2)
感谢Matthew Story和Paul Brackin,但我需要更改他们的建议。 因此,有效的解决方案是:
.
├── __init__.py
├── blueprint.py
└── library.py
# __init__.py
from flask import Flask
from .blueprint import bp
from .library import AfterResponse
app = Flask(__name__)
with app.app_context():
app.register_blueprint(bp, url_prefix='/')
AfterResponse(app)
# blueprint.py
from flask import Blueprint, request, current_app as app
from time import sleep
bp = Blueprint('app', __name__)
@bp.route('/')
def root():
body = request.json
@app.after_response
def worker():
print(body)
sleep(5)
print('finished_after_processing')
print('returned')
return 'finished_fast', 200
# library.py
from werkzeug.wsgi import ClosingIterator
from traceback import print_exc
class AfterResponse:
def __init__(self, application=None):
self.functions = list()
if application:
self.init_app(application)
def __call__(self, function):
self.functions.append(function)
def init_app(self, application):
application.after_response = self
application.wsgi_app = AfterResponseMiddleware(application.wsgi_app, self)
def flush(self):
while self.functions:
try:
self.functions.pop()()
except Exception:
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:
print_exc()
return iterator
可以找到源代码here
答案 4 :(得分:2)
除了其他解决方案,您还可以通过结合 after_this_request 和 response.call_on_close 来执行路由特定操作:
@app.route('/')
def index():
# Do your pre-response work here
msg = 'Hello World!'
@flask.after_this_request
def add_close_action(response):
@response.call_on_close
def process_after_request():
# Do your post-response work here
time.sleep(3.0)
print('Delayed: ' + msg)
return response
return msg
答案 5 :(得分:2)
我尝试了所有这些方法,包括使用 Thread、AfterResponse、call_on_close。
一切正常。
@app.route('/inner')
def foo():
for i in range(10):
sleep(1)
print(i)
return
@app.route('/inner', methods=['POST'])
def run_jobs():
try:
thread = Thread(target=foo)
thread.start()
return render_template("index_inner.html", img_path=DIR_OF_PHOTOS, video_path=UPLOAD_VIDEOS_FOLDER)
app = Flask(__name__)
AfterResponse(app)
@app.route('/inner', methods=['POST'])
def save_data():
pass
@app.after_response
def foo():
for i in range(10):
sleep(1)
print(i)
return
3.close_on_close
from time import sleep
from flask import Flask, Response, request
app = Flask('hello')
@app.route('/')
def hello():
response = Response('hello')
@response.call_on_close
def on_close():
for i in range(10):
sleep(1)
print(i)
return response
if __name__ == '__main__':
app.run()
答案 6 :(得分:0)
您可以使用我尝试过的此代码。它有效。
此代码将打印字符串“ message”。 3秒后,从调度时间开始。您可以根据自己的需要更改自己的时间。
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")
答案 7 :(得分:0)
烧瓶蓝图的中间件解决方案
这与Matthew Story提出的解决方案相同(这是IMHO的完美解决方案-感谢Matthew),适用于Flask Blueprints。这里的秘诀是使用current_app代理来掌握应用上下文。阅读here了解更多信息(http://flask.pocoo.org/docs/1.0/appcontext/)
我们假设AfterThisResponse和AfterThisResponseMiddleware类放在.utils.after_this_response.py
中的模块中然后在Flask对象创建的地方,例如...
__ 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 )
然后在您的蓝图模块中...
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"
答案 8 :(得分:0)
信号request_finished
接收一个Response
实例作为参数。连接到该信号即可完成任何后处理。
来自https://flask-doc.readthedocs.io/en/latest/signals.html:
def log_response(sender, response, **extra):
sender.logger.debug('Request context is about to close down. '
'Response: %s', response)
from flask import request_finished
request_finished.connect(log_response, app)
obs:如果发生错误,则可以使用信号got_request_exception
。
答案 9 :(得分:0)
阅读许多主题后。 我找到了适合我的解决方案,如果使用 Blueprint,它适用于 python 3.8 和 SQLAlchemy
init.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
import dir
import time
from flask_mail import Mail
from flask_cors import CORS
import flask_excel as excel
# init SQLAlchemy so we can use it later in our models
dbb = SQLAlchemy()
def create_app():
app = Flask(__name__)
from .bp_route_1 import auth as bp_route_1_blueprint
app.register_blueprint(bp_route_1_blueprint)
CORS(app)
return app
bp_route_1.py
from flask import Blueprint, request, redirect, Response, url_for, abort, flash, render_template, \
copy_current_request_context
from . import dbb
from .models import #Import Models
from threading import Thread
bp_route_1 = Blueprint('bp_route_1', __name__)
@bp_route_1.route('/wehooks', methods=['POST'])
def route_1_wehooks_post():
@copy_current_request_context #to copy request
def foo_main():
# insert your code here
do_long_time_webhook(request)
Thread(target=foo_main).start()
print("do Webhook by Thread")
return Response(status=200)
def do_long_time_webhook(request):
try:
data = request.get_data()
print(data)
#do long tim function for webhook data
except Exception as e:
print('Dont do webhook', e)