ChatGPT解决这个技术问题 Extra ChatGPT

How to schedule a function to run every hour on Flask?

I have a Flask web hosting with no access to cron command.

How can I execute some Python function every hour?


D
Dewsworld

You can use BackgroundScheduler() from APScheduler package (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=60)
scheduler.start()

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

Note that two of these schedulers will be launched when Flask is in debug mode. For more information, check out this question.


@user5547025 How does the schedule work suppose i have put the contents in schedule.py how will it run automatically?
I think schedule as suggested by user5547025 is for synchronous tasks which can block the master thread. You will need to spin up a worker thread for it not to block.
if flask had an App.runonce or App.runForNseconds you could switch between schedule and the flask runner, but this is not the case, so the only way for now is using this
How to run scheduler for everyday once?
@Dewsworld why use lambda on the last line? Why not instead of atexit.register(lambda: scheduler.shutdown()) do just atexit.register(scheduler.shutdown)
i
ivanleoncz

I'm a little bit new with the concept of application schedulers, but what I found here for APScheduler v3.3.1 , it's something a little bit different. I believe that for the newest versions, the package structure, class names, etc., have changed, so I'm putting here a fresh solution which I made recently, integrated with a basic Flask application:

#!/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()

I'm also leaving this Gist here, if anyone have interest on updates for this example.

Here are some references, for future readings:

APScheduler Doc: https://apscheduler.readthedocs.io/en/latest/

daemon=True: https://docs.python.org/3.4/library/threading.html#thread-objects


This works great, hopefully it will get voted higher to the top as more people see this thread.
Have your tried using this on an application that sits on the web such as PythonAnywhere or something?
Thanks, @Mwspencer. Yes, I have used and it works fine :), although I recommend you to explore more options provided by apscheduler.schedulers.background, for it might be possible that you could encounter other useful scenarios for your application. Regards.
Don't forget to shutdown the scheduler when the app exists
Hello! can you give some advice for a situation where there are multiple gunicorn workers? i mean, will the scheduler execute once per worker?
o
okandas

You could make use of APScheduler in your Flask application and run your jobs via its interface:

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()

May I ask a novice question? Why is there lambda within atexit.register?
Because atexit.register needs a function to call. If we just passed cron.shutdown(wait=False) we'd be passing the result of calling cron.shutdown (which is probably None). So instead, we pass a zero-argument function, and instead of giving it a name and using a statement def shutdown(): cron.shutdown(wait=False) and atexit.register(shutdown) we instead register it inline with lambda: (which is a zero-argument function expression.)
Thanks. So the problem is that we we want to pass argument to the function, if I understand right.
C
Chris

I've tried using flask instead of a simple apscheduler what you need to install is

pip3 install flask_apscheduler

Below is the sample of my code:

from flask import Flask
from flask_apscheduler import APScheduler

app = Flask(__name__)
scheduler = APScheduler()

def scheduleTask():
    print("This test runs every 3 seconds")

if __name__ == '__main__':
    scheduler.add_job(id = 'Scheduled Task', func=scheduleTask, trigger="interval", seconds=3)
    scheduler.start()
    app.run(host="0.0.0.0")

B
Bemmu

For a simple solution, you could add a route such as

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

Then add a unix cron job that POSTs to this endpoint periodically. For example to run it once a minute, in terminal type crontab -e and add this line:

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

(Note that the path to curl has to be complete, as when the job runs it won't have your PATH. You can find out the full path to curl on your system by which curl)

I like this in that it's easy to test the job manually, it has no extra dependencies and as there isn't anything special going on it is easy to understand.

Security

If you'd like to password protect your cron job, you can pip install Flask-BasicAuth, and then add the credentials to your app configuration:

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

To password protect the job endpoint:

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

Then to call it from your cron job:

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

The problem with this approach, is that it is hard to document that the app has a scheduled component. That said, I have done this in the past.
@SidKwakkel Including a comment like "this function is called from cron" is helpful, but I know what you mean. I have some code that I wouldn't be able to quickly recall just how (and if) it's being executed periodically.
K
KD Chang

You could try using APScheduler's BackgroundScheduler to integrate interval job into your Flask app. Below is the example that uses blueprint and 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()

Hope it helps :)

Ref :

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


I am sure that a return statement will never raise an exception
M
Mads Jensen

Another alternative might be to use Flask-APScheduler which plays nicely with Flask, e.g.:

Loads scheduler configuration from Flask configuration,

Loads job definitions from Flask configuration

More information here:

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


M
MortenB

A complete example using schedule and multiprocessing, with on and off control and parameter to run_job() the return codes are simplified and interval is set to 10sec, change to every(2).hour.do()for 2hours. Schedule is quite impressive it does not drift and I've never seen it more than 100ms off when scheduling. Using multiprocessing instead of threading because it has a termination method.

#!/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)

You test this by just issuing:

$ 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

Every 10sec the timer is on it will issue a timer message to 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

Am not an expert in multiprocessing but if you use this you will most likely get pickle errors.
@PatrickMutuku, only problem I see with digital serializing(cookies, temp files) is async and websockets, but then Flask is not your api, look at github.com/kennethreitz/responder. Flask excells at pure REST with a simple frontend on apache wsgi.
R
Rishabh Jhalani

You may use flask-crontab module, which is quite easy.

Step 1: pip install flask-crontab

Step 2:

from flask import Flask
from flask_crontab import Crontab

app = Flask(__name__)
crontab = Crontab(app)

Step 3:

@crontab.job(minute="0", hour="6", day="*", month="*", day_of_week="*")
def my_scheduled_job():
    do_something()

Step 4: On cmd, hit

flask crontab add

Done. now simply run your flask application, and you can check your function will call at 6:00 every day.

You may take reference from Here (Official DOc).


A
Alexander Davydov

You might want to use some queue mechanism with scheduler like RQ scheduler or something more heavy like Celery (most probably an overkill).


This is useful for when you want to run jobs either per request or per user account, but for trivial system level tasks, you can always use APScheduler or even just set up a cron job (assuming you're not using Docker for the latter, as that tends to get complex trying to run process managers and setting up loggers).