Redis
 sql >> Database >  >> NoSQL >> Redis

Come faccio a restituire flask render_template dopo che il lavoro in background Redis è terminato?

Una soluzione semplice ma praticabile (estratto):

Puoi farlo semplicemente reindirizzando dal percorso che accoda il lavoro, quindi fare in modo che un meta tag aggiorni periodicamente quella pagina. Prima importa le librerie richieste:

from flask import Flask, redirect, url_for, render_template_string
app = Flask(__name__)

from time import sleep

from rq import Queue
from rq.job import Job
from redis import Redis

Imposta le connessioni relative a rq e definisci la funzione da eseguire:

r = Redis(host='redisserver')
q = Queue(connection=r)

def slow_func(data):
    sleep(5)
    return 'Processed %s' % (data,)

Quindi definisci un modello che può aggiornare la pagina ogni 5 secondi:

template_str='''<html>
    <head>
      {% if refresh %}
        <meta http-equiv="refresh" content="5">
      {% endif %}
    </head>
    <body>{{result}}</body>
    </html>'''

Creeremo anche una funzione di supporto per restituire quel modello con una variabile inserita, usando flask render_template_string . Si noti che l'aggiornamento predefinito è False, se non fornito:

def get_template(data, refresh=False):
    return render_template_string(template_str, result=data, refresh=refresh)

Ora crea un percorso che accoderà la nostra funzione, otterrà il suo rq job-id, quindi restituirà un reindirizzamento al result visualizzare con quell'id . Questo prende solo l'input nella stringa URL, ma potrebbe ottenerlo da qualsiasi luogo:

@app.route('/process/<string:data>')
def process(data):
    job = q.enqueue(slow_func, data)
    return redirect(url_for('result', id=job.id))

Ora gestiamo il risultato vero e proprio, con l'aiuto del rq.Job oggetto. La logica qui potrebbe essere modificata, poiché ciò causerà un aggiornamento della pagina su tutti i valori tranne "finished" :

@app.route('/result/<string:id>')
def result(id):
    job = Job.fetch(id, connection=r)
    status = job.get_status()
    if status in ['queued', 'started', 'deferred', 'failed']:
        return get_template(status, refresh=True)
    elif status == 'finished':
        result = job.result 
        # If this is a string, we can simply return it:
        return get_template(result)

Se lo stato è "finished" quindi job.result conterrà il valore di ritorno di slow_func , quindi lo rendiamo sulla pagina.

Questo metodo ha lo svantaggio di causare diverse richieste al server, in attesa del completamento del lavoro. Il meta tag di aggiornamento potrebbe essere un po' non convenzionale. Se stai inviando la richiesta di aggiornamento da Javascript, ci sono soluzioni che possono inviare la richiesta AJAX a intervalli, anche se questo soffre dello stesso problema di richieste multiple.

L'alternativa è utilizzare websocket o SSE per trasmettere in streaming il risultato del lavoro completato al frontend non appena viene completato.

AGGIORNAMENTO:27 febbraio 2021

Ho deciso di provare il metodo SSE per aggiornare il frontend con lo stato del lavoro. Ho imparato che rq ha il supporto nativo per l'aggiornamento di un meta attributo all'interno del lavoro, importando rq.get_current_job all'interno del lavoro, a cui è possibile accedere dall'esterno dopo l'aggiornamento del lavoro.

Vedi il codice dimostrativo per:

Un esempio di base con una barra di avanzamento (gist):