pamoja.codec

Idiomatic codec facade.

Python already has json, so this facade takes and returns ordinary values and does the encoding itself, leaving callers to think in documents rather than buffers. The conversion and the packing happen in the Rust core.

  1"""Idiomatic codec facade.
  2
  3Python already has :mod:`json`, so this facade takes and returns ordinary values
  4and does the encoding itself, leaving callers to think in documents rather than
  5buffers. The conversion and the packing happen in the Rust core.
  6"""
  7
  8from __future__ import annotations
  9
 10import json
 11from typing import Any, Sequence
 12
 13from pamoja._native import Quantizer as _NativeQuantizer
 14from pamoja._native import cbor_to_json_bytes as _cbor_to_json_bytes
 15from pamoja._native import decode_delta_samples as _decode_delta_samples
 16from pamoja._native import encode_delta_samples as _encode_delta_samples
 17from pamoja._native import json_to_cbor_bytes as _json_to_cbor_bytes
 18
 19__all__ = ["Quantizer", "from_cbor", "pack_samples", "to_cbor", "unpack_samples"]
 20
 21
 22def to_cbor(value: Any) -> bytes:
 23    """Encode a value as CBOR, typically much smaller than its JSON form.
 24
 25    :param value: Any JSON-serializable value, or the raw bytes of a JSON
 26        document.
 27    :returns: The CBOR encoding.
 28    :raises ValueError: If the value holds a NaN or an infinity, which JSON
 29        has no way to write.
 30    :raises TypeError: If the value holds something else JSON cannot write,
 31        such as a set.
 32    :raises PamojaError: If the bytes given are not a valid JSON document.
 33    """
 34    if isinstance(value, (bytes, bytearray, memoryview)):
 35        document = bytes(value)
 36    else:
 37        document = json.dumps(value, allow_nan=False).encode("utf-8")
 38    return _json_to_cbor_bytes(document)
 39
 40
 41def from_cbor(cbor: bytes) -> Any:
 42    """Decode a CBOR document back into an ordinary Python value.
 43
 44    :param cbor: The CBOR document to decode.
 45    :returns: The decoded value.
 46    :raises PamojaError: If the document is malformed, or holds a construct with
 47        no JSON equivalent such as a non-string map key.
 48    """
 49    return json.loads(_cbor_to_json_bytes(bytes(cbor)).decode("utf-8"))
 50
 51
 52def pack_samples(samples: Sequence[int]) -> bytes:
 53    """Delta-encode a series of integer samples into a compact buffer.
 54
 55    :param samples: The samples, in order.
 56    :returns: The packed encoding, far smaller than the samples for a
 57        slow-moving series.
 58    :raises TypeError: If a sample is not an ``int``.
 59    :raises OverflowError: If a sample does not fit in 64 bits.
 60    """
 61    return _encode_delta_samples(list(samples))
 62
 63
 64def unpack_samples(data: bytes) -> list[int]:
 65    """Unpack a buffer produced by :func:`pack_samples`.
 66
 67    :param data: The packed encoding.
 68    :returns: The samples, in order.
 69    :raises PamojaError: If the buffer is malformed or carries bytes after its
 70        last sample.
 71    """
 72    return _decode_delta_samples(bytes(data))
 73
 74
 75class Quantizer:
 76    """Packs float readings to a fixed precision, for a link charging per byte.
 77
 78    Example::
 79
 80        quantizer = Quantizer(100)  # keep two decimal places
 81        packed = quantizer.encode([20.0, 20.1, 20.2])
 82        quantizer.decode(packed)  # [20.0, 20.1, 20.2], to within 0.01
 83    """
 84
 85    __slots__ = ("_native",)
 86
 87    def __init__(self, scale: float) -> None:
 88        """Create a quantizer at the given precision.
 89
 90        :param scale: The multiplier applied before rounding; ``100`` keeps two
 91            decimal places. Must be positive and finite.
 92        :raises ValueError: If the scale is not positive and finite.
 93        """
 94        self._native = _NativeQuantizer(scale)
 95
 96    def encode(self, readings: Sequence[float]) -> bytes:
 97        """Quantize and pack a batch of readings.
 98
 99        :param readings: The readings, in order.
100        :returns: The packed encoding.
101        :raises PamojaError: If a reading is not a number, is infinite, or is too
102            large for the scale; the format has no way to carry a missing reading.
103        """
104        return self._native.encode(list(readings))
105
106    def decode(self, data: bytes) -> list[float]:
107        """Unpack a batch, to within this quantizer's precision.
108
109        :param data: The encoding produced by :meth:`encode` at the same scale.
110        :returns: The readings, in order.
111        :raises PamojaError: If the buffer is malformed or carries bytes after its
112            last reading.
113        """
114        return self._native.decode(bytes(data))
class Quantizer:
 76class Quantizer:
 77    """Packs float readings to a fixed precision, for a link charging per byte.
 78
 79    Example::
 80
 81        quantizer = Quantizer(100)  # keep two decimal places
 82        packed = quantizer.encode([20.0, 20.1, 20.2])
 83        quantizer.decode(packed)  # [20.0, 20.1, 20.2], to within 0.01
 84    """
 85
 86    __slots__ = ("_native",)
 87
 88    def __init__(self, scale: float) -> None:
 89        """Create a quantizer at the given precision.
 90
 91        :param scale: The multiplier applied before rounding; ``100`` keeps two
 92            decimal places. Must be positive and finite.
 93        :raises ValueError: If the scale is not positive and finite.
 94        """
 95        self._native = _NativeQuantizer(scale)
 96
 97    def encode(self, readings: Sequence[float]) -> bytes:
 98        """Quantize and pack a batch of readings.
 99
100        :param readings: The readings, in order.
101        :returns: The packed encoding.
102        :raises PamojaError: If a reading is not a number, is infinite, or is too
103            large for the scale; the format has no way to carry a missing reading.
104        """
105        return self._native.encode(list(readings))
106
107    def decode(self, data: bytes) -> list[float]:
108        """Unpack a batch, to within this quantizer's precision.
109
110        :param data: The encoding produced by :meth:`encode` at the same scale.
111        :returns: The readings, in order.
112        :raises PamojaError: If the buffer is malformed or carries bytes after its
113            last reading.
114        """
115        return self._native.decode(bytes(data))

