-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
93 lines (73 loc) · 2.72 KB
/
Copy pathmain.py
File metadata and controls
93 lines (73 loc) · 2.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import logging
import threading
import time
import pika
import uvicorn
import config
import metrics
from order import Order
logger = logging.getLogger(__name__)
def setup_rabbit_connection():
"""Establish connection to RabbitMQ."""
credentials = pika.PlainCredentials(config.RABBITMQ_USER, config.RABBITMQ_PASS)
parameters = pika.ConnectionParameters(
host=config.RABBITMQ_HOST,
port=config.RABBITMQ_PORT,
virtual_host=config.RABBITMQ_VHOST,
credentials=credentials,
heartbeat=600,
blocked_connection_timeout=300,
)
return pika.BlockingConnection(parameters)
def _declare_topology(channel):
"""Declare DLX, DLQ, and main queue with dead-letter routing.
Must match the topology declared by the groceror producer so that
passive re-declarations succeed without argument conflicts.
"""
channel.exchange_declare(exchange=config.DLX_EXCHANGE, exchange_type="direct", durable=True)
channel.queue_declare(queue=config.DLQ_NAME, durable=True)
channel.queue_bind(exchange=config.DLX_EXCHANGE, queue=config.DLQ_NAME, routing_key=config.QUEUE_NAME)
channel.queue_declare(
queue=config.QUEUE_NAME,
durable=True,
arguments={
"x-dead-letter-exchange": config.DLX_EXCHANGE,
"x-dead-letter-routing-key": config.QUEUE_NAME,
},
)
def start_consumer():
"""Start consuming messages from RabbitMQ with reconnect loop."""
while True:
try:
connection = setup_rabbit_connection()
channel = connection.channel()
_declare_topology(channel)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(
queue=config.QUEUE_NAME, on_message_callback=Order.save_order_info
)
logger.info("groceror-orders consumer started. Waiting for messages...")
metrics.set_consumer_up(1)
channel.start_consuming()
except pika.exceptions.AMQPConnectionError:
logger.error("Lost connection to RabbitMQ. Retrying in 5 seconds...")
metrics.set_consumer_up(0)
time.sleep(5)
except Exception as exc:
logger.error("Unexpected error: %s. Retrying in 5 seconds...", exc)
metrics.set_consumer_up(0)
time.sleep(5)
def start_api():
"""Start the FastAPI analytics server."""
uvicorn.run(
"api:app",
host=config.API_HOST,
port=config.API_PORT,
log_level="info",
)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
consumer_thread = threading.Thread(target=start_consumer, daemon=True)
consumer_thread.start()
# Analytics API runs on the main thread
start_api()