topic exchange rabbitmq
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"daemons":
|
||||||
|
[
|
||||||
|
"welcome_email",
|
||||||
|
"reset_email"
|
||||||
|
]
|
||||||
|
}
|
||||||
+48
-3
@@ -1,11 +1,56 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from src.messaging.consumers.consumers import email_consumer
|
from src.daemons.registry import DAEMONS
|
||||||
|
from src.models.configs_read.daemons_json import daemons_config
|
||||||
|
|
||||||
|
|
||||||
|
async def run_one(daemon_name: str) -> None:
|
||||||
|
daemon_cls = DAEMONS.get(daemon_name)
|
||||||
|
if daemon_cls is None:
|
||||||
|
print(f"Unknown daemon: {daemon_name}. Available: {list(DAEMONS.keys())}")
|
||||||
|
sys.exit(1)
|
||||||
|
daemon = daemon_cls()
|
||||||
|
await daemon.run()
|
||||||
|
|
||||||
|
|
||||||
|
async def run_enabled_from_config() -> None:
|
||||||
|
|
||||||
|
daemons = []
|
||||||
|
|
||||||
|
for name in daemons_config.daemons:
|
||||||
|
name = name.strip()
|
||||||
|
if name not in DAEMONS:
|
||||||
|
print(f"Warning: unknown daemon '{name}' in config, skipping")
|
||||||
|
continue
|
||||||
|
daemons.append(DAEMONS[name]())
|
||||||
|
|
||||||
|
if not daemons:
|
||||||
|
print("No enabled daemons found in config")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
await asyncio.gather(*(d.run() for d in daemons))
|
||||||
|
|
||||||
|
|
||||||
async def main() -> None:
|
async def main() -> None:
|
||||||
await email_consumer.start_consuming()
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python run_daemon.py <daemon_name> | --all")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
arg = sys.argv[1]
|
||||||
|
if arg == "--all":
|
||||||
|
await run_enabled_from_config()
|
||||||
|
else:
|
||||||
|
await run_one(arg)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
try:
|
||||||
|
asyncio.run(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('Interrupted')
|
||||||
|
try:
|
||||||
|
sys.exit(0)
|
||||||
|
except SystemExit:
|
||||||
|
os._exit(0)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class BaseDaemon(ABC):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def run(self) -> None:
|
||||||
|
...
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from src.daemons.base import BaseDaemon
|
||||||
|
from src.messaging.consumers.consumers import (
|
||||||
|
reset_email_consumer,
|
||||||
|
welcome_email_consumer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WelcomeEmailDaemon(BaseDaemon):
|
||||||
|
name = "welcome_email"
|
||||||
|
|
||||||
|
async def run(self) -> None:
|
||||||
|
await welcome_email_consumer.start_consuming()
|
||||||
|
|
||||||
|
|
||||||
|
class ResetEmailDaemon(BaseDaemon):
|
||||||
|
name = "reset_email"
|
||||||
|
|
||||||
|
async def run(self) -> None:
|
||||||
|
await reset_email_consumer.start_consuming()
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from src.daemons.email_daemons import ResetEmailDaemon, WelcomeEmailDaemon
|
||||||
|
|
||||||
|
DAEMONS = {
|
||||||
|
"welcome_email": WelcomeEmailDaemon,
|
||||||
|
"reset_email": ResetEmailDaemon,
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
|
import aio_pika
|
||||||
|
|
||||||
from src.messaging.rabbitmq_client import rabbitmq_client
|
from src.messaging.rabbitmq_client import rabbitmq_client
|
||||||
|
|
||||||
|
|
||||||
class EmailConsumer:
|
class WelcomeEmailConsumer:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.channel = None
|
self.channel = None
|
||||||
@@ -13,23 +15,65 @@ class EmailConsumer:
|
|||||||
|
|
||||||
self.channel = await rabbitmq_client.get_channel()
|
self.channel = await rabbitmq_client.get_channel()
|
||||||
await self.channel.set_qos(prefetch_count=10)
|
await self.channel.set_qos(prefetch_count=10)
|
||||||
self.queue = await self.channel.declare_queue("email_queue", durable=True)
|
|
||||||
|
exchange = await self.channel.declare_exchange("email", aio_pika.ExchangeType.TOPIC)
|
||||||
|
self.queue = await self.channel.declare_queue("queue_welcome_email", durable=True, arguments={"x-queue-type": "quorum"})
|
||||||
|
|
||||||
|
await self.queue.bind(exchange, routing_key="email.welcome")
|
||||||
|
|
||||||
async def process_message(self, message) -> None:
|
async def process_message(self, message) -> None:
|
||||||
|
|
||||||
async with message.process():
|
async with message.process():
|
||||||
data = json.loads(message.body)
|
data = json.loads(message.body)
|
||||||
print(f"Отправляю email на {data['email']}")
|
print(f"Обрабатываю: {data}, метка: {message.routing_key}")
|
||||||
|
|
||||||
async def start_consuming(self)->None:
|
async def start_consuming(self)->None:
|
||||||
|
|
||||||
if self.queue is None:
|
if self.queue is None:
|
||||||
await self.setup()
|
await self.setup()
|
||||||
|
|
||||||
if self.channel is None or self.queue is None:
|
queue = self.queue
|
||||||
raise RuntimeError("Failed to set up RabbitMQ channel/queue")
|
if queue is None:
|
||||||
|
raise RuntimeError("Failed to set up RabbitMQ queue")
|
||||||
async with self.queue.iterator() as queue_iter:
|
|
||||||
|
async with queue.iterator() as queue_iter:
|
||||||
async for message in queue_iter:
|
async for message in queue_iter:
|
||||||
await self.process_message(message)
|
await self.process_message(message)
|
||||||
|
|
||||||
email_consumer=EmailConsumer()
|
class ResetEmailConsumer:
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.channel = None
|
||||||
|
self.queue = None
|
||||||
|
|
||||||
|
async def setup(self)->None:
|
||||||
|
|
||||||
|
self.channel = await rabbitmq_client.get_channel()
|
||||||
|
await self.channel.set_qos(prefetch_count=10)
|
||||||
|
|
||||||
|
exchange = await self.channel.declare_exchange("email", aio_pika.ExchangeType.TOPIC)
|
||||||
|
self.queue = await self.channel.declare_queue("queue_reset_email", durable=True, arguments={"x-queue-type": "quorum"})
|
||||||
|
|
||||||
|
await self.queue.bind(exchange, routing_key="email.reset")
|
||||||
|
|
||||||
|
async def process_message(self, message) -> None:
|
||||||
|
|
||||||
|
async with message.process():
|
||||||
|
data = json.loads(message.body)
|
||||||
|
print(f"Обрабатываю: {data}, метка: {message.routing_key}")
|
||||||
|
|
||||||
|
async def start_consuming(self)->None:
|
||||||
|
|
||||||
|
if self.queue is None:
|
||||||
|
await self.setup()
|
||||||
|
|
||||||
|
queue = self.queue
|
||||||
|
if queue is None:
|
||||||
|
raise RuntimeError("Failed to set up RabbitMQ queue")
|
||||||
|
|
||||||
|
async with queue.iterator() as queue_iter:
|
||||||
|
async for message in queue_iter:
|
||||||
|
await self.process_message(message)
|
||||||
|
|
||||||
|
welcome_email_consumer=WelcomeEmailConsumer()
|
||||||
|
reset_email_consumer=ResetEmailConsumer()
|
||||||
@@ -14,20 +14,28 @@ class EmailProducer:
|
|||||||
async def setup(self)->None:
|
async def setup(self)->None:
|
||||||
|
|
||||||
self.channel = await rabbitmq_client.get_channel()
|
self.channel = await rabbitmq_client.get_channel()
|
||||||
self.queue = await self.channel.declare_queue("email_queue", durable=True)
|
self.exchange = await self.channel.declare_exchange("email", aio_pika.ExchangeType.TOPIC)
|
||||||
|
|
||||||
async def send_welcome_email(self, email:str)->None:
|
async def send_welcome_email(self, email:str)->None:
|
||||||
|
|
||||||
if self.queue is None:
|
await self._publish({"email":email}, routing_key="email.welcome")
|
||||||
await self.setup()
|
|
||||||
|
|
||||||
if self.channel is None or self.queue is None:
|
async def send_reset_email(self, email:str)->None:
|
||||||
raise RuntimeError("Failed to set up RabbitMQ channel/queue")
|
|
||||||
|
await self._publish({"email":email}, routing_key="email.reset")
|
||||||
|
|
||||||
|
async def _publish(self,data:dict, routing_key:str)->None:
|
||||||
|
|
||||||
|
if self.exchange is None:
|
||||||
|
await self.setup()
|
||||||
|
|
||||||
message=aio_pika.Message(
|
if self.exchange is None:
|
||||||
body=json.dumps({"email": email}).encode(),
|
raise RuntimeError("Failed to set up RabbitMQ exchange")
|
||||||
|
|
||||||
|
message = aio_pika.Message(
|
||||||
|
body=json.dumps(data).encode(),
|
||||||
delivery_mode=aio_pika.DeliveryMode.PERSISTENT,
|
delivery_mode=aio_pika.DeliveryMode.PERSISTENT,
|
||||||
)
|
)
|
||||||
await self.channel.default_exchange.publish(message=message, routing_key=self.queue.name)
|
await self.exchange.publish(message=message, routing_key=routing_key)
|
||||||
|
|
||||||
email_producer=EmailProducer()
|
email_producer=EmailProducer()
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
from pydantic_settings import SettingsConfigDict
|
||||||
|
|
||||||
|
from src.models.configs_read.env import Base
|
||||||
|
|
||||||
|
|
||||||
|
class DaemonsConfig(Base):
|
||||||
|
|
||||||
|
daemons: list[str]
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(json_file="configs/daemons.json")
|
||||||
|
|
||||||
|
daemons_config = DaemonsConfig() # type: ignore[call-arg]
|
||||||
Reference in New Issue
Block a user