Как запланировать выполнение функции каждый час во Flask?

99

У меня есть веб-хостинг Flask без доступа к cronкомандам.

Как я могу выполнять какую-нибудь функцию Python каждый час?

РомаВальцер
источник

Ответы:

107

Вы можете использовать BackgroundScheduler()из APScheduler пакета (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())

Обратите внимание, что два из этих планировщиков будут запущены, когда Flask находится в режиме отладки. Для получения дополнительной информации ознакомьтесь с этим вопросом.

Туомастик
источник
1
@ user5547025 Как работает расписание. Предположим, я поместил содержимое в schedule.py, как оно будет выполняться автоматически?
Кишан Мета
2
Я думаю, что расписание, предложенное пользователем 5547025, предназначено для синхронных задач, которые могут блокировать главный поток. Вам нужно будет развернуть рабочий поток, чтобы он не блокировался.
Саймон
1
если бы flaskу вас был App.runonceили App.runForNsecondsвы могли бы переключаться между scheduleи бегуном фляги, но это не так, поэтому единственный способ на данный момент использовать это
lurscher
Спасибо за это! Куда мне вставить эту функцию, в if name __ == "__ main "? Также мы можем заменить функцию print_date_time на нашу, верно?
Ambleu
Как запустить планировщик на каждый день один раз?
Арун Кумар
59

Вы можете использовать его APSchedulerв своем приложении Flask и запускать свои задания через его интерфейс:

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
Могу я задать вопрос новичку? Почему lambdaвнутри atexit.register?
Пигмалион
2
Потому что atexit.registerнужна функция для вызова. Если бы мы просто прошли, cron.shutdown(wait=False)мы бы передали результат вызова cron.shutdown(что, вероятно, так и есть None). Поэтому вместо этого мы передаем функцию с нулевым аргументом, и вместо того, чтобы давать ей имя и использовать оператор, def shutdown(): cron.shutdown(wait=False) и atexit.register(shutdown)вместо этого мы регистрируем ее в строке с lambda:(что является выражением функции с нулевым аргументом .)
Шон Виейра,
Спасибо. Итак, проблема в том, что мы хотим передать аргумент функции, если я правильно понимаю.
Пигмалион
52

Я немного новичок в концепции планировщиков приложений, но то, что я нашел здесь для APScheduler v3.3.1 , немного отличается. Я считаю, что для новейших версий структура пакета, имена классов и т. Д. Изменились, поэтому я помещаю здесь свежее решение, которое я недавно сделал, интегрированное с базовым приложением Flask:

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

Я также оставляю этот Gist здесь , если у кого-то есть интерес к обновлениям для этого примера.

Вот несколько ссылок для будущих чтений:

ivanleoncz
источник
2
Это отлично работает, надеюсь, за него проголосуют все выше, чем больше людей увидят эту ветку.
Mwspencer
1
Вы пробовали использовать это в приложении, которое находится в Интернете, таком как PythonAnywhere или что-то в этом роде?
Mwspencer
1
Спасибо, @Mwspencer. Да, я использовал, и он отлично работает :), хотя я рекомендую вам изучить дополнительные варианты, предоставляемые apscheduler.schedulers.background, поскольку возможно, вы можете столкнуться с другими полезными сценариями для вашего приложения. С уважением.
ivanleoncz 05
2
Не забудьте выключить планировщик, когда приложение существует
Ханиновский
1
Здравствуйте! Можете ли вы дать совет для ситуации, когда есть несколько рабочих-пулеметчиков? Я имею в виду, будет ли планировщик выполняться один раз для каждого рабочего?
ElPapi42,
13

Вы можете попробовать использовать BackgroundScheduler APScheduler для интеграции интервального задания в ваше приложение Flask. Ниже приведен пример, в котором используются blueprint и 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()

Надеюсь, это поможет :)

Ссылка:

  1. https://github.com/agronholm/apscheduler/blob/master/examples/schedulers/background.py
К.Д. Чанг
источник
1
Я уверен, что ответное заявление никогда не вызовет исключения
Тамас Хегедус
12

В качестве простого решения вы можете добавить маршрут, например

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

Затем добавьте задание unix cron, которое периодически отправляет POST на эту конечную точку. Например, чтобы запускать его раз в минуту, в терминальном типе crontab -eи добавьте эту строку:

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

(Обратите внимание, что путь к curl должен быть полным, поскольку при запуске задания у него не будет вашего PATH. Вы можете узнать полный путь к curl в вашей системе which curl)

Мне это нравится тем, что легко протестировать задание вручную, в нем нет дополнительных зависимостей и, поскольку в нем нет ничего особенного, его легко понять.

Безопасность

Если вы хотите защитить паролем свое задание cron, вы можете pip install Flask-BasicAuth, а затем добавить учетные данные в конфигурацию своего приложения:

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

Чтобы защитить паролем конечную точку задания:

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

Затем, чтобы вызвать его из своей работы cron:

* * * * * /opt/local/bin/curl -X POST https://falken:joshua@YOUR_APP/cron/do_the_thing
Бемму
источник
1
Ты гений! действительно полезный совет.
Шарл Шериф
6

Другой альтернативой может быть использование Flask-APScheduler, который хорошо работает с Flask, например:

  • Загружает конфигурацию планировщика из конфигурации Flask,
  • Загружает определения заданий из конфигурации Flask

Больше информации здесь:

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

Мэдс Дженсен
источник
4

Полный пример с использованием расписания и многопроцессорной обработки, с включением и выключением и параметром run_job (), коды возврата упрощены, а интервал установлен на 10 секунд, измененный на 2 часа every(2).hour.do(). График впечатляет, он не дрейфует, и я никогда не видел, чтобы он отклонялся более чем на 100 мс при планировании. Использование многопроцессорной обработки вместо многопоточности, поскольку она имеет метод завершения.

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

Вы проверяете это, просто выдав:

$ 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

Каждые 10 секунд таймер будет выдавать на консоль сообщение таймера:

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
MortenB
источник
Я не эксперт в многопроцессорности, но если вы воспользуетесь этим, вы, скорее всего, получите ошибки рассола.
Патрик Мутуку
@PatrickMutuku, единственная проблема, которую я вижу с цифровой сериализацией (куки, временные файлы), - это асинхронность и веб-сокеты, но тогда Flask - это не ваш api, посмотрите github.com/kennethreitz/responder . Flask превосходит чистый REST с простым интерфейсом на apache wsgi.
MortenB
1

Возможно, вы захотите использовать какой-либо механизм очереди с планировщиком, например планировщик RQ, или что-то более тяжелое, например Celery (скорее всего, излишек).

Александр Давыдов
источник