Làm cách nào để lên lịch một chức năng chạy mỗi giờ trên Flask?


98

Tôi có một lưu trữ web Flask không có quyền truy cập vào cronlệnh.

Làm cách nào tôi có thể thực thi một số hàm Python mỗi giờ?

Câu trả lời:


104

Bạn có thể sử dụng BackgroundScheduler()từ gói 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())

Lưu ý rằng hai trong số các bộ lập lịch này sẽ được khởi chạy khi Flask ở chế độ gỡ lỗi. Để biết thêm thông tin, hãy kiểm tra câu hỏi này .


1
@ user5547025 Lịch biểu hoạt động như thế nào, giả sử tôi đã đưa nội dung vào history.py làm cách nào để lịch biểu chạy tự động?
Kishan Mehta

2
Tôi nghĩ rằng lịch biểu do user5547025 đề xuất là dành cho các tác vụ đồng bộ có thể chặn luồng chính. Bạn sẽ cần quay một luồng công nhân để nó không bị chặn.
Simon,

1
nếu flaskcó một App.runoncehoặc App.runForNsecondsbạn có thể chuyển đổi giữa schedulevà Á hậu bình, nhưng đây không phải là trường hợp, vì vậy cách duy nhất cho bây giờ đang sử dụng này
lurscher

Cảm ơn vì điều đó! Tôi sẽ chèn hàm này vào đâu, dưới if tên __ == "__ main "? Ngoài ra chúng ta có thể thay thế hàm print_date_time bằng hàm của mình đúng không?
Ambleu

Làm thế nào để chạy công cụ lập lịch mỗi ngày một lần?
arun kumar

57

Bạn có thể sử dụng APSchedulerứng dụng Flask của mình và thực hiện công việc của mình thông qua giao diện của nó:

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
Tôi có thể hỏi một câu hỏi mới làm quen không? Tại sao có lambdabên trong atexit.register?
Pygmalion

2
atexit.registercần một hàm để gọi. Nếu chúng tôi vừa vượt qua, cron.shutdown(wait=False)chúng tôi sẽ chuyển kết quả của việc gọi cron.shutdown(có thể là như vậy None). Vì vậy, thay vào đó, chúng tôi vượt qua một hàm không có đối số, và thay vì cho nó một cái tên và sử dụng một tuyên bố def shutdown(): cron.shutdown(wait=False)atexit.register(shutdown)chúng tôi thay vì đăng ký nó inline với lambda:(mà là một chức năng không có đối số biểu hiện .)
Sean Vieira

Cảm ơn. Vì vậy, vấn đề là chúng ta muốn truyền đối số cho hàm, nếu tôi hiểu đúng.
Pygmalion

51

Tôi hơi mới với khái niệm về bộ lập lịch ứng dụng, nhưng những gì tôi tìm thấy ở đây cho APScheduler v3.3.1 , nó hơi khác một chút. Tôi tin rằng đối với các phiên bản mới nhất, cấu trúc gói, tên lớp, v.v., đã thay đổi, vì vậy tôi đang đặt ở đây một giải pháp mới mà tôi đã thực hiện gần đây, được tích hợp với ứng dụng Flask cơ bản:

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

Tôi cũng đang để Gist này ở đây , nếu ai đó quan tâm đến các bản cập nhật cho ví dụ này.

Dưới đây là một số tài liệu tham khảo, cho các bài đọc trong tương lai:


2
Điều này hoạt động tuyệt vời, hy vọng nó sẽ được bình chọn cao hơn ở đầu khi nhiều người xem chủ đề này hơn.
Mwspencer

1
Bạn đã thử sử dụng điều này trên một ứng dụng nằm trên web như PythonAnywhere hay thứ gì đó chưa?
Mwspencer

1
Cảm ơn, @Mwspencer. Có, tôi đã sử dụng và nó hoạt động tốt :), mặc dù tôi khuyên bạn nên khám phá thêm các tùy chọn được cung cấp bởi apscheduler.schedulers.backgroundvì có thể bạn có thể gặp phải các tình huống hữu ích khác cho ứng dụng của mình. Trân trọng.
ivanleoncz

