Come pianificare una funzione da eseguire ogni ora su Flask?


98

Ho un web hosting Flask senza accesso al croncomando.

Come posso eseguire qualche funzione Python ogni ora?

Risposte:


104

È possibile utilizzare BackgroundScheduler()da APScheduler pacchetto (v3.5.3):

import time
import atexit

from apscheduler.schedulers.background import BackgroundScheduler


def print_date_time():
    print(time.strftime("%A, %d. %B %Y %I:%M:%S %p"))


scheduler = BackgroundScheduler()
scheduler.add_job(func=print_date_time, trigger="interval", seconds=3)
scheduler.start()

# Shut down the scheduler when exiting the app
atexit.register(lambda: scheduler.shutdown())

Si noti che due di questi programmi di pianificazione verranno avviati quando Flask è in modalità debug. Per ulteriori informazioni, controlla questa domanda.


1
@ user5547025 Come funziona la pianificazione supponiamo di aver inserito i contenuti in schedule.py come verrà eseguita automaticamente?
Kishan Mehta

2
Penso che la pianificazione suggerita da user5547025 sia per attività sincrone che possono bloccare il thread principale. Dovrai avviare un thread di lavoro affinché non si blocchi.
Simon

1
se flaskavessi un App.runonceo App.runForNsecondspotresti passare da schedulee al flask runner, ma non è questo il caso, quindi l'unico modo per ora è usare questo
lurscher

Grazie per questo! Dove dovrei inserire questa funzione, sotto if name __ == "__ main "? Inoltre possiamo sostituire la funzione print_date_time con la nostra funzione, giusto?
Ambleu

Come eseguire lo scheduler per tutti i giorni una volta?
arun kumar

56

Puoi utilizzarlo APSchedulernella tua applicazione Flask ed eseguire i tuoi lavori tramite la sua interfaccia:

import atexit

# v2.x version - see https://stackoverflow.com/a/38501429/135978
# for the 3.x version
from apscheduler.scheduler import Scheduler
from flask import Flask

app = Flask(__name__)

cron = Scheduler(daemon=True)
# Explicitly kick off the background thread
cron.start()

@cron.interval_schedule(hours=1)
def job_function():
    # Do your work here


# Shutdown your cron thread if the web process is stopped
atexit.register(lambda: cron.shutdown(wait=False))

if __name__ == '__main__':
    app.run()

1
Posso fare una domanda per principianti? Perché c'è lambdadentro atexit.register?
Pigmalione

2
Perché ha atexit.registerbisogno di una funzione da chiamare. Se passassimo appena cron.shutdown(wait=False)passeremmo il risultato della chiamata cron.shutdown(che è probabilmente None). Quindi, invece, passiamo una funzione senza argomenti e invece di darle un nome e usare un'istruzione def shutdown(): cron.shutdown(wait=False) e atexit.register(shutdown)invece la registriamo in linea con lambda:(che è un'espressione di funzione senza argomenti ).
Sean Vieira

Grazie. Quindi il problema è che vogliamo passare l'argomento alla funzione, se ho capito bene.
Pigmalione

49

Sono un po 'nuovo con il concetto di scheduler delle applicazioni, ma quello che ho trovato qui per APScheduler v3.3.1 è qualcosa di leggermente diverso. Credo che per le versioni più recenti, la struttura del pacchetto, i nomi delle classi, ecc. Siano cambiati, quindi sto mettendo qui una nuova soluzione che ho creato di recente, integrata con un'applicazione Flask di base:

#!/usr/bin/python3
""" Demonstrating Flask, using APScheduler. """

from apscheduler.schedulers.background import BackgroundScheduler
from flask import Flask

def sensor():
    """ Function for test purposes. """
    print("Scheduler is alive!")

sched = BackgroundScheduler(daemon=True)
sched.add_job(sensor,'interval',minutes=60)
sched.start()

app = Flask(__name__)

@app.route("/home")
def home():
    """ Function for test purposes. """
    return "Welcome Home :) !"

if __name__ == "__main__":
    app.run()

Lascio anche questo Gist qui , se qualcuno è interessato agli aggiornamenti per questo esempio.

Ecco alcuni riferimenti, per letture future:


2
Funziona alla grande, si spera che venga votato più in alto man mano che più persone vedono questo thread.
Mwspencer

1
Hai provato a usarlo su un'applicazione che si trova sul web come PythonAnywhere o qualcosa del genere?
Mwspencer

1
Grazie, @Mwspencer. Sì, l'ho usato e funziona bene :), anche se ti consiglio di esplorare più opzioni fornite da apscheduler.schedulers.background, perché potrebbe essere possibile che tu possa incontrare altri scenari utili per la tua applicazione. Saluti.
ivanleoncz

2
Non dimenticare di chiudere lo scheduler quando l'app esiste
Hanynowsky

1
Ciao! puoi dare qualche consiglio per una situazione in cui ci sono più lavoratori gunicorn? Voglio dire, lo scheduler verrà eseguito una volta per lavoratore?
ElPapi42

13

Puoi provare a utilizzare BackgroundScheduler di APScheduler per integrare il lavoro a intervalli nella tua app Flask. Di seguito è riportato l'esempio che utilizza blueprint e app factory ( init .py):

from datetime import datetime

# import BackgroundScheduler
from apscheduler.schedulers.background import BackgroundScheduler
from flask import Flask

from webapp.models.main import db 
from webapp.controllers.main import main_blueprint    

# define the job
def hello_job():
    print('Hello Job! The time is: %s' % datetime.now())

