-
-
Notifications
You must be signed in to change notification settings - Fork 79
Tutorial Background Tasks
Run tasks asynchronously without blocking your web requests. Perfect for sending emails, processing uploads, or any slow operation.
Time: ~15 minutes
- Enable Celery with Redis
- Create a background task
- Call it from a route
- Monitor task execution
Redis running locally:
# macOS
brew install redis
brew services start redis
# Ubuntu
sudo apt install redis-server
sudo systemctl start redisEnferno has optional Celery support:
uv sync --extra fullThis installs celery and redis packages.
Add to .env:
REDIS_URL=redis://localhost:6379/1
CELERY_BROKER_URL=redis://localhost:6379/2
CELERY_RESULT_BACKEND=redis://localhost:6379/2Enferno already has Celery configured in enferno/tasks/__init__.py:
if CELERY_AVAILABLE:
from celery import Celery
from enferno.settings import Config as cfg
if cfg.CELERY_BROKER_URL:
celery = Celery(
"enferno.tasks",
broker=cfg.CELERY_BROKER_URL,
backend=cfg.CELERY_RESULT_BACKEND,
)
# Flask app context for tasks
class ContextTask(celery.Task):
def __call__(self, *args, **kwargs):
from enferno.app import create_app
with create_app(cfg).app_context():
return super().__call__(*args, **kwargs)
celery.Task = ContextTaskThe ContextTask ensures your tasks have access to Flask's app context (database, config, etc).
Prompt for AI assistant:
Add an email notification task to enferno/tasks/init.py that takes an email address and message, logs the send attempt, and simulates sending. Include proper error handling.
Add to enferno/tasks/__init__.py (after the existing code):
if celery:
@celery.task(bind=True, max_retries=3)
def send_email_task(self, to_email, subject, body):
"""Send email asynchronously."""
import time
from flask import current_app
try:
current_app.logger.info(f"Sending email to {to_email}: {subject}")
# Simulate email sending (replace with real SMTP)
time.sleep(2) # Simulates network delay
current_app.logger.info(f"Email sent to {to_email}")
return {"status": "sent", "to": to_email}
except Exception as e:
current_app.logger.error(f"Email failed: {e}")
raise self.retry(exc=e, countdown=60)Pattern notes:
-
bind=Truegives access toselffor retries -
max_retries=3limits retry attempts -
self.retry(countdown=60)waits 60 seconds before retrying - Flask app context is available via
ContextTask
Prompt for AI assistant:
Add a route /api/notify in the portal blueprint that accepts POST with email and message, queues a send_email_task, and returns the task ID.
Add to enferno/portal/views.py:
from enferno.tasks import celery
@portal.route("/api/notify", methods=["POST"])
def send_notification():
# Check if Celery is available
if not celery:
return jsonify({"error": "Background tasks not configured"}), 503
from enferno.tasks import send_email_task
data = request.json
email = data.get("email")
message = data.get("message", "Hello from Enferno!")
if not email:
return jsonify({"error": "Email required"}), 400
# Queue the task
task = send_email_task.delay(
to_email=email,
subject="Notification",
body=message
)
return jsonify({
"status": "queued",
"task_id": task.id
})Open a second terminal:
cd enferno
source .venv/bin/activate
uv run celery -A enferno.tasks worker -l infoYou should see:
[config]
.> app: enferno.tasks:0x...
.> transport: redis://localhost:6379/2
...
[queues]
.> celery exchange=celery(direct) key=celery
celery@hostname ready.
With both Flask and Celery running:
curl -X POST http://localhost:5000/dashboard/api/notify \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com", "message": "Hello!"}'Response:
{"status": "queued", "task_id": "abc123..."}Watch the Celery terminal - you'll see the task execute.
Add a status endpoint:
@portal.route("/api/task/<task_id>")
def task_status(task_id):
if not celery:
return jsonify({"error": "Background tasks not configured"}), 503
from celery.result import AsyncResult
result = AsyncResult(task_id, app=celery)
return jsonify({
"task_id": task_id,
"status": result.status,
"result": result.result if result.ready() else None
})With Docker Compose, Celery runs automatically. The docker-compose.yml includes:
celery:
build: .
command: celery -A enferno.tasks worker -l info
depends_on:
- redis
environment:
- CELERY_BROKER_URL=redis://:${REDIS_PASSWORD}@redis:6379/2
- CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD}@redis:6379/3Just run:
docker compose up --buildIgnite sets up Celery as a systemd service automatically:
# Ignite creates these for you:
/etc/systemd/system/celery-yourdomain.serviceView Celery logs:
journalctl -u celery-yourdomain.service -fProcess uploaded file:
@celery.task
def process_upload(file_path):
# Resize image, generate thumbnails, etc.
passSend bulk emails:
@celery.task
def send_newsletter(user_ids):
for user_id in user_ids:
user = db.session.get(User, user_id)
send_email_task.delay(user.email, "Newsletter", content)Scheduled cleanup:
# In celery config, add beat schedule
celery.conf.beat_schedule = {
'cleanup-daily': {
'task': 'enferno.tasks.cleanup_old_data',
'schedule': 86400.0, # 24 hours
},
}| Issue | Solution |
|---|---|
| "Background tasks not configured" | Check CELERY_BROKER_URL in .env
|
| Tasks not executing | Ensure Celery worker is running |
| Redis connection refused | Start Redis: brew services start redis
|
| Import errors in task | Check task has Flask app context |