我最近切换到Celery 3.0。在此之前,我使用Flask-Celery来整合Celery和Flask。虽然它有许多问题,比如隐藏一些强大的Celery功能,但它允许我使用Flask app的完整上下文,特别是Flask-SQLAlchemy。
在我的后台任务中,我正在处理数据,而SQLAlchemy ORM则用于存储数据。 Flask-Celery的维护者已经放弃了对该插件的支持。该插件在任务中挑选了Flask实例,因此我可以完全访问SQLAlchemy。
我正在尝试在tasks.py文件中复制此行为,但没有成功。你有任何关于如何实现这一目标的提示吗?
答案 0 :(得分:60)
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,方法是创建一个单独的文件,该文件使用应用程序的上下文调用celery.start()。这意味着您的任务文件不必充满上下文设置和拆卸。它也适用于烧瓶的“应用工厂”模式。
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()
答案 1 :(得分:5)
在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
答案 2 :(得分:5)
我使用Paul Gibbs' answer有两个不同之处。我使用了worker_process_init而不是task_postrun。而不是.remove()我使用了db.session.expire_all()。
我不是100%肯定,但根据我的理解,这种工作方式是Celery创建工作进程时,所有继承/共享数据库会话都将过期,SQLAlchemy将创建新会话该工人流程特有的需求。
到目前为止似乎解决了我的问题。使用Paul的解决方案,当一个worker完成并删除会话时,另一个使用相同会话的worker仍在运行其查询,因此db.session.remove()在使用它时关闭了连接,给了我一个& #34;查询期间与MySQL服务器的连接丢失"异常。德尔>
感谢保罗带领我朝着正确的方向前进!
没关系,没有用。我最终在我的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)
答案 3 :(得分:2)
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