{"id":23498364,"url":"https://github.com/cucumberian/tutorial_celery","last_synced_at":"2025-04-23T00:37:30.762Z","repository":{"id":225036627,"uuid":"764914435","full_name":"cucumberian/tutorial_celery","owner":"cucumberian","description":"celery","archived":false,"fork":false,"pushed_at":"2024-05-16T17:14:49.000Z","size":13,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-16T15:18:27.483Z","etag":null,"topics":["celery","django","docker","docker-compose","tutorial"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cucumberian.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-28T23:54:19.000Z","updated_at":"2024-05-16T17:14:53.000Z","dependencies_parsed_at":"2024-02-29T00:43:39.793Z","dependency_job_id":"9f6030c6-969d-43fa-9ba7-730e079cb473","html_url":"https://github.com/cucumberian/tutorial_celery","commit_stats":null,"previous_names":["cucumberian/tutorial_celery"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cucumberian%2Ftutorial_celery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cucumberian%2Ftutorial_celery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cucumberian%2Ftutorial_celery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cucumberian%2Ftutorial_celery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cucumberian","download_url":"https://codeload.github.com/cucumberian/tutorial_celery/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250348872,"owners_count":21415907,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["celery","django","docker","docker-compose","tutorial"],"created_at":"2024-12-25T05:31:29.252Z","updated_at":"2025-04-23T00:37:30.733Z","avatar_url":"https://github.com/cucumberian.png","language":"Python","readme":"# Asynchronous Tasks With Django and Celery\n1. https://habr.com/ru/companies/otus/articles/503380/  (https://github.com/testdrivenio/django-celery)\n2. https://realpython.com/asynchronous-tasks-with-django-and-celery/\n\n\nCelery это отдельная очередь задач, которая может собирать, получать, планировать и выполнять задачи вне основной программы.\nЧтобы получить и отдавать готовые задачи celery нужен брокер сообщений для коммуникации.\nОбычно вместе с Celery используется Redis и RabbitMQ.\n- Celery workers - это рабочие процессы, котрые выполняют задачи независимо вне основной программы.\n- Celery beat - планировщик, который определяет когда запускать задачи.\n\n## Установка\n```shell\npip3 install django\n...\npip3 install celery\npip3 install redis\n```\nТеперь можно запустить worker командой `celery worker`. Но получим сообщение об ошибке, что celery не может работать с брокером сообщений.\nCelery будет безуспешно пытаться подключиться к локальному хосту по протоколу amqp - advanced message queuing protocol (https://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol).\n\n### Redis\nУстановим redis-server\n```shell\nsudo apt update\nsudo apt install redis\n```\nКонечно можно ставить отдельно в виде докер-контейнера.\n\nМожно запустить redis-server\n```shell\nredis-server\n```\nПроверить работает ли redis\n```shell\nps aux | grep redis\n```\nОстановить\n```shell\nsudo service redis-server stop\n```\nПингануть\n```shell\nredis-cli ping\n```\nЗапустить клиент\n```shell\nredis-cli\n```\nУстановим питоновский клиент\n```shell\npip install redis\npip install celery\npip install flower\n```\nТ.е. нам нужен редис сервер, как отдельное приложение и пакет для питоновских программ для работы с ним.\n\n## Добавление celery у джанго проекту\nСоздадим файл `celery.py` в папке корневого приложенияЮ рядом c `settings.py`.\n```python\n# django_celery/celery.py\nimport os\nfrom celery import Celery\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"django_celery.settings\")\n\napp = Celery(\"django_celery\")\n# app = Celery(\"hello\", backend=\"redis://localhost:6379\", broker=\"pyamqp://quest@127.0.0.1:6379/\")\napp.config_from_object(\"django.conf.settings\", namespace=\"CELERY\")\napp.autodiscover_tasks()\n\n\n@app.task\ndef add(x, y):\n    return x + y\n```\nЗдесь мы устанавливаем переменную окружения, чтобы получить модуль джанго `project_name.settings.py` через переменную окружения `DJANGO_SETTINGS_MODULE`.\n\nЗатем создаём экземпляр приложения Celery и передаём внутрь имя нашего приложения (главного модуля).\n\nДалее мы задаем путь до файла настроек и имя неймспейса с настройками celery. В файле конфигурации `settings.py` все настройки начинающиеся с `CELERY_` будут прочтены этим приложением. При желании можно определить и другой файл конфигурации.\n\nЧерез автодисковер мы говорим приложению celery искать задачи в каждом приложении джанго.\n\nДалее добавляем настройки для celery в `settings.py`:\n```python\n# settings.py\n\n# Celery settings\nCELERY_BROKER_URL = \"redis://localhost:6379/0\"\nCELERY_RESULT_BACKEND = \"redis://localhost:6379/0\"\n```\nДанные строки дают инстансу celery достаточно информации, чтобы понять куда отправлять сообщения и куда записывать результат.\nЗаметим, что начинаются эти строки на имя `CELERY_`, где название `CELERY` задается как namespace в файле `celery.py` в строке `app.config_from_object(\"django.conf:settings\", namespace=\"CELERY\")`.\n\nДобавим celery.app в загрузку модуля через файл `main_app/__init__.py`:\n```python\n# __init__.py\n\nfrom .celery import app as celery_app\n\n__all__ = (\"celery_app\", )\n```\nзапуск приложения `celery_app` при запуске джанго будет гарантировать нам, что декоратор `@shared_task` будет использовать его корректно.\n\nТеперь можно протестировать приложение. Напомним что наша связка с celery-django будет состоять из трёх модулей:\n- producer - приложение джанго\n- message-broker - сервер редис\n- consumer - приложение celery_app в джанго\n\n\n### Запуск\n\n- Запускаем сервер redis  `redis-server` если еще не запущен как сервис или в докере\n- запускаем джанго `python manage.py runserver`\n- запускаем воркер `python -m celery -A django_celery worker --loglevel=INFO`\n    При запуске воркера передаём celery имя нашего джанго модуля в котором есть инстанс Celery.\n    - `-A` = `--app=`\n    - `-l` = `--loglevel=`\n    - `-b` = `--broker=`\n    Можно явно указать инстанс Celery:\n    ```shell\n    python -m celery --app=django_celery:celery_app worker --loglevel=INFO\n    ```\n- запускаем flower на порту 5555\n```shell\npython -m celery --broker=redis://127.0.0.1:6379/0 flower -A django_celery --port=5555\n```\nили если подтягиваем настройки для фловера из аппы\n```shell\npython -m celery -A django_celery flower --port=5555\n```\n\n### Использование\nДля использование надо в тексте программы добавить задачу для воркера\n```python\ntask = add.delay(1, 2)\n```\n, где `add` - функция задекорированная `@celery_app.task` - специальным декоратором от инстанса Celery, который сы создали в `celery.py`.\n\nПолучить статус задачи и результат выполнения:\n```python\nprint(task.result)\nprint(task.status)\n```\n\nВ случае если работа ведётся с базой данных, например создаваться объект, сохраняется и потом добавляется задача, то может случиться, что воркер получит задачу и начнёт её выполнять, а значения в базе данных ещё не будет.\nТогда лучше запускать воркер после [транзакции](https://docs.djangoproject.com/en/5.0/topics/db/transactions/) в базу данных, например так:\n```python\nfrom django.db import transaction\n\ntransaction.on_commit(lambda: some_celery_task.delay(obj.id))\n```\nПо самому celery. Уже много раз обсуждалось и везде предупреждают, но повторю — не используйте в качестве аргументов для тасков сложные объекты, например модели django. Передавайте лучше id и уже в таске получайте объект из БД. Ещё один важный момент, который может смутить начинающего разработчика на django — вьюхи, как правило, выполняются в транзакции. Это может привести к тому, сохранив новый объект и сразу отправь его id в таск вы можете получить object not found. Чтобы такого избежать, нужно использовать конструкцию типа `transaction.on_commit(lambda: some_celery_task.delay(obj.id))`\n\n\nТак же можно смотреть текущие задачи и их статусы с помощью\n```shell\npython -m celery -A worker events\n```\n#### Django\nВот пример использования в django:\n```python\n# для запуска после транзакции\nfrom django.db import transaction\n# для декорирования задачи\nfrom django_cel.celery import app as celery_app\n\nclass SimpleView(View):\n    def post(self, request):\n        value = request.POST.get(\"value\")\n        if value:\n            simple = Simple(value=value)\n            simple.save()\n            # отдаём задачу воркеру после выполнения транзакции,\n            # когда объект уже будет создан\n            transaction.on_commit(lambda: simple_task.delay(simple.id))\n            return JsonResponse({\"id\": simple.id})\n        return JsonResponse({\"error\": \"Value is required\"})\n\n# регистрируем задачу для воркера\n@celery_app.task\ndef simple_task(simple_id):\n    print(f\"Simple task started with id {simple_id}\")\n    simple = Simple.objects.get(id=simple_id)\n    simple.result = len(Simple.objects.all())\n    simple.is_completed = True\n    simple.save()\n    print(f\"Simple task finished with id {simple_id}\")\n    return simple_id\n```\nЕсли celery не может найти задачу (ошибка Not registered), то просто перезапустите celery.\n\n## Конструкции celery\n```python\nfrom celery import Celery\nfrom celery import shared_task\n...\ncelery_app = Celery(\n    \"project-name\",\n    broker=\"redis://localhost:6379/0\",  # можно задать потом\n    backend=\"redis://localhost:6379/0\", # можно задать потом    \n)\ncelery_app.autodiscover_tasks(force=True)\n...\n\n# регистрация таски\n@celery_app.task\ndef add(x, y):\n    return x + y\n\n# регистрация shared_task\n@shared_task\ndef sub(x, y):\n    return x - y\n\ntask_sub = sub.delay(2, 1)\n\ntask = add.delay(1, 2)\nwhile not task.ready():\n    pass\nprint(task.get())\n```\n\n### `@shared_task` vs `@app.task`\nhttps://docs.celeryq.dev/en/stable/userguide/tasks.html\nПри использовании shared_task нет необходимости икспортировать экземпляр `Celery`.\nТакже можно использовать `@app.task(shared=True)`.\nВ случае если есть несколько экземпляров Celery\n```python\napp1 = Celery()\n\napp2 = Celery()\n\n@app1.task\ndef test():\n    pass\n```\n, то __таска__ `test` будет зарегистрирована в обоих иснтансах Celery, но __имя__ `test` будет относиться только к app1.\nОднако `@shared_task` позволяет использовать таск в обоих инстансах.\n\n### bind=True\nТакие задачи первым аргументом принимают себя и позволяют повторять себя при необходимости повторов.\n```python\nlogger = get_task_logger(__name__)\n\n@app.task(bind=True)\ndef add(self, x, y):\n    logger.info(self.request.id)\n```\nBound tasks are needed for retries (using app.Task.retry()), for accessing information about the current task request, and for any additional functionality you add to custom task base classes.\n\n#### delay\n`task.delay()` - это метод который является псевдонимом более мощного метода `.apply_async()`, у которого есть опции выполнения.\n```python\n@shared_task\ndef add_task(x, y):\n    return x + y\n\nadd_task.apply_async(\n    args=[1, 2]\n)\n```\nХотя для многих простых случаев использование `delay` является предпочтительнее, использование метода `apply_async` иногда оправдано, например со счётчиками или повторами.\n\n### tasks.py\nМожно задать выполняемые задачи в файле tasks.py.\n```python\n#tasks.py\nfrom celery import shared_task\n\n\n@shared_task\ndef hello():\n    print(\"Hello Celery\")\n\n@shared_task\ndef add(x, y):\n    return x + y\n```\n\n## docker-compose\n```shell\nversion: \"3\"\n\nservices:\n  redis:\n    image: redis:7.2.4-alpine3.19\n    # ports:\n    #   - 6379:6379\n  \n  postgres:\n    image: postgres:13.14-alpine3.19\n    restart: always\n    # ports:\n    #   - \"5444:5432\"\n    env_file: .env\n    environment:\n      - POSTGRES_USER=$POSTGRES_USER\n      - POSTGRES_PASSWORD=$POSTGRES_PASSWORD\n      - POSTGRES_DB=$POSTGRES_DB\n    volumes:\n      - postgresql_volume:/var/lib/postgresql/data\n\n  django:\n    build:\n      context: celery2\n      dockerfile: Dockerfile\n    command: sh -c \"python manage.py makemigrations \u0026\u0026 python manage.py migrate --noinput \u0026\u0026 python manage.py runserver 0.0.0.0:8000\"\n    ports:\n      - 8080:8000\n    env_file: .env\n    environment:\n      - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}\n      - CELERY_BROKER_URL=redis://redis:6379/0\n      - CELERY_RESULT_BACKEND=redis://redis:6379/0\n      - POSTGRES_HOST=postgres\n      - POSTGRES_PORT=5432\n    depends_on:\n      - redis\n      - postgres\n\n    \n  worker:\n    build:\n      context: celery2\n      dockerfile: Dockerfile\n    command: python -m celery -A celery2:celery_app worker\n    env_file: .env\n    environment:\n      - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}\n      - CELERY_BROKER_URL=redis://redis:6379/0\n      - CELERY_RESULT_BACKEND=redis://redis:6379/0\n      - POSTGRES_HOST=postgres\n      - POSTGRES_PORT=5432\n    depends_on:\n      - redis\n      - postgres\n  \n  flower:\n    build:\n      context: celery2\n      dockerfile: Dockerfile\n    command: python -m celery -A celery2 flower --port=5555\n    ports:\n      - \"5555:5555\"\n    env_file: .env\n    environment:\n      - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}\n      - CELERY_BROKER_URL=redis://redis:6379/0\n      - CELERY_RESULT_BACKEND=redis://redis:6379/0\n      - POSTGRES_HOST=postgres\n      - POSTGRES_PORT=5432\n    depends_on:\n      - redis\n      - postgres\n\n\n\nvolumes:\n  postgresql_volume:\n    name: postgresql_volume\n```\n\nЧтобы изменить количество запущенных контейнеров с воркерами можно воспользоваться командой\n```shell\ndocker-compose up -d --build --scale worker=3\n```\n\n## Celery для произвольного проекта\n\nЗадача - запустить асинхронный расчет хэша от строки.\nТ.к. задача асинхронная, то выполнять её можно в отдельном процессе.\nА в главной программе мы будем асинхронно ожидать выполнения этого процесса через `asyncio.sleep`.\n\n1. Создаем экземпляр celery приложения в файле `celery_app.py`\n```python\nimport time\nimport hashlib\nfrom celery import Celery\n\ncelery_app = Celery(\n    \"tasks\", broker=\"redis://localhost:6379/0\", backend=\"redis://localhost:6379/0\"\n)\n\ncelery_app.conf.broker_url = Config.CELERY_BROKER_URL\ncelery_app.conf.result_backend = Config.CELERY_RESULT_BACKEND\ncelery_app.conf.update(result_expires=3600)\n\n@celery_app.task\ndef calc_hash(string: str) -\u003e str:\n    time.sleep(10)\n    hash_str = hashlib.sha256(string.encode()).hexdigest()\n    return hash_str\n```\n\n2. Используем эту задачу в коде:\n```python\nimport asyncio\nfrom celery_app import celery_hash\n\nasync def calc_hash(string: str) -\u003e str:\n  \"\"\"\n  Асинхронный расчёт хэша в отдельном процессе Celery воркера\n  \"\"\"\n  task = celery_hash.delay(string=string)\n  while not task.ready():\n    asyncio.sleep(0.1)\n  return task.result\n```\n3. Запускаем воркера\n```shell\ncelery -A celery_app worker --loglevel=INFO\n```\n\n4. Запускаем наше приложение","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcucumberian%2Ftutorial_celery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcucumberian%2Ftutorial_celery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcucumberian%2Ftutorial_celery/lists"}