Spec-Zone.ru › Celery

Приложение

Перед использованием библиотеку Celery необходимо инициализировать; этот экземпляр называется приложением (или кратко app).

Приложение потокобезопасно, поэтому несколько приложений Celery с разными конфигурациями, компонентами и задачами могут сосуществовать в одном пространстве процессов.

Создадим приложение:

>>> from celery import Celery
>>> app = Celery()
>>> app
<Celery __main__:0x100469fd0>

В последней строке показано текстовое представление приложения: имя класса приложения (Celery), имя текущего главного модуля (__main__) и адрес объекта в памяти (0x100469fd0).

Важен только один из этих параметров — имя главного модуля. Давайте разберёмся почему.

При отправке сообщения о задаче в Celery в нём содержится не исходный код, а только имя задачи, которую нужно выполнить. Это работает примерно так же, как имена узлов в интернете: каждый рабочий процесс сопоставляет имена задач с соответствующими функциями. Такое сопоставление называется реестром задач.

При каждом определении задачи она также добавляется в локальный реестр:

>>> @app.task
... def add(x, y):
...     return x + y

>>> add
<@task: __main__.add>

>>> add.name
__main__.add

>>> app.tasks['__main__.add']
<@task: __main__.add>

И снова видим __main__; если Celery не удаётся определить, к какому модулю относится функция, для формирования начала имени задачи используется имя главного модуля.

Это проблема лишь в ограниченном числе случаев:

  1. Если модуль, в котором определена задача, запускается как программа.

  2. Если приложение создано в оболочке Python (REPL).

Например, в этом случае модуль tasks также используется для запуска рабочего процесса с помощью app.worker_main():

tasks.py:

from celery import Celery
app = Celery()

@app.task
def add(x, y): return x + y

if __name__ == '__main__':
    args = ['worker', '--loglevel=INFO']
    app.worker_main(argv=args)

При выполнении этого модуля имена задач будут начинаться с «__main__», но при импорте модуля другим процессом, например для вызова задачи, имена задач будут начинаться с «tasks» (настоящего имени модуля):

>>> from tasks import add
>>> add.name
tasks.add

Можно указать другое имя главного модуля:

>>> app = Celery('tasks')
>>> app.main
'tasks'

>>> @app.task
... def add(x, y):
...     return x + y

>>> add.name
tasks.add

См. также

Имена

Можно задать несколько параметров, которые изменят работу Celery. Эти параметры можно задать непосредственно в экземпляре приложения или использовать специальный модуль конфигурации.

Конфигурация доступна в виде app.conf:

>>> app.conf.timezone
'Europe/London'

где можно также задавать значения конфигурации напрямую:

>>> app.conf.enable_utc = True

или обновить сразу несколько ключей с помощью метода update:

>>> app.conf.update(
...     enable_utc=True,
...     timezone='Europe/London',
...)

Объект конфигурации состоит из нескольких словарей, которые проверяются по порядку:

  1. Изменения, внесённые во время выполнения.

  2. Модуль конфигурации (если он задан).

  3. Конфигурация по умолчанию (celery.app.defaults).

Можно даже добавлять новые источники значений по умолчанию с помощью метода app.add_defaults().

См. также

Полный список всех доступных параметров и их значений по умолчанию см. в справочнике по конфигурации.

config_from_object

Метод app.config_from_object() загружает конфигурацию из объекта конфигурации.

Это может быть модуль конфигурации или любой объект с атрибутами конфигурации.

Обратите внимание: при вызове config_from_object() все параметры конфигурации, заданные ранее, будут сброшены. Если нужно задать дополнительные параметры конфигурации, сделайте это после вызова метода.

Пример 1: использование имени модуля

Методу app.config_from_object() можно передать полное имя модуля Python или даже имя атрибута Python, например: "celeryconfig", "myproj.config.celery" или "myproj.config:CeleryConfig":

from celery import Celery

app = Celery()
app.config_from_object('celeryconfig')

Модуль celeryconfig может выглядеть так:

celeryconfig.py:

enable_utc = True
timezone = 'Europe/London'

После этого приложение сможет использовать его, если доступен import celeryconfig.

Пример 2: передача объекта модуля

Можно также передать уже импортированный объект модуля, однако это не всегда рекомендуется.