Packs float readings to a fixed precision, for a link charging per byte.

Example::

quantizer = Quantizer(100)  # keep two decimal places
packed = quantizer.encode([20.0, 20.1, 20.2])
quantizer.decode(packed)  # [20.0, 20.1, 20.2], to within 0.01
Quantizer(scale: float)
88    def __init__(self, scale: float) -> None:
89        """Create a quantizer at the given precision.
90
91        :param scale: The multiplier applied before rounding; ``100`` keeps two
92            decimal places. Must be positive and finite.
93        :raises ValueError: If the scale is not positive and finite.
94        """
95        self._native = _NativeQuantizer(scale)

Create a quantizer at the given precision.

Parameters
  • scale: The multiplier applied before rounding; 100 keeps two decimal places. Must be positive and finite.
Raises
  • ValueError: If the scale is not positive and finite.
def encode(self, readings: Sequence[float]) -> bytes:
 97    def encode(self, readings: Sequence[float]) -> bytes:
 98        """Quantize and pack a batch of readings.
 99
100        :param readings: The readings, in order.
101        :returns: The packed encoding.
102        :raises PamojaError: If a reading is not a number, is infinite, or is too
103            large for the scale; the format has no way to carry a missing reading.
104        """
105        return self._native.encode(list(readings))

Quantize and pack a batch of readings.

Parameters
  • readings: The readings, in order. :returns: The packed encoding.
Raises
  • PamojaError: If a reading is not a number, is infinite, or is too large for the scale; the format has no way to carry a missing reading.
