Django芹菜任务保持全球状态

时间:2013-11-20 11:30:02

标签: django celery

我目前正在开发基于django-tenants-schema的Django应用程序。您不需要查看模块的实际代码,但其想法是它具有当前数据库连接的全局设置,用于定义用于应用程序租户的模式,例如,

tenant = tenants_schema.get_tenant()

并设置

tenants_schema.set_tenant(xxx)

对于某些任务,我希望他们记住在实例化期间选择的当前全局租户,例如在理论上:

class AbstractTask(Task):
    '''
    Run this method before returning the task future
    '''
    def before_submit(self):
         self.run_args['tenant'] = tenants_schema.get_tenant()

    '''
    This method is run before related .run() task method
    '''
    def before_run(self):
         tenants_schema.set_tenant(self.run_args['tenant'])

在芹菜中有这种优雅的方式吗?

2 个答案:

答案 0 :(得分:1)

芹菜(截至3.1)有signals你可以挂钩来做这件事。您可以更改传入的kwargs,并在另一方面撤消您的更改,然后才能执行实际任务:

from celery import shared_task
from celery.signals import before_task_publish, task_prerun, task_postrun
from threading import local

current_tenant = local()

@before_task_publish.connect
def add_tenant_to_task(body=None, **unused):
    body['kwargs']['tenant_middleware.tenant'] = getattr(current_tenant, 'id', None)
    print 'sending tenant: {t}'.format(t=current_tenant.id)


@task_prerun.connect
def extract_tenant_from_task(kwargs=None, **unused):
    tenant_id = kwargs.pop('tenant_middleware.tenant', None)
    current_tenant.id = tenant_id
    print 'current_tenant.id set to {t}'.format(t=tenant_id)


@task_postrun.connect
def cleanup_tenant(**kwargs):
    current_tenant.id = None
    print 'cleaned current_tenant.id'


@shared_task
def get_current_tenant():
    # Here is where you would do work that relied on current_tenant.id being set.
    import time
    time.sleep(1) 
    return current_tenant.id

如果您运行任务(不显示来自工作人员的日志记录):

In [1]: current_tenant.id = 1234; ct = get_current_tenant.delay(); current_tenant.id = 5678; ct.get()
sending tenant: 1234
Out[1]: 1234

In [2]: current_tenant.id
Out[2]: 5678

如果没有发送消息,则不会调用信号(直接调用任务函数,没有delay()apply_async())。如果要对任务名称进行过滤,则body['task']信号处理程序中的before_task_publish格式为tasktask_prerun和{task_postrun对象本身可用1}}处理程序。

我是Celery的新手,所以我无法确定这是否是在Celery中做“中间件”类型的“幸福”方式,但我认为它对我有用。

答案 1 :(得分:0)

我不确定你的意思,是在客户端调用任务之前执行before_submit吗?

在这种情况下,我宁愿在这里使用with语句:

from contextlib import contextmanager

@contextmanager
def set_tenant_db(tenant):
    prev_tenant = tenants_schema.get_tenant()
    try:
        tenants_scheme.set_tenant(tenant)
        yield
    finally:
        tenants_schema.set_tenant(prev_tenant)


@app.task
def tenant_task(tenant=None):
    with set_tenant_db(tenant):
        do_actions_here()


tenant_task.delay(tenant=tenants_scheme.get_tenant())

您当然可以创建一个自动执行此操作的基本任务, 例如,您可以在Task.__call__中应用上下文,但我不确定 如果你可以明确地使用with语句,那么你可以节省很多。