2
Đừng quên tắt trình lên lịch khi ứng dụng tồn tại
Hanynowsky

1
Xin chào! bạn có thể đưa ra một số lời khuyên cho tình huống có nhiều công nhân gunicorn không? ý tôi là, bộ lập lịch sẽ thực thi một lần cho mỗi công nhân?
ElPapi42

13

Bạn có thể thử sử dụng BackgroundScheduler của APScheduler để tích hợp công việc ngắt quãng vào ứng dụng Flask của mình. Dưới đây là ví dụ sử dụng bản thiết kế và nhà máy ứng dụng ( 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()

Hy vọng nó giúp :)

Tham khảo:

  1. https://github.com/agronholm/apscheduler/blob/master/examples/schedulers/background.py

1
Tôi chắc chắn rằng một tuyên bố trở lại sẽ không bao giờ tăng một ngoại lệ
Tamas Hegedus

12

Đối với một giải pháp đơn giản, bạn có thể thêm một tuyến đường như

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

Sau đó, thêm một công việc unix cron mà POST đến điểm cuối này theo định kỳ. Ví dụ: chạy nó mỗi phút một lần, trong loại thiết bị đầu cuối crontab -evà thêm dòng này:

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

(Lưu ý rằng đường dẫn để cuộn phải hoàn chỉnh, vì khi công việc chạy nó sẽ không có PATH của bạn. Bạn có thể tìm hiểu đường dẫn đầy đủ để cuộn trên hệ thống của mình bằng cách which curl)

Tôi thích điều này vì nó dễ dàng để kiểm tra công việc theo cách thủ công, nó không có phụ thuộc bổ sung và vì không có bất kỳ điều gì đặc biệt xảy ra nên rất dễ hiểu.

Bảo vệ

Nếu bạn muốn đặt mật khẩu bảo vệ công việc cron của mình, bạn có thể pip install Flask-BasicAuth, sau đó thêm thông tin đăng nhập vào cấu hình ứng dụng của bạn:

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

Để bảo vệ bằng mật khẩu cho điểm cuối công việc:

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

Sau đó, để gọi nó từ công việc cron của bạn:

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

1
Bạn là một thiên tài! mẹo thực sự tiện dụng.
Sharl Sherif

6

Một giải pháp thay thế khác có thể là sử dụng Flask-APScheduler chơi tốt với Flask, ví dụ:

  • Tải cấu hình bộ lập lịch từ cấu hình Flask,
  • Tải các định nghĩa công việc từ cấu hình Flask

Thông tin thêm tại đây:

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


4

Một ví dụ hoàn chỉnh bằng cách sử dụng lịch trình và đa xử lý, với điều khiển bật và tắt và tham số run_job (), mã trả về được đơn giản hóa và khoảng thời gian được đặt thành 10 giây, thay đổi thành 2 giờ every(2).hour.do(). Lịch trình khá ấn tượng, nó không bị trôi và tôi chưa bao giờ thấy nó giảm quá 100ms khi lên lịch. Sử dụng đa xử lý thay vì phân luồng vì nó có phương thức kết thúc.

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

Bạn kiểm tra điều này bằng cách chỉ phát hành:

$ 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

Cứ sau 10 giây, bộ hẹn giờ được bật, nó sẽ đưa ra thông báo hẹn giờ đến bảng điều khiển:

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

Không phải là chuyên gia trong lĩnh vực đa xử lý nhưng nếu bạn sử dụng điều này, rất có thể bạn sẽ gặp phải lỗi kén.
Patrick Mutuku

@PatrickMutuku, vấn đề duy nhất tôi thấy với tuần tự hóa kỹ thuật số (cookie, tệp tạm thời) là không đồng bộ và cổng web, nhưng Flask không phải là api của bạn, hãy xem github.com/kennethreitz/responder . Flask vượt trội ở REST thuần túy với giao diện người dùng đơn giản trên apache wsgi.
MortenB

1

Bạn có thể muốn sử dụng một số cơ chế hàng đợi với bộ lập lịch như bộ lập lịch RQ hoặc một cái gì đó nặng hơn như Cần tây (hầu hết có thể là quá mức cần thiết).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.