Flaskで1時間ごとに実行する関数をスケジュールするにはどうすればよいですか?


98

cronコマンドにアクセスできないFlask Webホスティングを使用しています。

1時間ごとに一部のPython関数を実行するにはどうすればよいですか?

回答:


104

APSchedulerパッケージ(v3.5.3)BackgroundScheduler()から使用できます。

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

これらのスケジューラのうち2つは、Flaskがデバッグモードのときに起動されることに注意してください。詳細については、この質問を確認してください。


1
@ user5547025 schedule.pyにコンテンツを配置した場合、スケジュールはどのように機能しますか?
Kishan Mehta 2017年

2
user5547025によって提案されたスケジュールは、マスタースレッドをブロックする可能性がある同期タスク用であると思います。ブロックしないようにするには、ワーカースレッドを起動する必要があります。
Simon

1
flaskあった場合、App.runonceまたはフラスコランナーをApp.runForNseconds切り替えることができますがschedule、これはそうではないので、今のところ唯一の方法はこれを使用することです
lurscher

これをありがとう!if name __ == "__ main "の下にこの関数をどこに挿入しますか?また、print_date_time関数を関数に置き換えることもできますか?
Ambleu

毎日のスケジューラを一度実行する方法は?
arun kumar

56

APSchedulerFlaskアプリケーションでを利用し、そのインターフェースを介してジョブを実行できます。

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:(ゼロ引数関数である表現。)
ショーン・ビエラ

ありがとう。問題は、私が正しく理解している場合、関数に引数を渡したいということです。
ピグマリオン

49

私はアプリケーションスケジューラの概念については少し新しいですが、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などのWeb上にあるアプリケーションでこれを使用してみましたか?
Mwspencer

1
ありがとう、@ Mwspencer。はい、使用しましたが、正常に動作します:)。ただしapscheduler.schedulers.background、アプリケーションで他の有用なシナリオが発生する可能性があるため、で提供されるオプションをさらに検討することをお勧めします。よろしく。
ivanleoncz

2
アプリが存在する場合はスケジューラをシャットダウンすることを忘れないでください
Hanynowsky

1
こんにちは!複数の軍人労働者がいる状況についていくつかアドバイスをいただけますか?つまり、スケジューラはワーカーごとに1回実行されますか?
ElPapi42

13

APSchedulerのBackgroundSchedulerを使用して、間隔ジョブを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
returnステートメントによって例外が発生することはないと確信しています
Tamas Hegedus

11

簡単な解決策として、次のようなルートを追加できます

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

次にこのエンドポイントに定期的にPOST するUNIX cronジョブ追加します。たとえば、ターミナルタイプで1分に1回実行し、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


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、デジタルシリアライゼーション(Cookie、一時ファイル)で発生する唯一の問題は非同期およびWebSocketですが、FlaskはAPIではありません。github.com/ kennethreitz / responderを参照してください。Flaskは、Apache wsgiのシンプルなフロントエンドを備えた純粋なRESTに優れています。
MortenB

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.