简体   繁体   English

使用Celery worker与SQLAlchemy DB进行交互,包括从请求中了解用户

[英]Using a Celery worker to interact with a SQLAlchemy DB, including knowing the user from the request

I have done plenty of research on this, including trying answers like this . 我做了这个大量的研究,包括像试图回答这样 It appears Celery has no access to my Flask app's context. 看来Celery无法访问我的Flask应用的上下文。

I know fully well my celery object, what will decorate my tasks, must have access to my Flask app's context. 我完全知道我的celery对象(用来装饰任务的东西)必须有权访问Flask应用程序的上下文。 And I do believe it should, as I followed this guide to create my celery object. 而且我相信它应该,因为我跟着这个向导创建我芹菜对象。 I am unsure if the confusion lies somewhere in the fact that I am using Flask-HTTPAuth. 我不确定混淆是否存在于我正在使用Flask-HTTPAuth的事实中。

Here is some of what I have. 这是我的一些东西。

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)

Running this task using Flask is a no-go, though. 但是,使用Flask运行此任务是不行的。 I try to start this function by hitting the server (while authorized!). 我尝试通过点击服务器来启动此功能(在授权时!)。

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

But the Celery worker tells me the task raised unexpected: AttributeError("'_AppCtxGlobals' object has no attribute 'user'",) , which I believe would imply, as mentioned, my celery object does not have the app context, even though I used the recommended factory pattern. 但是Celery工作人员告诉我,该任务raised unexpected: AttributeError("'_AppCtxGlobals' object has no attribute 'user'",) ,我认为这暗示我的celery对象没有应用程序上下文,即使我使用推荐的工厂模式。

While the recoomendations in Dave and Greg's answers are valid, what they miss to highlight is the misunderstanding that you have regarding the use of an application context in a Celery task. 尽管Dave和Greg的答案中的建议是正确的,但他们没有强调的是您对Celery任务中使用应用程序上下文的误解。

You have a Flask application, in which you are using Flask-HTTPAuth. 您有一个Flask应用程序,在其中使用Flask-HTTPAuth。 You probably have a verify_password handler that sets g.user to the authenticated user. 您可能有一个verify_password处理函数, g.user设置为经过身份验证的用户。 This means that while you are handling a request you can access the user as g.user . 这意味着在处理请求时,您可以以g.user访问用户。 This is all good. 一切都很好。

You also have one or more Celery workers, which are separate processes that have no direct connection to the Flask server. 您也有一个或多个Celery worker,这是独立的进程,与Flask服务器没有直接连接。 The only communication between the Flask server and the Celery worker processes happens over the message broker that you are using (typically Redis or RabbitMQ). Flask服务器和Celery worker进程之间的唯一通信是通过您正在使用的消息代理(通常是Redis或RabbitMQ)进行的。

Depending on your needs, the Celery workers may need to have access to the Flask application. 根据您的需求,芹菜工人可能需要访问Flask应用程序。 This is very common when using Flask extensions that store their configuration in the app.config dictionary. 当使用Flask扩展将其配置存储在app.config词典中时,这非常常见。 Two common extensions that require this are Flask-SQLAlchemy and Flask-Mail. 需要此的两个常见扩展是Flask-SQLAlchemy和Flask-Mail。 Without access to app.config , the Celery task would have no way to open a connection to the database or to send an email, since it would not know the details of the database and/or email server. 如果无法访问app.config ,Celery任务将无法打开与数据库的连接或发送电子邮件,因为它不知道数据库和/或电子邮件服务器的详细信息。

To give the Celery workers access to the configuration, the accepted practice is to create duplicate Flask applications in each worker. 为了使Celery工作人员可以访问配置,公认的做法是在每个工作人员中创建重复的Flask应用程序。 These are secondary applications that are in no way connected to the actual application object used by the main Flask server. 这些是辅助应用程序,它们绝不连接到主Flask服务器使用的实际应用程序对象。 Their only purpose is to hold a copy of the original app.config dictionary that can be accessed by your task or by any Flask extensions your task is using. 它们的唯一目的是保存原始app.config词典的副本,您的任务或任务使用的任何Flask扩展都可以访问该字典。

So it is invalid to expect that a g.user set in the Flask server will be accessible also as g.user in the Celery task, simply because these are different g objects, from different application instances. 因此,期望在Flask服务器中设置的g.user也可以作为Celery任务中的g.user进行访问是无效的,仅仅是因为这些是来自不同应用程序实例的不同g对象。

If you need to use the authenticated user in the Celery task, what you should do is pass the user_id (usually g.user.id ) as an argument to your task. 如果您需要在Celery任务中使用经过身份验证的用户,则应该将user_id (通常为g.user.id )作为任务的参数传递。 Then in your task, you can load the user from the database using this id . 然后,在您的任务中,您可以使用此id从数据库加载用户。 Hope this helps! 希望这可以帮助!

To retrieve the user from within a task execution, you can try passing the User object (if celery can pickle it), or pass along enough information that the task can retrieve the User object (eg the User's id). 要从任务执行中检索用户,您可以尝试传递用户对象(如果芹菜可以腌制),或者传递足够的信息以使任务可以检索用户对象(例如,用户ID)。 In this latter case, your task would look something like 在后一种情况下,您的任务将类似于

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

and you'd kick it off (assuming you're using flask_login) via 并通过以下方式将其启动(假设您使用的是flask_login)

result = loop.delay(current_user.id)

As noted by @Dave W. Smith, rather than relying on g for retrieving the user, passing the user info as an argument to the Celery task might be a better approach. 正如@Dave W. Smith所指出的那样,将用户信息作为参数传递给Celery任务可能不是更好的方法,而不是依靠g来检索用户。 According to Flask documentation on app context , the lifetime of g is a request. 根据Flask关于应用程序上下文的文档g的生存期是一个请求。 Since the Celery task is executed asynchronously, it would be executed within a different app context than the one in the request where you defined the user. 由于Celery任务是异步执行的,因此它将在与您定义用户的请求中不同的应用程序上下文中执行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM