У мене є веб-хостинг Flask, який не має доступу до cron
команд.
Як я можу виконувати якусь функцію Python щогодини?
У мене є веб-хостинг Flask, який не має доступу до cron
команд.
Як я можу виконувати якусь функцію Python щогодини?
Відповіді:
Ви можете використовувати 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 перебуває в режимі налагодження. Для отримання додаткової інформації перегляньте це питання.
flask
у вас є App.runonce
або App.runForNseconds
ви можете перемикатися між schedule
бігунком колби, але це не так, тому єдиний спосіб на даний момент - це використання
Ви можете скористатися 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()
lambda
всередині atexit.register
?
atexit.register
потрібна функція для виклику. Якби ми просто пройшли, cron.shutdown(wait=False)
ми передавали б результат виклику cron.shutdown
(що, ймовірно, є None
). Отже, замість цього ми передаємо функцію з нульовим аргументом і замість того, щоб давати їй ім’я та використовувати оператор, def shutdown(): cron.shutdown(wait=False)
а atexit.register(shutdown)
замість цього реєструємо її в рядку lambda:
(що є виразом функції з нульовим аргументом .)
Я трохи новачок у концепції планувальників додатків, але те, що я знайшов тут для 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 , якщо хтось цікавиться оновленнями для цього прикладу.
Ось кілька посилань для подальших читань:
apscheduler.schedulers.background
, оскільки можливо, ви можете зіткнутися з іншими корисними сценаріями для вашої програми. З повагою.
Ви можете спробувати використовувати 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()
Сподіваюся, це допоможе :)
Посилання:
Для простого рішення ви можете додати такий маршрут, як
@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
Іншою альтернативою може бути використання Flask-APScheduler, який чудово грає з Flask, наприклад:
Більше інформації тут:
Повний приклад використання розкладу та багатопроцесорної обробки, з керуванням увімкнення та вимкнення та параметром 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
Можливо, ви захочете використати якийсь механізм черги з планувальником, таким як планувальник RQ, або щось більш важке, як Celery (швидше за все, надмірне).