def create_app(object_name):
    app = Flask(__name__)
    app.config.from_object(object_name)
    db.init_app(app)
    app.register_blueprint(main_blueprint)
    # init BackgroundScheduler job
    scheduler = BackgroundScheduler()
    # in your case you could change seconds to hours
    scheduler.add_job(hello_job, trigger='interval', seconds=3)
    scheduler.start()

    try:
        # To keep the main thread alive
        return app
    except:
        # shutdown if app occurs except 
        scheduler.shutdown()

Spero che sia d'aiuto :)

Rif:

  1. https://github.com/agronholm/apscheduler/blob/master/examples/schedulers/background.py

1
Sono sicuro che una dichiarazione di ritorno non solleverà mai un'eccezione
Tamas Hegedus

11

Per una soluzione semplice, potresti aggiungere un percorso come

@app.route("/cron/do_the_thing", methods=['POST'])
def do_the_thing():
    logging.info("Did the thing")
    return "OK", 200

Quindi aggiungi un cron job unix che POST periodicamente a questo endpoint. Ad esempio per eseguirlo una volta al minuto, nel tipo di terminale crontab -ee aggiungere questa riga:

* * * * * /opt/local/bin/curl -X POST https://YOUR_APP/cron/do_the_thing

(Nota che il percorso per curl deve essere completo, poiché quando il lavoro viene eseguito non avrà il tuo PATH. Puoi scoprire il percorso completo per curl sul tuo sistema which curl)

Mi piace in quanto è facile testare il lavoro manualmente, non ha dipendenze aggiuntive e poiché non c'è nulla di speciale in corso è facile da capire.

Sicurezza

Se desideri proteggere con password il tuo cron job, puoi pip install Flask-BasicAuth, quindi aggiungere le credenziali alla configurazione dell'app:

app = Flask(__name__)
app.config['BASIC_AUTH_REALM'] = 'realm'
app.config['BASIC_AUTH_USERNAME'] = 'falken'
app.config['BASIC_AUTH_PASSWORD'] = 'joshua'

Per proteggere con password l'endpoint del lavoro:

from flask_basicauth import BasicAuth
basic_auth = BasicAuth(app)

@app.route("/cron/do_the_thing", methods=['POST'])
@basic_auth.required
def do_the_thing():
    logging.info("Did the thing a bit more securely")
    return "OK", 200

Quindi per chiamarlo dal tuo cron job:

* * * * * /opt/local/bin/curl -X POST https://falken:joshua@YOUR_APP/cron/do_the_thing

1
Sei un genio! suggerimento davvero utile.
Sharl Sherif il

6

Un'altra alternativa potrebbe essere quella di utilizzare Flask-APScheduler che funziona bene con Flask, ad esempio:

  • Carica la configurazione dello scheduler dalla configurazione Flask,
  • Carica le definizioni del lavoro dalla configurazione Flask

Maggiori informazioni qui:

https://pypi.python.org/pypi/Flask-APScheduler


4

Un esempio completo che utilizza la pianificazione e il multiprocessing, con controllo di accensione e spegnimento e parametro per run_job () i codici di ritorno sono semplificati e l'intervallo è impostato su 10sec, passare a every(2).hour.do()per 2 ore. La pianificazione è piuttosto impressionante, non si sposta e non l'ho mai vista a più di 100 ms durante la pianificazione. Utilizzo del multiprocessing invece del threading perché ha un metodo di terminazione.

#!/usr/bin/env python3

import schedule
import time
import datetime
import uuid

from flask import Flask, request
from multiprocessing import Process

app = Flask(__name__)
t = None
job_timer = None

def run_job(id):
    """ sample job with parameter """
    global job_timer
    print("timer job id={}".format(id))
    print("timer: {:.4f}sec".format(time.time() - job_timer))
    job_timer = time.time()

def run_schedule():
    """ infinite loop for schedule """
    global job_timer
    job_timer = time.time()
    while 1:
        schedule.run_pending()
        time.sleep(1)

@app.route('/timer/<string:status>')
def mytimer(status, nsec=10):
    global t, job_timer
    if status=='on' and not t:
        schedule.every(nsec).seconds.do(run_job, str(uuid.uuid4()))
        t = Process(target=run_schedule)
        t.start()
        return "timer on with interval:{}sec\n".format(nsec)
    elif status=='off' and t:
        if t:
            t.terminate()
            t = None
            schedule.clear()
        return "timer off\n"
    return "timer status not changed\n"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Puoi testarlo semplicemente emettendo:

$ curl http://127.0.0.1:5000/timer/on
timer on with interval:10sec
$ curl http://127.0.0.1:5000/timer/on
timer status not changed
$ curl http://127.0.0.1:5000/timer/off
timer off
$ curl http://127.0.0.1:5000/timer/off
timer status not changed

Ogni 10 secondi in cui il timer è attivo, emetterà un messaggio di timer alla console:

127.0.0.1 - - [18/Sep/2018 21:20:14] "GET /timer/on HTTP/1.1" 200 -
timer job id=b64ed165-911f-4b47-beed-0d023ead0a33
timer: 10.0117sec
timer job id=b64ed165-911f-4b47-beed-0d023ead0a33
timer: 10.0102sec

Non sono un esperto di multiprocessing, ma se lo usi molto probabilmente otterrai errori di pickle.
Patrick Mutuku

@PatrickMutuku, l'unico problema che vedo con la serializzazione digitale (cookie, file temporanei) è asincrono e websocket, ma Flask non è la tua API, guarda github.com/kennethreitz/responder . Flask eccelle in puro REST con un semplice frontend su apache wsgi.
MortenB

1

Potresti voler usare un meccanismo di coda con uno scheduler come lo scheduler RQ o qualcosa di più pesante come Celery (molto probabilmente un esagerato).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.