Saltar al contenido

Ejecutar una función después de que Flask devuelva la respuesta

Este team de expertos despúes de varios días de trabajo y de recopilar de información, han obtenido los datos necesarios, nuestro deseo es que resulte de gran utilidad para tu trabajo.

Solución:

En resumen, Flask no proporciona ninguna capacidad especial para lograr esto. Para tareas simples y únicas, 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 lograr el objetivo previsto. Todos estos son útiles en otros casos y son una buena lectura, pero no ayudan con las tareas en segundo plano.

Matraz after_request manipulador

Matraz after_request handler, como se detalla en este patrón para devoluciones de llamada de solicitud diferidas y este fragmento sobre cómo 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 para adjuntar una cookie.

Por lo tanto, la solicitud esperará a que estos controladores terminen de ejecutarse porque la expectativa es que la solicitud en sí cambie como resultado.

Matraz teardown_request manipulador

Esto es similar a after_request, pero teardown_request no recibe el request objeto. Entonces eso significa que no esperará la solicitud, ¿verdad?

Esta parece ser la solución, como sugiere esta respuesta a una pregunta similar de Stack Overflow. 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 creer esto.

Desafortunadamente, teardown_request sigue siendo sincrónico, solo sucede en una parte posterior del manejo de solicitudes de Flask cuando la solicitud ya no es modificable. Matraz todavía esperará las funciones de desmontaje para completar antes de devolver la respuesta, como dicta esta lista de devoluciones de llamada y errores de Flask.

Respuestas de transmisión de Flask

El matraz puede transmitir respuestas pasando un generador a Response(), como sugiere esta respuesta de Stack Overflow a una pregunta similar.

Con streaming, el cliente lo hace comience a recibir la respuesta antes de que concluya 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 finaliza la transmisión.

Este patrón de matraz para transmisión incluye cierta documentación sobre el uso 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 utilizar una cola de tareas como RQ o Apio. Estos manejan cosas complicadas como la configuración, la programación y la distribución de trabajadores por usted. Esta es la respuesta más común a este tipo de pregunta 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 Python integrado threading o multiprocessing para generar un trabajador en segundo plano.

No puedes acceder request u otros de los subprocesos locales de Flask de las 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(value)

    thread = Thread(target=do_work, kwargs='value': request.args.get('value', 20))
    thread.start()
    return 'started'

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í es responsable solo de construir el objeto iterador de respuesta para el servidor WSGI.

Sin embargo, un servidor WSGI (como gunicorn) puede proporcionar esta funcionalidad muy fácilmente, 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 close método del iterador de respuesta que se ejecuta después de que se cierra la solicitud.

He aquí un ejemplo de un ingenuo after_response implementación 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 enrolle “/” verá lo siguiente en sus registros:

127.0.0.1 - - [24/Jun/2018 19:30:48] "GET / HTTP/1.1" 200 -
hi

Esto resuelve el problema simplemente sin introducir subprocesos (GIL ??) o tener que instalar y administrar una cola de tareas y un software de cliente.

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 apoderarse del contexto de la aplicación usando el proxy current_app. Lea aquí para obtener más información (http://flask.pocoo.org/docs/1.0/appcontext/)

Supongamos que las clases AfterThisResponse y AfterThisResponseMiddleware están ubicadas en un módulo en .utils.after_this_response.py

Luego, donde ocurre 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"

Sección de Reseñas y Valoraciones

Agradecemos que desees estimular nuestro quehacer poniendo un comentario y dejando una puntuación te estamos eternamente agradecidos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *