У меня есть веб-хостинг 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. Ниже приведен пример, в котором используются 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()
Надеюсь, это поможет :)
Ссылка:
В качестве простого решения вы можете добавить маршрут, например
@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
Другой альтернативой может быть использование Flask-APScheduler, который хорошо работает с Flask, например:
Больше информации здесь:
Полный пример с использованием расписания и многопроцессорной обработки, с включением и выключением и параметром 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
Возможно, вы захотите использовать какой-либо механизм очереди с планировщиком, например планировщик RQ, или что-то более тяжелое, например Celery (скорее всего, излишек).