Files
gapido-grpc-auth/src/gapido_auth/infrastructure/worker.py

90 lines
3.1 KiB
Python

import asyncio
import json
import logging
from typing import Any, cast
import aio_pika
from aio_pika.abc import AbstractIncomingMessage
from redis.asyncio import Redis
from gapido_auth.config import get_settings
from gapido_auth.domain.entities import SmsJob
from gapido_auth.domain.ports import SmsClient
from gapido_auth.infrastructure.rabbitmq import (
SMS_ROUTING_KEY,
connect_robust,
declare_sms_topology,
)
from gapido_auth.infrastructure.sms_provider import create_sms_client
logger = logging.getLogger(__name__)
MAX_RETRIES = 3
async def handle_message(
message: AbstractIncomingMessage,
client: SmsClient,
exchange: Any,
dlx: Any,
) -> None:
"""Process one SMS job, retry bounded failures, and dead-letter permanent failures."""
async with message.process(ignore_processed=True, requeue=False):
payload = json.loads(message.body.decode())
job = SmsJob(**payload)
retry_count = int(cast(int | str, (message.headers or {}).get("x-retry-count", 0)))
try:
await client.send_otp(job.mobile, job.code, job.template)
except Exception:
if retry_count >= MAX_RETRIES:
logger.exception("SMS job failed permanently for mobile=%s", job.mobile)
await dlx.publish(
aio_pika.Message(
body=message.body,
content_type="application/json",
headers={"x-retry-count": retry_count},
delivery_mode=aio_pika.DeliveryMode.PERSISTENT,
),
routing_key=SMS_ROUTING_KEY,
)
return
# Re-publish instead of requeueing indefinitely so retries remain bounded.
logger.warning(
"SMS job failed for mobile=%s retry=%s", job.mobile, retry_count + 1, exc_info=True
)
await exchange.publish(
aio_pika.Message(
body=message.body,
content_type="application/json",
headers={"x-retry-count": retry_count + 1},
delivery_mode=aio_pika.DeliveryMode.PERSISTENT,
),
routing_key=SMS_ROUTING_KEY,
)
async def main() -> None:
"""Start the RabbitMQ consumer and bind it to the configured SMS strategy."""
logging.basicConfig(level=logging.INFO)
settings = get_settings()
connection = await connect_robust(settings.rabbitmq_url)
async with connection:
channel = await connection.channel()
await channel.set_qos(prefetch_count=20)
exchange, queue, dlx = await declare_sms_topology(channel)
redis = Redis.from_url(settings.redis_url, decode_responses=True)
try:
client = create_sms_client(settings, debug_store=redis)
await queue.consume(lambda message: handle_message(message, client, exchange, dlx))
logger.info("SMS worker started with provider=%s", settings.sms_provider)
await asyncio.Future()
finally:
await redis.aclose()
if __name__ == "__main__":
asyncio.run(main())