最近、Celery 3.0に切り替えました。その前は、CeleryをFlaskと統合するために Flask-Celery を使用していました。いくつかの強力なセロリ機能を隠すなど多くの問題がありましたが、Flaskアプリ、特にFlask-SQLAlchemyの完全なコンテキストを使用することができました。
私のバックグラウンドタスクでは、データとデータを格納するSQLAlchemy ORMを処理しています。 Flask-Celeryのメンテナはプラグインのサポートをやめました。プラグインはタスクのFlaskインスタンスを酸洗いしていたので、SQLAlchemyへの完全なアクセス権を持つことができました。
この動作をtasks.pyファイルに再現しようとしていますが、成功しません。これを達成する方法についてのヒントはありますか?
extensions.py
_import flask
from flask.ext.sqlalchemy import SQLAlchemy
from celery import Celery
class FlaskCelery(Celery):
def __init__(self, *args, **kwargs):
super(FlaskCelery, self).__init__(*args, **kwargs)
self.patch_task()
if 'app' in kwargs:
self.init_app(kwargs['app'])
def patch_task(self):
TaskBase = self.Task
_celery = self
class ContextTask(TaskBase):
abstract = True
def __call__(self, *args, **kwargs):
if flask.has_app_context():
return TaskBase.__call__(self, *args, **kwargs)
else:
with _celery.app.app_context():
return TaskBase.__call__(self, *args, **kwargs)
self.Task = ContextTask
def init_app(self, app):
self.app = app
self.config_from_object(app.config)
celery = FlaskCelery()
db = SQLAlchemy()
_
app.py
_from flask import Flask
from extensions import celery, db
def create_app():
app = Flask()
#configure/initialize all your extensions
db.init_app(app)
celery.init_app(app)
return app
_
この方法でアプリを設定すると、アプリケーションコンテキスト内から明示的に実行する必要なく、セロリを実行して使用できます。すべてのタスクは、必要に応じてアプリケーションコンテキストで自動的に実行されます。管理する重要な問題である、タスク後の分解について明示的に心配する(以下の他の応答を参照)。
with _celery.app.app_context(): AttributeError: 'FlaskCelery' object has no attribute 'app'
を取得し続ける人は、次のことを確認してください。
1)celery
インポートを_app.py
_ファイルレベルで維持します。避ける:
app.py
_from flask import Flask
def create_app():
app = Flask()
initiliaze_extensions(app)
return app
def initiliaze_extensions(app):
from extensions import celery, db # DOOMED! Keep celery import at the FILE level
db.init_app(app)
celery.init_app(app)
_
2)_flask run
_の前にセロリワーカーを開始して使用する
_celery worker -A app:celery -l info -f celery.log
_
_app:celery
_、つまり_app.py
_からのロードに注意してください。
拡張機能からインポートしてタスクを装飾することもできます。つまり、_from extensions import celery
_です。
アプリケーションのコンテキストでcelery.start()を呼び出す別のファイルを作成して、アプリケーションコンテキスト内ですべてのceleryを実行することを好みます。これは、タスクファイルをコンテキストのセットアップや破棄で散らかす必要がないことを意味します。また、flask 'application factory'パターンに適しています。
extensions.py
_from from flask.ext.sqlalchemy import SQLAlchemy
from celery import Celery
db = SQLAlchemy()
celery = Celery()
_
tasks.py
_from extensions import celery, db
from flask.globals import current_app
from celery.signals import task_postrun
@celery.task
def do_some_stuff():
current_app.logger.info("I have the application context")
#you can now use the db object from extensions
@task_postrun.connect
def close_session(*args, **kwargs):
# Flask SQLAlchemy will automatically create new sessions for you from
# a scoped session factory, given that we are maintaining the same app
# context, this ensures tasks have a fresh session (e.g. session errors
# won't propagate across tasks)
db.session.remove()
_
app.py
_from extensions import celery, db
def create_app():
app = Flask()
#configure/initialize all your extensions
db.init_app(app)
celery.config_from_object(app.config)
return app
_
RunCelery.py
_from app import create_app
from extensions import celery
app = create_app()
if __name__ == '__main__':
with app.app_context():
celery.start()
_
Tasks.pyファイルで、次の操作を行います。
from main import create_app
app = create_app()
celery = Celery(__name__)
celery.add_defaults(lambda: app.config)
@celery.task
def create_facet(project_id, **kwargs):
with app.test_request_context():
# your code
私は Paul Gibbsの回答 を使用しましたが、2つの違いがあります。 task_postrunの代わりに、worker_process_initを使用しました。そして、.remove()の代わりに、db.session.expire_all()を使用しました。
100%確信はありませんが、これが機能する方法を理解しているところによると、Celeryがワーカープロセスを作成すると、継承/共有されたすべてのdbセッションが期限切れになり、SQLAlchemyはそのワーカープロセスに固有のオンデマンドで新しいセッションを作成します。
これまでのところ、問題は解決したようです。 Paulのソリューションでは、あるワーカーがセッションを終了して削除したときに、同じセッションを使用している別のワーカーがまだクエリを実行していたため、db.session.remove()が使用中に接続を閉じ、「MySQLへの接続が失われましたクエリ中のサーバー」例外。
正しい方向に導いてくれたPaulに感謝します。
うまくいきませんでした。 Flaskアプリファクトリで、Celeryがそれを呼び出している場合、db.init_app(app)を実行しないように引数を指定しました。代わりに、Celeryがフォークした後、ワーカーがそれを呼び出します。 MySQLプロセスリストの接続。
from extensions import db
from celery.signals import worker_process_init
from flask import current_app
@worker_process_init.connect
def celery_worker_init_db(**_):
db.init_app(current_app)
from flask import Flask
from werkzeug.utils import import_string
from celery.signals import worker_process_init, celeryd_init
from flask_celery import Celery
from src.app import config_from_env, create_app
celery = Celery()
def get_celery_conf():
config = import_string('src.settings')
config = {k: getattr(config, k) for k in dir(config) if k.isupper()}
config['BROKER_URL'] = config['CELERY_BROKER_URL']
return config
@celeryd_init.connect
def init_celeryd(conf=None, **kwargs):
conf.update(get_celery_conf())
@worker_process_init.connect
def init_celery_flask_app(**kwargs):
app = create_app()
app.app_context().Push()
これにより、ワーカーごとのデータベース接続を維持できます。
flaskコンテキストでタスクを実行する場合は、Task.__call__
:
class SmartTask(Task):
abstract = True
def __call__(self, *_args, **_kwargs):
with self.app.flask_app.app_context():
with self.app.flask_app.test_request_context():
result = super(SmartTask, self).__call__(*_args, **_kwargs)
return result
class SmartCelery(Celery):
def init_app(self, app):
super(SmartCelery, self).init_app(app)
self.Task = SmartTask