Source code for aiowamp.message

"""Contains the abstract message type and message utilities.

The built-in message definitions can be found in the msg.py file.
"""

from __future__ import annotations

import abc
from typing import ClassVar, Dict, List, Optional, Type, TypeVar, Union, cast

import aiowamp
from .errors import InvalidMessage

__all__ = ["WAMPType", "WAMPList", "WAMPDict",
           "MessageABC",
           "is_message_type", "message_as_type",
           "register_message_cls", "get_message_cls", "build_message_from_list"]

WAMPType = Union[int, str, bool, "aiowamp.WAMPList", "aiowamp.WAMPDict"]
"""WAMP type.

Contains value types which SHOULD be supported by all serializers.
"""

WAMPList = List["aiowamp.WAMPType"]
"""Type of a list containing WAMP types."""

WAMPDict = Dict[str, "aiowamp.WAMPDict"]
"""Dict with string keys and WAMP type values."""

T = TypeVar("T")


[docs]class MessageABC(abc.ABC): """Base class for WAMP messages.""" __slots__ = () message_type: ClassVar[int] """Type code of the message."""
[docs] def __str__(self) -> str: return f"{self.message_type} {type(self).__qualname__}"
[docs] @abc.abstractmethod def to_message_list(self) -> aiowamp.WAMPList: """Convert the message to a list Returns: List containing the message including the message type. """ ...
[docs] @classmethod @abc.abstractmethod def from_message_list(cls: Type[T], msg_list: aiowamp.WAMPList) -> T: """Build the message from msg_list. Args: msg_list: Message list without the message type. Returns: Instance of the message type. """ ...
[docs]def is_message_type(msg: aiowamp.MessageABC, msg_type: Type["aiowamp.MessageABC"]) -> bool: """Checks whether msg is of type msg_type. This should be preferred over `isinstance` checks as it supports duck typing. If, however, the built-in message class is overwritten with a custom one and you depend on a specific feature of said class then `isinstance` should be used. Args: msg: Message to check. msg_type: Message type to check against. IMPORTANT: The function doesn't actually cast to the type, it is assumed that if two message types share the same type code, they are interchangeable. Returns: Whether msg and msg_type have the same `aiowamp.MessageABC.message_type`. """ return msg.message_type == msg_type.message_type
MsgT = TypeVar("MsgT", bound="aiowamp.MessageABC")
[docs]def message_as_type(msg: aiowamp.MessageABC, msg_type: Type[MsgT]) -> Optional[MsgT]: """Cast msg if it is of type msg_type. Uses `aiowamp.is_message_type` to check if msg is of type msg_type. Args: msg: Message to cast. msg_type: Message type to cast to. Returns: `None`, if msg is not of type msg_type, otherwise it returns msg. Examples: This function can be used as a type guard: >>> import aiowamp >>> msg: aiowamp.MessageABC = aiowamp.msg.Hello(aiowamp.URI(""), {}) >>> # type of hello_msg is inferred as Optional[aiowamp.msg.Hello] >>> hello_msg = message_as_type(msg, aiowamp.msg.Hello) >>> hello_msg is msg True """ if is_message_type(msg, msg_type): return cast(MsgT, msg) return None
MESSAGE_TYPE_MAP: Dict[int, Type["aiowamp.MessageABC"]] = {}
[docs]def register_message_cls(*messages: Type["aiowamp.MessageABC"], overwrite: bool = False) -> None: """Register the messages. Args: *messages: Message types to register. overwrite: If existing types should be overwritten. Defaults to `False` which makes overwriting an existing message type an error. Raises: ValueError: If the message type for a message is already registered by another type and overwrite is `False`. """ for m in messages: try: mtyp = m.message_type except AttributeError: raise NotImplementedError(f"{m!r} does not overwrite the 'message_type' attribute.") from None if not overwrite and mtyp in MESSAGE_TYPE_MAP: raise ValueError(f"cannot register message {m!r} for code {mtyp}, " f"already used by {MESSAGE_TYPE_MAP[mtyp]!r}") MESSAGE_TYPE_MAP[mtyp] = m
[docs]def get_message_cls(msg_type: int) -> Type["aiowamp.MessageABC"]: """Returns the message type registered for msg_type. Args: msg_type: Message type to look for. Returns: Message type registered for msg_type. Raises: KeyError: If no message type is registered for msg_type. """ try: return MESSAGE_TYPE_MAP[msg_type] except KeyError: raise KeyError(f"no message type registered for type: {msg_type}") from None
[docs]def build_message_from_list(msg_list: aiowamp.WAMPList) -> aiowamp.MessageABC: """Create a message from msg_list. Uses the registered message type. Args: msg_list: Message list containing the message type as the first element. Returns: Built message. Raises: aiowamp.InvalidMessage: If the message is invalid. """ try: msg_type = msg_list[0] except IndexError: raise InvalidMessage("received message without message type") from None if not isinstance(msg_type, int): raise InvalidMessage("received message without integer message type") try: msg_cls = get_message_cls(msg_type) except KeyError: raise InvalidMessage(f"no message class for type {msg_type!r} found") try: return msg_cls.from_message_list(msg_list[1:]) except Exception as e: raise InvalidMessage(f"failed to build message type {msg_cls!r} from message list") from e