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


Ответы:


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 здесь , если у кого-то есть интерес к обновлениям для этого примера.

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


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

Я не эксперт в многопроцессорности, но если вы воспользуетесь этим, вы, скорее всего, получите ошибки рассола.
— Патрик Мутуку

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

1

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.