def decode(self, data: bytes) -> list[float]:
107    def decode(self, data: bytes) -> list[float]:
108        """Unpack a batch, to within this quantizer's precision.
109
110        :param data: The encoding produced by :meth:`encode` at the same scale.
111        :returns: The readings, in order.
112        :raises PamojaError: If the buffer is malformed or carries bytes after its
113            last reading.
114        """
115        return self._native.decode(bytes(data))

Unpack a batch, to within this quantizer's precision.

Parameters
  • data: The encoding produced by encode() at the same scale. :returns: The readings, in order.
Raises
  • PamojaError: If the buffer is malformed or carries bytes after its last reading.
def from_cbor(cbor: bytes) -> Any:
42def from_cbor(cbor: bytes) -> Any:
43    """Decode a CBOR document back into an ordinary Python value.
44
45    :param cbor: The CBOR document to decode.
46    :returns: The decoded value.
47    :raises PamojaError: If the document is malformed, or holds a construct with
48        no JSON equivalent such as a non-string map key.
49    """
50    return json.loads(_cbor_to_json_bytes(bytes(cbor)).decode("utf-8"))

Decode a CBOR document back into an ordinary Python value.

Parameters
  • cbor: The CBOR document to decode. :returns: The decoded value.
Raises
  • PamojaError: If the document is malformed, or holds a construct with no JSON equivalent such as a non-string map key.
def pack_samples(samples: Sequence[int]) -> bytes:
53def pack_samples(samples: Sequence[int]) -> bytes:
54    """Delta-encode a series of integer samples into a compact buffer.
55
56    :param samples: The samples, in order.
57    :returns: The packed encoding, far smaller than the samples for a
58        slow-moving series.
59    :raises TypeError: If a sample is not an ``int``.
60    :raises OverflowError: If a sample does not fit in 64 bits.
61    """
62    return _encode_delta_samples(list(samples))

Delta-encode a series of integer samples into a compact buffer.

Parameters
  • samples: The samples, in order. :returns: The packed encoding, far smaller than the samples for a slow-moving series.
Raises
  • TypeError: If a sample is not an int.
  • OverflowError: If a sample does not fit in 64 bits.
def to_cbor(value: Any) -> bytes:
23def to_cbor(value: Any) -> bytes:
24    """Encode a value as CBOR, typically much smaller than its JSON form.
25
26    :param value: Any JSON-serializable value, or the raw bytes of a JSON
27        document.
28    :returns: The CBOR encoding.
29    :raises ValueError: If the value holds a NaN or an infinity, which JSON
30        has no way to write.
31    :raises TypeError: If the value holds something else JSON cannot write,
32        such as a set.
33    :raises PamojaError: If the bytes given are not a valid JSON document.
34    """
35    if isinstance(value, (bytes, bytearray, memoryview)):
36        document = bytes(value)
37    else:
38        document = json.dumps(value, allow_nan=False).encode("utf-8")
39    return _json_to_cbor_bytes(document)

Encode a value as CBOR, typically much smaller than its JSON form.

Parameters
  • value: Any JSON-serializable value, or the raw bytes of a JSON document. :returns: The CBOR encoding.
Raises
  • ValueError: If the value holds a NaN or an infinity, which JSON has no way to write.
  • TypeError: If the value holds something else JSON cannot write, such as a set.
  • PamojaError: If the bytes given are not a valid JSON document.
def unpack_samples(data: bytes) -> list[int]:
65def unpack_samples(data: bytes) -> list[int]:
66    """Unpack a buffer produced by :func:`pack_samples`.
67
68    :param data: The packed encoding.
69    :returns: The samples, in order.
70    :raises PamojaError: If the buffer is malformed or carries bytes after its
71        last sample.
72    """
73    return _decode_delta_samples(bytes(data))

Unpack a buffer produced by pack_samples().

Parameters
  • data: The packed encoding. :returns: The samples, in order.
Raises
  • PamojaError: If the buffer is malformed or carries bytes after its last sample.