จะกำหนดเวลาให้ฟังก์ชันทำงานทุกชั่วโมงบน Flask ได้อย่างไร?


99

ฉันมีเว็บโฮสติ้ง Flask ที่ไม่มีสิทธิ์เข้าถึงcronคำสั่ง

ฉันจะเรียกใช้ฟังก์ชัน Python ทุกชั่วโมงได้อย่างไร

คำตอบ:


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 แล้วจะทำงานโดยอัตโนมัติได้อย่างไร
Kishan Mehta

2
ฉันคิดว่ากำหนดการตามที่แนะนำโดย user5547025 มีไว้สำหรับงานซิงโครนัสซึ่งสามารถบล็อกเธรดหลักได้ คุณจะต้องหมุนเธรดผู้ปฏิบัติงานเพื่อไม่ให้บล็อก
Simon

1
หากflaskมีApp.runonceหรือApp.runForNsecondsคุณสามารถสลับไปมาระหว่างscheduleและนักวิ่งขวดได้ แต่นี่ไม่ใช่กรณีดังนั้นวิธีเดียวสำหรับตอนนี้คือใช้สิ่งนี้
lurscher

ขอบคุณสำหรับสิ่งนี้! ฉันจะแทรกฟังก์ชันนี้ที่ไหนภายใต้ if name __ == "__ main " นอกจากนี้เราสามารถแทนที่ฟังก์ชัน print_date_time ด้วยฟังก์ชันของเราได้ใช่ไหม
Ambleu

จะเรียกใช้ตัวกำหนดตารางเวลาทุกวันได้อย่างไร?
arun kumar

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?
Pygmalion

2
เพราะatexit.registerต้องการฟังก์ชันในการโทร ถ้าเราเพิ่งผ่านไปcron.shutdown(wait=False)เราจะส่งผลของการโทรcron.shutdown(ซึ่งอาจเป็นไปได้None) ดังนั้นเราจึงส่งฟังก์ชันที่ไม่มีอาร์กิวเมนต์แทนและแทนที่จะตั้งชื่อและใช้คำสั่ง def shutdown(): cron.shutdown(wait=False)และatexit.register(shutdown)เราจะลงทะเบียนแบบอินไลน์ด้วยlambda:(ซึ่งเป็นนิพจน์ฟังก์ชันที่ไม่มีอาร์กิวเมนต์)
Sean Vieira

ขอบคุณ. ปัญหาคือเราต้องการส่งอาร์กิวเมนต์ไปยังฟังก์ชันถ้าฉันเข้าใจถูก
Pygmalion

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()

ฉันยังออกจากกระทู้นี้ที่นี่ถ้าใครมีความสนใจในการปรับปรุงเช่นนี้

นี่คือข้อมูลอ้างอิงบางส่วนสำหรับการอ่านในอนาคต:


2
วิธีนี้ใช้งานได้ดีหวังว่าจะได้รับการโหวตให้อยู่ในอันดับต้น ๆ เนื่องจากมีคนเห็นกระทู้นี้มากขึ้น
Mwspencer

1
คุณได้ลองใช้สิ่งนี้กับแอปพลิเคชันที่อยู่บนเว็บเช่น PythonAnywhere หรืออะไร?
Mwspencer

1
ขอบคุณ @Mwspencer ใช่ฉันใช้แล้วและใช้งานได้ดี :) แม้ว่าฉันจะแนะนำให้คุณสำรวจตัวเลือกเพิ่มเติมที่มีให้apscheduler.schedulers.backgroundเพราะอาจเป็นไปได้ว่าคุณอาจพบสถานการณ์ที่เป็นประโยชน์อื่น ๆ สำหรับแอปพลิเคชันของคุณ ความนับถือ.
ivanleoncz

2
อย่าลืมปิดตัวกำหนดตารางเวลาเมื่อมีแอปอยู่
Hanynowsky

1
สวัสดี! คุณสามารถให้คำแนะนำสำหรับสถานการณ์ที่มีคนงาน gunicorn หลายคนได้หรือไม่? ฉันหมายถึงตัวกำหนดตารางเวลาจะดำเนินการหนึ่งครั้งต่อคนงานหรือไม่?
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
ฉันแน่ใจว่าคำสั่งคืนสินค้าจะไม่มีข้อยกเว้น
Tamas Hegedus

12

สำหรับวิธีง่ายๆคุณสามารถเพิ่มเส้นทางเช่น

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

จากนั้นเพิ่มงาน unix cronที่ POSTs ไปยังจุดสิ้นสุดนี้เป็นระยะ ตัวอย่างเช่นเรียกใช้นาทีละครั้งในประเภทเทอร์มินัล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
คุณเป็นอัจฉริยะ! เคล็ดลับที่มีประโยชน์จริงๆ
Sharl Sherif

6

อีกทางเลือกหนึ่งคือการใช้ Flask-APScheduler ซึ่งเล่นกับ 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

ฉันไม่ใช่ผู้เชี่ยวชาญในการประมวลผลหลายขั้นตอน แต่ถ้าคุณใช้สิ่งนี้คุณมักจะได้รับข้อผิดพลาดในการดอง
Patrick Mutuku

@PatrickMutuku ปัญหาเดียวที่ฉันเห็นกับ serializing ดิจิตอล (คุกกี้, ไฟล์ temp) เป็น async และ WebSockets แต่แล้วขวดไม่ API ของคุณดูที่github.com/kennethreitz/responder กระติกน้ำมีความโดดเด่นที่ REST ที่บริสุทธิ์ด้วยส่วนหน้าที่เรียบง่ายบน apache wsgi
MortenB

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.