Source code for gunz_cm.compressions._protocol

"""
Codec wire-format protocol for GZCM tile storage.

Defines the contract between codec encoder/decoder pairs and the GZCM container
writer/reader. This is the v5.1 foundation that prevents Bug 0.3
(writing-v4-file-with-lz4-codec-fails-to-decode because the writer prepended
an 8-byte shape header over LZ4's own 12-byte header) from recurring.

The protocol has two parts:

1. ``WireFormat`` enum — declares whether a codec's encoded payload is
   ``OPAQUE_PAYLOAD`` (writer prepends an 8-byte ``(rows, cols)`` shape header)
   or ``SELF_DESCRIBING`` (encoder adds its own header; writer does NOT prepend).

2. ``Codec`` protocol — minimal interface every codec must satisfy:
   ``encode_tile``, ``decode_tile``, ``wire_format``.

Adding a new codec becomes: implement ``Codec``, register it via
``register_codec(...)``. No other code in the writer or reader changes.

See :mod:`gunz_cm.compressions` for the registry + auto-registration logic.
See ``docs/analysis/gzcm-v5-design-proposals.md`` §2 Proposal 1 for the
architectural rationale.
"""
from __future__ import annotations

import enum
from typing import TYPE_CHECKING, Protocol, runtime_checkable

if TYPE_CHECKING:
    import numpy  # noqa: F401  (used only for type annotations)


[docs]class WireFormat(enum.Enum): """How a codec's encoded payload is delimited on disk. Attributes ---------- OPAQUE_PAYLOAD The encoder produces an opaque byte stream with no shape information. The writer prepends an 8-byte ``(rows: int32, cols: int32)`` shape header before the encoded payload so the decoder can reshape on read. Used by: zstd, cmc, bsc, bsc_cmc, cmc_zstd. SELF_DESCRIBING The encoder adds its own header (size, shape, or both) inside the encoded payload. The writer MUST NOT prepend any header; doing so would shift the encoder's header and corrupt the payload. Used by: lz4 (12-byte ``(rows, cols, uncompressed_size)`` header). """ OPAQUE_PAYLOAD = "opaque" SELF_DESCRIBING = "self"
[docs]@runtime_checkable class Codec(Protocol): """Minimal interface every GZCM tile codec must satisfy. Implementations: :class:`CmcEncoder`, :class:`ZstdEncoder`, :class:`CmcZstdEncoder`, :class:`BscEncoder`, :class:`BscCmcEncoder`, :class:`Lz4Encoder`. Decoders are paired via the registry. The :func:`runtime_checkable` decorator lets the registry assert ``isinstance(cls, Codec)`` at registration time without requiring explicit subclassing. """ @property def wire_format(self) -> WireFormat: """What the writer must do (or not do) around this codec's payload.""" ...
[docs] def encode_tile(self, tile) -> bytes: """Encode a 2-D uint tile to bytes. Parameters ---------- tile : numpy.ndarray 2-D array of shape ``(rows, cols)`` with integer dtype. Returns ------- bytes The encoded payload. The wire-format contract (``OPAQUE_PAYLOAD`` vs ``SELF_DESCRIBING``) determines whether the writer prepends an 8-byte shape header before this payload. """ ...
[docs] def decode_tile(self, payload: bytes, shape) -> "numpy.ndarray": """Decode bytes back to a 2-D uint tile of the given shape. Parameters ---------- payload : bytes For ``OPAQUE_PAYLOAD``: the raw encoded bytes (writer-prepended 8-byte shape header has already been stripped by the reader). For ``SELF_DESCRIBING``: the full payload including the encoder's own header. shape : tuple[int, int] ``(rows, cols)``. For ``OPAQUE_PAYLOAD`` this comes from the writer's 8-byte shape header. For ``SELF_DESCRIBING`` this should match what the encoder embedded in its own header. Returns ------- numpy.ndarray 2-D array of shape ``shape`` with integer dtype. """ ...