Совет

Рекомендуется указывать имя модуля: тогда модуль не нужно сериализовать при использовании пула prefork. Если возникли проблемы с конфигурацией или ошибки pickle, попробуйте вместо этого указать имя модуля.

import celeryconfig

from celery import Celery

app = Celery()
app.config_from_object(celeryconfig)

Пример 3: использование объекта/класса конфигурации

from celery import Celery

app = Celery()

class Config:
    enable_utc = True
    timezone = 'Europe/London'

app.config_from_object(Config)
# or using the fully qualified name of the object:
#   app.config_from_object('module:Config')

config_from_envvar

Метод app.config_from_envvar() получает имя модуля конфигурации из переменной окружения.

Например, чтобы загрузить конфигурацию из модуля, указанного в переменной окружения с именем CELERY_CONFIG_MODULE:

import os
from celery import Celery

#: Set default configuration module name
os.environ.setdefault('CELERY_CONFIG_MODULE', 'celeryconfig')

app = Celery()
app.config_from_envvar('CELERY_CONFIG_MODULE')

Затем можно указать модуль конфигурации через окружение:

$ CELERY_CONFIG_MODULE="celeryconfig.prod"celeryworker-lINFO

Фильтрация конфигурации

Если нужно вывести конфигурацию, например для отладки, может понадобиться скрыть конфиденциальные данные, такие как пароли и ключи API.

В Celery есть несколько полезных средств для представления конфигурации. Одно из них — humanize():

>>> app.conf.humanize(with_defaults=False, censored=True)

Этот метод возвращает конфигурацию в виде таблицы. По умолчанию в ней будут только изменения конфигурации, но можно включить встроенные ключи и значения по умолчанию, установив аргумент with_defaults.

Если вместо этого нужно работать с конфигурацией как со словарём, используйте метод table():

>>> app.conf.table(with_defaults=False, censored=True)

Обратите внимание: Celery не может удалить всю конфиденциальную информацию, поскольку для поиска ключей с распространёнными именами используется только регулярное выражение. Если вы добавляете собственные параметры с конфиденциальной информацией, назовите ключи так, чтобы Celery распознал их как секретные.

Параметр конфигурации будет отфильтрован, если его имя содержит одну из следующих подстрок:

API, TOKEN, KEY, SECRET, PASS, SIGNATURE, DATABASE

Экземпляр приложения является ленивым: его инициализация происходит только тогда, когда он действительно нужен.

Создание экземпляра Celery приводит только к следующим действиям:

  1. Создаётся экземпляр логических часов, используемый для событий.

  2. Создаётся реестр задач.

  3. Экземпляр назначается текущим приложением (если аргумент set_as_current не отключён).

  4. Вызывается обратный вызов app.on_init() (по умолчанию ничего не делает).

Декораторы app.task() не создают задачи в момент их определения. Вместо этого создание задачи откладывается до момента её использования или до финализации приложения.

В этом примере показано, что задача не создаётся, пока вы её не используете или не обратитесь к её атрибуту (в данном случае repr()):

>>> @app.task
>>> def add(x, y):
...    return x + y

>>> type(add)
<class 'celery.local.PromiseProxy'>

>>> add.__evaluated__()
False

>>> add        # <-- causes repr(add) to happen
<@task: __main__.add>

>>> add.__evaluated__()
True

Финализация приложения выполняется явно при вызове app.finalize() или неявно при обращении к атрибуту app.tasks.

Финализация объекта приводит к следующим действиям:

  1. Копируются задачи, которые должны совместно использоваться приложениями.

    По умолчанию задачи являются общими, однако если аргумент shared декоратора задачи отключён, задача будет доступна только приложению, к которому она привязана.

  2. Вычисляются все ожидающие декораторы задач.

  3. Обеспечивается привязка всех задач к текущему приложению.

    Задачи привязываются к приложению, чтобы считывать из конфигурации значения по умолчанию.

«Приложение по умолчанию»

Раньше в Celery не было приложений — существовал только API на основе модулей. До выпуска Celery 5.0 API совместимости был доступен по старому адресу, но затем его удалили.

Celery всегда создаёт специальное приложение — «приложение по умолчанию». Оно используется, если не создано пользовательское приложение.

