如何在Celery任务中使用Flask-SQLAlchemy

时间:2012-08-20 20:38:54

标签: python flask celery flask-sqlalchemy

我最近切换到Celery 3.0。在此之前,我使用Flask-Celery来整合Celery和Flask。虽然它有许多问题,比如隐藏一些强大的Celery功能,但它允许我使用Flask app的完整上下文,特别是Flask-SQLAlchemy。

在我的后台任务中,我正在处理数据,而SQLAlchemy ORM则用于存储数据。 Flask-Celery的维护者已经放弃了对该插件的支持。该插件在任务中挑选了Flask实例,因此我可以完全访问SQLAlchemy。

我正在尝试在tasks.py文件中复制此行为,但没有成功。你有任何关于如何实现这一目标的提示吗?

4 个答案:

答案 0 :(得分:60)

更新:我们已经开始使用更好的方法来处理应用程序拆解并基于所描述的模式in the more recent flask documentation基于每个任务进行设置。

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()
  • 在celeryd init更新celery配置
  • 使用您的烧瓶应用工厂启动所有烧瓶扩展,包括SQLAlchemy扩展。

通过这样做,我们能够维护每个工作者的数据库连接。

如果要在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