orchestrata_ultimate.cxp.binary_serializer
Reference › orchestrata_ultimate › cxp › binary_serializer
BinarySerializer is the compact binary form of the ISerializer contract (Spec 004 US-006). It uses MessagePack when the msgpack package is installed and otherwise falls back to gzip-compressed JSON. The output carries a one-byte prefix, 0x01 for MessagePack and 0x02 for gzip JSON, so from_bytes can detect the format; it raises DeserializationError for input that is not bytes, is too short to hold a prefix, or cannot be decoded. to_bytes converts the packet with to_dict, prepares values with the private _encode_value (and _decode_value on the way back) and raises SerializationError with the packet id on failure. The text form is json_serializer. See exceptions and cxp.
At a glance
| Source | src/orchestrata_ultimate/cxp/binary_serializer.py |
| Lines of code | 244 |
| Classes | 1 |
| Functions | 2 |
| Imports (internal) | orchestrata_ultimate.core.base.traces, orchestrata_ultimate.core.packet, orchestrata_ultimate.cxp.exceptions |
| Imported by | orchestrata_ultimate.cxp |
| Tests that import it | tests/cxp/test_binary_serializer.py |
Class diagram
Dependency diagram
API
Classes
BinarySerializer
Decorators: traces(spec='004', story='US-006', reqs=['REQ-004-US-006-017', 'REQ-004-US-006-021']) · Line 93
Binary serializer for CXP packets.
| Method | Signature | Summary |
|---|---|---|
__init__ |
__init__() -> None |
|
_has_msgpack property |
_has_msgpack() -> bool |
True when the msgpack library is importable. |
to_bytes |
to_bytes(packet: CXPPacket) -> bytes |
Serialize a CXPPacket to bytes. |
from_bytes |
from_bytes(data: bytes) -> CXPPacket |
Deserialize bytes to a CXPPacket. |
Functions
| Function | Signature | Summary |
|---|---|---|
_encode_value traces |
_encode_value(obj: Any) -> Any |
Recursively convert non-serializable types for msgpack/json. |
_decode_value traces |
_decode_value(obj: Any) -> Any |
Recursively restore encoded types from msgpack/json output. |
Source
# mypy: ignore-errors
"""
CXP Binary Serializer.
Binary serialization and deserialization for CXP packets.
Uses MessagePack when available, falls back to JSON + gzip.
Implements ISerializer binary methods per Spec 004 Section US-006.
Performance targets:
- to_bytes: < 5ms for 1KB payload
- from_bytes: < 5ms
Author: Mohamed
"""
import base64
import gzip
import json
import logging
from datetime import datetime
from enum import Enum
from typing import Any, cast
import msgpack as _msgpack
from orchestrata_ultimate.core.base.traces import traces
from orchestrata_ultimate.core.packet import CXPPacket
from orchestrata_ultimate.cxp.exceptions import DeserializationError, SerializationError
# Spec: 004 - CXP Protocol
__all__ = ["BinarySerializer"]
logger = logging.getLogger(__name__)
# ---------- msgpack availability ----------
_msgpack_available = True
# ---------- helpers ----------
@traces(spec="004", story="US-006", reqs=["REQ-004-US-006-018"])
def _encode_value(obj: Any) -> Any:
"""Recursively convert non-serializable types for msgpack/json.
Handles datetime, Enum, bytes, set, frozenset.
"""
if isinstance(obj, datetime):
return {"__datetime__": obj.isoformat()}
if isinstance(obj, Enum):
return obj.value
if isinstance(obj, bytes):
return {"__bytes__": base64.b64encode(obj).decode("ascii")}
if isinstance(obj, (set, frozenset)):
return sorted(obj, key=str)
if isinstance(obj, dict):
return {k: _encode_value(v) for k, v in obj.items()}
if isinstance(obj, (list, tuple)):
return [_encode_value(item) for item in obj]
return obj
@traces(spec="004", story="US-006", reqs=["REQ-004-US-006-022"])
def _decode_value(obj: Any) -> Any:
"""Recursively restore encoded types from msgpack/json output."""
if isinstance(obj, dict):
if "__datetime__" in obj and len(obj) == 1:
iso = obj["__datetime__"]
return datetime.fromisoformat(iso.replace("Z", "+00:00"))
if "__bytes__" in obj and len(obj) == 1:
return base64.b64decode(obj["__bytes__"])
return {k: _decode_value(v) for k, v in obj.items()}
if isinstance(obj, (list, tuple)):
return [_decode_value(item) for item in obj]
if isinstance(obj, bytes):
# msgpack sometimes returns bytes for string keys
try:
return obj.decode("utf-8")
except UnicodeDecodeError:
return obj
return obj
@traces(
spec="004",
story="US-006",
reqs=["REQ-004-US-006-017", "REQ-004-US-006-021"],
)
class BinarySerializer:
"""Binary serializer for CXP packets.
Uses MessagePack when the ``msgpack`` package is installed.
Falls back to JSON-encoded bytes compressed with gzip otherwise.
Conforms to ISerializer binary methods (Spec 004 Section US-006).
Usage:
serializer = BinarySerializer()
raw = serializer.to_bytes(packet)
restored = serializer.from_bytes(raw)
"""
# 1-byte format prefix so from_bytes knows which codec produced the blob.
_MSGPACK_PREFIX = b"\x01"
_GZIP_JSON_PREFIX = b"\x02"
def __init__(self) -> None:
logger.debug(
"BinarySerializer initialized (msgpack=%s)",
"available" if _msgpack_available else "fallback:json+gzip",
)
# -- public properties --
@property
def _has_msgpack(self) -> bool:
"""True when the msgpack library is importable."""
return _msgpack_available
# -- public API --
@traces(
spec="004",
story="US-006",
reqs=[
"REQ-004-PERF-003",
"REQ-004-US-006-017",
"REQ-004-US-006-018",
"REQ-004-US-006-019",
],
)
def to_bytes(self, packet: CXPPacket) -> bytes:
"""Serialize a CXPPacket to bytes.
If msgpack is available, the output is prefixed with 0x01
followed by msgpack-encoded data. Otherwise, the output is
prefixed with 0x02 followed by gzip-compressed JSON.
Args:
packet: The CXP packet to serialize.
Returns:
Binary representation of the packet.
Raises:
SerializationError: If serialization fails.
"""
try:
data = packet.to_dict()
prepared = _encode_value(data)
payload = _msgpack.packb(prepared, use_bin_type=True)
# cast: msgpack's fallback.Packer.pack() stub-infers `bytes | None`
# (None only on the non-default autoreset=False path, which we
# never take), so packb() is bytes here.
return self._MSGPACK_PREFIX + cast(bytes, payload)
except (TypeError, ValueError, OverflowError, RecursionError) as exc:
raise SerializationError(
message=f"Binary serialization failed: {exc}",
format="binary",
details={"packet_id": getattr(packet, "message_id", "unknown")},
) from exc
@traces(
spec="004",
story="US-006",
reqs=[
"REQ-004-PERF-004",
"REQ-004-US-006-021",
"REQ-004-US-006-022",
"REQ-004-US-006-023",
"REQ-004-US-006-024",
],
)
def from_bytes(self, data: bytes) -> CXPPacket:
"""Deserialize bytes to a CXPPacket.
Detects the format prefix (0x01 = msgpack, 0x02 = gzip+json)
and decodes accordingly.
Args:
data: Binary data previously produced by to_bytes().
Returns:
Reconstructed CXPPacket instance.
Raises:
DeserializationError: If the data is invalid or packet
reconstruction fails.
"""
if not isinstance(data, (bytes, bytearray)):
raise DeserializationError(
message=f"Expected bytes, got {type(data).__name__}",
format="binary",
)
if len(data) < 2:
raise DeserializationError(
message="Binary data too short (missing format prefix)",
format="binary",
)
prefix = data[:1]
payload = data[1:]
try:
if prefix == self._MSGPACK_PREFIX:
raw = _msgpack.unpackb(payload, raw=False)
elif prefix == self._GZIP_JSON_PREFIX:
json_bytes = gzip.decompress(payload)
raw = json.loads(json_bytes.decode("utf-8"))
else:
raise DeserializationError(
message=f"Unknown binary format prefix: {prefix!r}",
format="binary",
)
except DeserializationError:
raise
except Exception as exc:
raise DeserializationError(
message=f"Binary deserialization failed: {exc}",
format="binary",
) from exc
decoded = _decode_value(raw)
if not isinstance(decoded, dict):
raise DeserializationError(
message=f"Decoded data must be a dict, got {type(decoded).__name__}",
format="binary",
)
try:
return CXPPacket.from_dict(decoded)
except (KeyError, TypeError, ValueError) as exc:
raise DeserializationError(
message=f"Packet reconstruction failed: {exc}",
format="binary",
details={"keys_present": list(decoded.keys())},
) from exc