diff --git a/app/core/celery_config.py b/app/core/celery_config.py index 53adb750..58214124 100644 --- a/app/core/celery_config.py +++ b/app/core/celery_config.py @@ -72,6 +72,12 @@ celery_app = Celery( include=get_all_task_modules(), ) +# Mark this as the default app so @shared_task decorators in module task +# files bind to it. Otherwise they fall back to Celery's built-in default, +# which uses amqp://localhost// and silently fails with "Connection refused" +# at .delay() time when no RabbitMQ is deployed. +celery_app.set_default() + # ============================================================================= # CELERY CONFIGURATION # ============================================================================= diff --git a/main.py b/main.py index cad60d4a..51e5d76b 100644 --- a/main.py +++ b/main.py @@ -32,6 +32,16 @@ from sqlalchemy import text from sqlalchemy.orm import Session from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware +# Bootstrap Celery default app BEFORE importing api_router, which +# transitively imports loyalty/billing/etc. task modules. Their @shared_task +# decorators bind at import time to whatever Celery considers the current +# default app — without our app.set_default() running first, they fall back +# to Celery's built-in default (amqp://localhost//) and .delay() raises +# ConnectionRefusedError at runtime. The isort: split below stops ruff from +# alphabetically reordering this back behind api_router. +from app.core import celery_config # noqa: F401 # side-effect: set_default() + +# isort: split from app.api.main import api_router from app.core.config import settings