Contenidos en streaming

A veces quieres enviar una enorme cantidad de datos al cliente, mucho más de lo que quieres mantener en la memoria. Sin embargo, cuando estás generando los datos sobre la marcha, ¿Cómo los envías de vuelta al cliente sin el viaje de ida y vuelta al sistema de archivos?

La respuesta es utilizar generadores y respuestas directas.

Uso básico

Esta es una función de vista básica que genera un montón de datos CSV sobre la marcha. El truco es tener una función interna que utilice un generador para generar los datos y luego invocar esa función y pasarla a un objeto de respuesta:

@app.route('/large.csv')
def generate_large_csv():
    def generate():
        for row in iter_all_rows():
            yield f"{','.join(row)}\n"
    return generate(), {"Content-Type": "text/csv")

Cada expresión yield se envía directamente al navegador. Sin embargo, ten en cuenta que algunos middlewares WSGI pueden romper el streaming, así que ten cuidado en entornos de depuración con perfiladores y otras cosas que puedas tener activadas.

Streaming desde plantillas

El motor de plantillas de Jinja2 soporta la representación de una plantilla pieza por pieza, devolviendo un iterador de cadenas. Flask proporciona las funciones stream_template() y stream_template_string() para facilitar su uso.

from flask import stream_template

@app.get("/timeline")
def timeline():
    return stream_template("timeline.html")

Las partes producidas por el flujo de renderización tienden a coincidir con los bloques de declaraciones en la plantilla.

Streaming con Contexto

El request` no estará activo mientras el generador se esté ejecutando, porque la vista ya ha regresado en ese momento. Si intentas acceder a request, obtendrás un RuntimeError.

If your generator function relies on data in request, use the stream_with_context() wrapper. This will keep the request context active during the generator.

from flask import stream_with_context, request
from markupsafe import escape

@app.route('/stream')
def streamed_response():
    def generate():
        yield '<p>Hello '
        yield escape(request.args['name'])
        yield '!</p>'
    return stream_with_context(generate())

También se puede utilizar como decorador.

@stream_with_context
def generate():
    ...

return generate()

Las funciones stream_template() y stream_template_string() utilizan automáticamente stream_with_context() si hay una petición activa.