import base64 from typing import List, Dict from dataclasses import dataclass, field from amqp.model.service_message_type import ServiceMessageType from amqp.model.snowflake_id import SnowflakeId from amqp.router.utils import sanitize_as_rabbitmq_name RS = ":" @dataclass(frozen=True) class AMQRoute: """ Configuration item representing one AMQRoute. Adapter uses this to declare the Exchange and/or queue and bind the key as routing expression. :param component_name: a service/component name that this route points to (owner of the route) :param exchange: RabbitMQ exchange name :param queue: RabbitMQ queue name :param key: RabbitMQ TOPIC Exchange routing key :param timeout: Timeout in milliseconds, how long to wait for reply, or not waiting if less then 1 :param valid_until: optional timestamp (millis from epoch) after which the route is ignored """ component_name: str exchange: str queue: str key: str timeout: int valid_until: int FORMAT = "Component-Name:Exchange-Name:Queue-Name:Routing-Key:Timeout:Valid-Until" def __str__(self): return ( f"{self.component_name}{RS}{self.exchange}{RS}{self.queue}{RS}{self.key}" f"{RS}{self.timeout}{RS}{self.valid_until}" ) def __eq__(self, other): if isinstance(other, AMQRoute): return (self.component_name == other.component_name and self.queue == other.queue and self.key == other.key) return False @property def primary_key(self): return f"{self.component_name}{RS}{self.queue}{RS}{self.key}" def as_string(self) -> str: return self.__str__() @dataclass class AMQMessage: """ The class represents a structure of a message that transfers the user request to the Service. It may also be used to make a call/request between services. The response to this call is in format of AMQResponse class. Please see AMQMessageFactory for code to instantiate, serialize and deserialize the class """ magic: str id: SnowflakeId method: str domain: str path: str headers: dict[str, List[str]] trace_info: dict[str, str] base64_body: bytes def routing_key(self) -> str: return sanitize_as_rabbitmq_name(f"{self.domain}.{self.path}") def body(self) -> bytes: return base64.b64decode(self.base64_body) @dataclass class AMQResponse: """ The response message to RPC style call (note: the request is made with AMQMessage) Please see AMQResponseFactory for code to instantiate, serialize and deserialize the class """ id: str response_code: int content_type: str response: bytes error: str | None error_cause: str | None trace_info: dict[str, str] def body(self) -> bytes: return base64.b64decode(self.response) @dataclass(frozen=True) class CleverMicroServiceMessage: """ The CleverMicro service message that is used to communicate the internal state of the AMQ channel. In v0.1 it supports service discovery / route registration and Backpressure status notification. """ id: SnowflakeId = None payload_type: int = 0 message_type: ServiceMessageType = ServiceMessageType.UNKNOWN recipient_name: str = None reply_to: str = None trace_info: Dict[str, str] = field(default_factory=dict) message: str = None def is_valid(self) -> bool: return self.message_type is not None