""" Брокер dramatiq поверх RabbitMQ (#78) и сообщения, которыми api ставит работу воркерам. Каждый воркер слушает общую очередь `robotex` и персональную `robotex.worker.`. В персональную api отправляет задачи сессии, чьё окно открыто на этом воркере (#83), иначе — в общую. Сообщение адресуется actor'у по имени, поэтому api не импортирует код воркера (и Chrome ему не нужен). `RABBITMQ_URL=stub://` — брокер в памяти для тестов. """ import os import dramatiq from dramatiq.brokers.stub import StubBroker from dramatiq.middleware import AsyncIO COMMON_QUEUE = "robotex" RUN_TASK = "run_task" CLOSE_WINDOW = "close_window" def personal_queue(worker_id: str) -> str: return f"{COMMON_QUEUE}.worker.{worker_id}" def make_broker(url: str) -> dramatiq.Broker: if url.startswith("stub://"): broker = StubBroker() else: from dramatiq.brokers.rabbitmq import RabbitmqBroker # confirm_delivery: enqueue возвращается, только когда RabbitMQ принял сообщение broker = RabbitmqBroker(url=url, confirm_delivery=True) broker.add_middleware(AsyncIO()) return broker broker = make_broker(os.getenv("RABBITMQ_URL", "amqp://guest:guest@localhost:5672/")) dramatiq.set_broker(broker) def task_message( task_id: str, session_id: str, payload: dict, idle_timeout: int, queue: str = COMMON_QUEUE, meta: dict | None = None, ) -> dramatiq.Message: """payload — SolveRequest/TaskRequest.model_dump(): в нём прокси и ключ 2captcha, поэтому он живёт только в сообщении, а не в Redis. meta — для журнала: client_id, api (v1/v2).""" return dramatiq.Message( queue_name=queue, actor_name=RUN_TASK, args=(task_id, session_id, payload, idle_timeout, meta or {}), kwargs={}, options={}, ) def close_message(session_id: str, queue: str) -> dramatiq.Message: return dramatiq.Message(queue_name=queue, actor_name=CLOSE_WINDOW, args=(session_id,), kwargs={}, options={})