Як запланувати функцію, яка запускається щогодини на Flask?


98

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

Як я можу виконувати якусь функцію Python щогодини?

Відповіді:


104

Ви можете використовувати 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 __ == "__ main "? Також ми можемо замінити функцію print_date_time нашою функцією, чи не так?
Ambleu

Як запустити планувальник щодня раз?
arun kumar

57

Ви можете скористатися 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:(що є виразом функції з нульовим аргументом .)
Шон Вієйра,

Дякую. Отже, проблема полягає в тому, що ми хочемо передати аргумент функції, якщо я правильно розумію.
Пігмаліон

51

Я трохи новачок у концепції планувальників додатків, але те, що я знайшов тут для 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

2
Не забудьте вимкнути планувальник, коли програма існує
Ганиновський

1
Привіт! чи можете ви дати пораду щодо ситуації, коли багато працівників зброї? я маю на увазі, чи планувальник буде виконувати один раз на одного працівника?
ElPapi42,

13

Ви можете спробувати використовувати BackgroundScheduler від APScheduler для інтеграції інтервальних завдань у програму Flask. Нижче наведено приклад, який використовує проект та фабрику додатків ( 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

(Зверніть увагу, що шлях до скручування повинен бути повним, оскільки при запуску завдання він не матиме вашого PATH. Ви можете дізнатися повний шлях до скручування у вашій системі 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 секунд, змінюється на every(2).hour.do()на 2 години. Розклад є досить вражаючим, він не дрейфує, і я ніколи не бачив його більше 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, єдиною проблемою, яку я бачу з цифровою серіалізацією (файли cookie, тимчасові файли), є асинхронізація та веб-розетки, але тоді 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.