使用Celery工作者与SQLAlchemy数据库进行交互,包括从请求中了解用户

时间:2019-02-22 23:36:57

标签: python flask celery flask-sqlalchemy flask-httpauth

我对此进行了大量研究,包括尝试像this之类的答案。看来Celery无法访问我的Flask应用的上下文。

我非常了解我的celery对象(装饰我的任务的东西)必须有权访问Flask应用程序的上下文。而且我确实相信,正如我遵循this指导来创建我的芹菜对象一样。我不确定混淆是否在于我正在使用Flask-HTTPAuth。

这是我的一些东西。

def make_celery(app):
    celery = Celery(app.import_name, backend=app.config["CELERY_RESULT_BACKEND"], broker=app.config["CELERY_BROKER_URL"])
    celery.conf.update(app.config)
    TaskBase = celery.Task
    class ContextTask(TaskBase):
        abstract = True
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return TaskBase.__call__(self, *args, **kwargs)
    celery.Task = ContextTask
    return celery

app = Flask(__name__)
auth = HTTPBasicAuth()
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///flask_app.db"
app.config["CELERY_BROKER_URL"] = "redis://localhost:6379"
app.config["CELERY_RESULT_BACKEND"] = "redis://localhost:6379"
celery = make_celery(app)
db = SQLAlchemy(app)

@celery.task(bind=True, name="flask_app.item_loop")
def loop(self):
    items = g.user.items
    for item in items:
        print(item)

使用Flask运行此任务是不行的。我尝试通过点击服务器(在授权状态下)来启动此功能。

@app.route("/item_loop")
@auth.login_required
def item_loop():
    result = loop.delay()
    return "It's running."

但是Celery工作人员告诉我任务raised unexpected: AttributeError("'_AppCtxGlobals' object has no attribute 'user'",),如上所述,我相信这意味着我的celery对象没有应用程序上下文,即使我使用了推荐的工厂模式。

3 个答案:

答案 0 :(得分:2)

虽然Dave和Greg的答案中的建议是正确的,但他们想强调的是您对Celery任务中使用应用程序上下文的误解。

您有一个Flask应用程序,正在其中使用Flask-HTTPAuth。您可能有一个verify_password处理程序,它为经过身份验证的用户设置了g.user。这意味着在处理请求时,您可以以g.user的身份访问用户。很好。

您还有一个或多个Celery worker,这是独立的进程,与Flask服务器没有直接连接。 Flask服务器与Celery worker进程之间的唯一通信是通过您正在使用的消息代理(通常是Redis或RabbitMQ)进行的。

根据您的需求,Celery工作人员可能需要访问Flask应用程序。当使用Flask扩展将其配置存储在app.config字典中时,这非常常见。需要此的两个常见扩展是Flask-SQLAlchemy和Flask-Mail。如果无法访问app.config,Celery任务将无法打开与数据库的连接或发送电子邮件,因为它将不知道数据库和/或电子邮件服务器的详细信息。

要使Celery工作人员可以访问该配置,可接受的做法是在每个工作人员中创建重复的Flask应用程序。这些是辅助应用程序,它们绝不连接到主Flask服务器使用的实际应用程序对象。它们的唯一目的是保存原始app.config词典的副本,您的任务或任务使用的任何Flask扩展都可以访问该字典。

因此,期望在Flaery服务器中设置的g.user也可以像Celerery任务中的g.user一样访问,只是因为这些是不同的g对象,应用程序实例。

如果您需要在Celery任务中使用经过身份验证的用户,则应该将user_id(通常为g.user.id)作为任务的参数传递。然后,在您的任务中,您可以使用此id从数据库中加载用户。希望这会有所帮助!

答案 1 :(得分:1)

要从任务执行中检索用户,您可以尝试传递User对象(如果芹菜可以腌制),或者传递足够的信息以使任务可以检索User对象(例如,User ID)。在后一种情况下,您的任务将类似于

@celery.task(bind=True, name="flask_app.item_loop")
def loop(self, user_id):
    user = User.query.get(user_id)
    items = user.items
    for item in items:
        print(item)

,您将通过以下方式开始(假设您使用的是flask_login)

result = loop.delay(current_user.id)

答案 2 :(得分:1)

如@Dave W. Smith所述,与其依赖g来获取用户,不如将用户信息作为参数传递给Celery任务可能是一种更好的方法。根据{{​​3}},g的生存期是一个请求。由于Celery任务是异步执行的,因此它将在与您定义用户的请求中不同的应用程序上下文中执行。