Модуль celery.task больше недоступен. Используйте методы экземпляра приложения, а не API на основе модулей:

from celery.task import Task   # << OLD Task base class.

from celery import Task        # << NEW base class.

Хотя можно полагаться на то, что текущее приложение уже задано, рекомендуется всегда передавать экземпляр приложения всем компонентам, которым он нужен.

Я называю это «цепочкой приложений»: экземпляры образуют цепочку, в которой каждый зависит от переданного приложения.

Следующий пример считается плохой практикой:

from celery import current_app

class Scheduler:

    def run(self):
        app = current_app

Вместо этого следует передавать app в качестве аргумента:

class Scheduler:

    def __init__(self, app):
        self.app = app

Внутри Celery использует функцию celery.app.app_or_default(), чтобы всё также работало с API совместимости на основе модулей.

from celery.app import app_or_default

class Scheduler:
    def __init__(self, app=None):
        self.app = app_or_default(app)

При разработке можно задать переменную окружения CELERY_TRACE_APP, чтобы при разрыве цепочки приложений возникало исключение:

$ CELERY_TRACE_APP=1celeryworker-lINFO

Развитие API

С момента создания Celery в 2009 году библиотека сильно изменилась.

Например, изначально в качестве задачи можно было использовать любую вызываемую сущность:

def hello(to):
    return 'hello {0}'.format(to)

>>> from celery.execute import apply_async

>>> apply_async(hello, ('world!',))

Также можно было создать класс Task, чтобы задать определённые параметры или переопределить другое поведение.

from celery import Task
from celery.registry import tasks

class Hello(Task):
    queue = 'hipri'

    def run(self, to):
        return 'hello {0}'.format(to)
tasks.register(Hello)

>>> Hello.delay('world!')

Позже было решено, что передача произвольных вызываемых объектов — антипаттерн, поскольку она значительно затрудняет использование сериализаторов, отличных от pickle. Эта возможность была удалена в версии 2.0 и заменена декораторами задач:

from celery import app

@app.task(queue='hipri')
def hello(to):
    return 'hello {0}'.format(to)

Все задачи, созданные с помощью декоратора app.task(), наследуются от базового класса приложения Task.

Можно указать другой базовый класс с помощью аргумента base:

@app.task(base=OtherTask):
def add(x, y):
    return x + y

Чтобы создать пользовательский класс задачи, унаследуйте его от нейтрального базового класса: celery.Task.

from celery import Task

class DebugTask(Task):

    def __call__(self, *args, **kwargs):
        print('TASK STARTING: {0.name}[{0.request.id}]'.format(self))
        return self.run(*args, **kwargs)

Совет

Если вы переопределяете метод __call__ задачи, очень важно также вызвать self.run, чтобы выполнить тело задачи. Не вызывайте super().__call__. Метод __call__ нейтрального базового класса celery.Task приведён только для справки. В целях оптимизации его код встроен в celery.app.trace.build_tracer.trace_task, который напрямую вызывает run пользовательского класса задачи, если метод __call__ не определён.

Нейтральный базовый класс является особым, поскольку он ещё не привязан к конкретному приложению. После привязки задачи к приложению она считывает конфигурацию, чтобы установить значения по умолчанию и выполнить другие действия.

Чтобы реализовать базовый класс, необходимо создать задачу с помощью декоратора app.task():

@app.task(base=DebugTask)
def add(x, y):
    return x + y

Также можно изменить базовый класс приложения по умолчанию, изменив его атрибут app.Task():

>>> from celery import Celery, Task

>>> app = Celery()

>>> class MyBaseTask(Task):
...    queue = 'hipri'

>>> app.Task = MyBaseTask
>>> app.Task
<unbound MyBaseTask>

>>> @app.task
... def add(x, y):
...     return x + y

>>> add
<@task: __main__.add>

>>> add.__class__.mro()
[<class add of <Celery __main__:0x1012b4410>>,
 <unbound MyBaseTask>,
 <unbound Task>,
 <type 'object'>]

Copyright © 2017-2026 Asif Saif Uddin, core team & contributors. All rights reserved.
Celery is licensed under The BSD License (3 Clause, also known as the new BSD license). The license is an OSI approved Open Source license and is GPL-compatible.
https://docs.celeryq.dev/en/stable/userguide/application.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API