python - Ejecuta una función después de que Flask devuelve la respuesta
multithreading (4)
Flask es una aplicación WSGI y, como resultado, fundamentalmente no puede manejar nada después de la respuesta. Esta es la razón por la que no existe tal controlador, la aplicación WSGI en sí misma es responsable solo de construir el objeto iterador de respuesta para el servidor WSGI.
Sin embargo, un servidor WSGI (como gunicorn ) puede proporcionar muy fácilmente esta funcionalidad, pero vincular la aplicación al servidor es una muy mala idea por varias razones.
Por esta razón exacta, WSGI proporciona una especificación para
Middleware
, y Werkzeug proporciona una serie de ayudantes para simplificar la funcionalidad común de Middleware.
Entre ellos se encuentra una clase
ClosingIterator
que le permite conectar métodos al método de
close
del iterador de respuesta que se ejecuta después de que se cierra la solicitud.
Aquí hay un ejemplo de una implementación ingenua de
after_response
realizada como una extensión de 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
Puedes usar esta extensión así:
import flask
app = flask.Flask("after_response")
AfterResponse(app)
@app.after_response
def say_hi():
print("hi")
@app.route("/")
def home():
return "Success!/n"
Cuando encrespes "/" verás lo siguiente en tus registros:
127.0.0.1 - - [24/Jun/2018 19:30:48] "GET / HTTP/1.1" 200 -
hi
Esto resuelve el problema simplemente sin introducir hilos (GIL ??) o tener que instalar y administrar una cola de tareas y software de cliente.
Tengo un código que debe ejecutarse después de que Flask devuelva una respuesta. No creo que sea lo suficientemente complejo como para configurar una cola de tareas como Celery. El requisito clave es que Flask debe devolver la respuesta al cliente antes de ejecutar esta función. No puede esperar a que se ejecute la función.
Hay algunas preguntas existentes sobre esto, pero ninguna de las respuestas parece abordar la ejecución de una tarea después de que la respuesta se envía al cliente, todavía se ejecutan sincrónicamente y luego se devuelve la respuesta.
La larga historia corta es que Flask no proporciona ninguna capacidad especial para lograr esto. Para tareas simples de una sola vez, considere el multiproceso de Python como se muestra a continuación. Para configuraciones más complejas, use una cola de tareas como RQ o Celery.
¿Por qué?
Es importante comprender las funciones que proporciona Flask y por qué no logran el objetivo previsto. Todos estos son útiles en otros casos y son una buena lectura, pero no ayudan con las tareas de fondo.
after_request
de Flask
El controlador
after_request
de Flask, como se detalla en
este patrón para devoluciones de llamada de solicitud diferida
y
este fragmento al adjuntar diferentes funciones por solicitud
, pasará la solicitud a la función de devolución de llamada.
El caso de uso previsto es
modificar la solicitud
, como adjuntar una cookie.
Por lo tanto, la solicitud esperará a que estos manejadores terminen de ejecutarse porque se espera que la solicitud en sí misma cambie como resultado.
Controlador de
teardown_request
de
teardown_request
de frasco
Esto es similar a
after_request
, pero
teardown_request
no recibe el objeto de
request
.
Eso significa que no esperará la solicitud, ¿verdad?
Esta parece ser la solución, como sugiere esta respuesta a una pregunta similar de . Y dado que la documentación de Flask explica que las devoluciones de llamada de desmontaje son independientes de la solicitud real y no reciben el contexto de la solicitud, tendría buenas razones para creerlo.
Desafortunadamente,
teardown_request
todavía es sincrónico, solo ocurre en una parte posterior del manejo de solicitudes de Flask cuando la solicitud ya no es modificable.
Flask
aún esperará
a que se completen las
funciones de desmontaje
antes de devolver la respuesta, como dicta
esta lista de devoluciones de llamadas y errores de Flask
.
Las respuestas de transmisión de Flask
Flask puede transmitir respuestas pasando un generador a
Response()
, como sugiere
esta respuesta de a una pregunta similar
.
Con la transmisión, el cliente comienza a recibir la respuesta antes de que finalice la solicitud. Sin embargo, la solicitud aún se ejecuta sincrónicamente, por lo que el trabajador que maneja la solicitud está ocupado hasta que finalice la secuencia.
Este patrón de matraz para la transmisión
incluye cierta documentación sobre el uso de
stream_with_context()
, que es necesario para incluir el contexto de la solicitud.
Entonces, ¿cuál es la solución?
Flask no ofrece una solución para ejecutar funciones en segundo plano porque esto no es responsabilidad de Flask.
En la mayoría de los casos, la mejor manera de resolver este problema es usar una cola de tareas como RQ o Celery. Estos manejan cosas difíciles como la configuración, la programación y la distribución de trabajadores para usted. Esta es la respuesta más común a este tipo de preguntas porque es la más correcta y lo obliga a configurar las cosas de una manera que considere el contexto, etc. correctamente.
Si necesita ejecutar una función en segundo plano y no desea configurar una cola para administrar esto, puede usar el
threading
o
multiprocessing
integrado de Python para generar un trabajador en segundo plano.
No puede acceder a la
request
ni a otros locales de subprocesos de Flask desde tareas en segundo plano, ya que la solicitud no estará activa allí.
En su lugar, pase los datos que necesita de la vista al hilo de fondo cuando lo cree.
@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''
Puedes usar este código, lo he probado. Funciona.
este código imprimirá la cadena "mensaje". después de los 3 segundos, desde el tiempo de programación. Puede cambiar el tiempo de acuerdo con sus requisitos.
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")
Solución de middleware para planos de matraces
Esta es la misma solución propuesta por Matthew Story (que es la solución perfecta en mi humilde opinión, gracias Matthew), adaptada para Flask Blueprints. La salsa secreta aquí es obtener el contexto de la aplicación utilizando el proxy current_app. Lea here para obtener más información ( here )
Supongamos que las clases AfterThisResponse y AfterThisResponseMiddleware se colocan en un módulo en .utils.after_this_response.py
Entonces, cuando se produce la creación del objeto Flask, es posible que tenga, por ejemplo ...
__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 )
Y luego en su módulo de planos ...
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"