messagex-client 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
messagex/frame.py ADDED
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import struct
5
+ from typing import Any, List
6
+
7
+ HEADER_SIZE = 4
8
+ DEFAULT_MAX_FRAME_SIZE = 1024 * 1024
9
+
10
+
11
+ def encode_frame(message: Any, max_frame_size: int = DEFAULT_MAX_FRAME_SIZE) -> bytes:
12
+ """Encode ``message`` as JSON with a 4-byte big-endian length prefix."""
13
+ try:
14
+ text = json.dumps(message, separators=(",", ":"), ensure_ascii=False, allow_nan=False)
15
+ except (TypeError, ValueError) as exc:
16
+ raise TypeError("MessageX protocol message must be JSON serializable") from exc
17
+
18
+ body = text.encode("utf-8")
19
+ if len(body) == 0 or len(body) > max_frame_size:
20
+ raise ValueError(f"MessageX frame size must be between 1 and {max_frame_size} bytes")
21
+ return struct.pack(">I", len(body)) + body
22
+
23
+
24
+ class FrameDecoder:
25
+ """Incremental decoder that turns a byte stream into frame bodies."""
26
+
27
+ def __init__(self, max_frame_size: int = DEFAULT_MAX_FRAME_SIZE) -> None:
28
+ self._max_frame_size = max_frame_size
29
+ self._buffer = bytearray()
30
+
31
+ def push(self, chunk: bytes) -> List[bytes]:
32
+ if not chunk:
33
+ return []
34
+ self._buffer.extend(chunk)
35
+ frames: List[bytes] = []
36
+
37
+ while len(self._buffer) >= HEADER_SIZE:
38
+ (length,) = struct.unpack_from(">I", self._buffer, 0)
39
+ if length < 1 or length > self._max_frame_size:
40
+ self.reset()
41
+ raise ValueError(f"Invalid MessageX frame length: {length}")
42
+
43
+ frame_length = HEADER_SIZE + length
44
+ if len(self._buffer) < frame_length:
45
+ break
46
+
47
+ frames.append(bytes(self._buffer[HEADER_SIZE:frame_length]))
48
+ del self._buffer[:frame_length]
49
+
50
+ return frames
51
+
52
+ def reset(self) -> None:
53
+ self._buffer = bytearray()
54
+
55
+ @property
56
+ def buffered_bytes(self) -> int:
57
+ return len(self._buffer)
messagex/py.typed ADDED
File without changes
messagex/types.py ADDED
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Awaitable, Callable, Dict, Optional, TypedDict, Union
5
+
6
+ try: # Python 3.8+
7
+ from typing import Literal
8
+ except ImportError: # pragma: no cover
9
+ from typing_extensions import Literal # type: ignore
10
+
11
+ ExchangeType = Literal["direct", "fanout", "topic", "headers"]
12
+
13
+
14
+ @dataclass
15
+ class DeliveredMessage:
16
+ """A message delivered to a consumer. Settle it with :meth:`ack` or :meth:`nack`."""
17
+
18
+ consumer_tag: str
19
+ queue_name: str
20
+ message_id: str
21
+ delivery_tag: str
22
+ body: Any
23
+ headers: Dict[str, Any]
24
+ routing_key: str
25
+ delivery_count: int
26
+ created_at: str
27
+ _ack: Optional[Callable[[], Awaitable[None]]] = field(default=None, repr=False, compare=False)
28
+ _nack: Optional[Callable[[bool], Awaitable[None]]] = field(default=None, repr=False, compare=False)
29
+
30
+ async def ack(self) -> None:
31
+ """Acknowledge successful processing."""
32
+ assert self._ack is not None
33
+ await self._ack()
34
+
35
+ async def nack(self, requeue: bool = True) -> None:
36
+ """Reject the delivery, optionally requeueing it."""
37
+ assert self._nack is not None
38
+ await self._nack(requeue)
39
+
40
+
41
+ MessageHandler = Callable[[DeliveredMessage], Union[None, Awaitable[None]]]
42
+
43
+
44
+ @dataclass
45
+ class ConsumerSubscription:
46
+ consumer_tag: str
47
+ queue_name: str
48
+ _cancel: Callable[[], Awaitable[None]] = field(repr=False, compare=False)
49
+
50
+ async def cancel(self) -> None:
51
+ await self._cancel()
52
+
53
+
54
+ @dataclass
55
+ class PublishResult:
56
+ exchange_name: str
57
+ routed_queues: int
58
+
59
+
60
+ class QueueInfo(TypedDict, total=False):
61
+ id: str
62
+ name: str
63
+ durable: bool
64
+ exclusive: bool
65
+ autoDelete: bool
66
+ maxDeliveryCount: Optional[int]
67
+ deadLetterQueue: Optional[str]
68
+
69
+
70
+ class ExchangeInfo(TypedDict, total=False):
71
+ id: str
72
+ name: str
73
+ type: str
74
+ durable: bool
75
+
76
+
77
+ class BindingInfo(TypedDict, total=False):
78
+ id: str
79
+ exchangeName: str
80
+ queueName: str
81
+ routingKey: str
82
+ arguments: Dict[str, Any]