Source code for dag.codec
"""Codec interface definitions for IPLD.
This module defines the abstract interfaces that every IPLD codec must
implement, following the same pattern as the JS ``@ipld/interface``
(``BlockEncoder`` / ``BlockDecoder`` / ``BlockCodec``).
A **codec** is identified by:
- ``name`` - a human-readable string (e.g. ``"dag-cbor"``)
- ``code`` - the multicodec code (e.g. ``0x71``)
And provides two operations:
- ``encode(node)`` → ``bytes`` - serialize an IPLD node
- ``decode(data)`` → ``node`` - deserialize bytes into an IPLD node
"""
from __future__ import annotations
import abc
from .ipld_model import IPLDNode
[docs]
class BlockEncoder(abc.ABC):
"""Abstract encoder - turns an IPLD node into bytes."""
@property
@abc.abstractmethod
def name(self) -> str:
"""Human-readable codec name (e.g. ``'dag-cbor'``)."""
@property
@abc.abstractmethod
def code(self) -> int:
"""Multicodec code (e.g. ``0x71``)."""
[docs]
@abc.abstractmethod
def encode(self, node: IPLDNode) -> bytes:
"""Encode an IPLD data model value into bytes."""
[docs]
class BlockDecoder(abc.ABC):
"""Abstract decoder - turns bytes into an IPLD node."""
@property
@abc.abstractmethod
def name(self) -> str:
"""Human-readable codec name."""
@property
@abc.abstractmethod
def code(self) -> int:
"""Multicodec code."""
[docs]
@abc.abstractmethod
def decode(self, data: bytes) -> IPLDNode:
"""Decode bytes into an IPLD data model value."""
[docs]
class BlockCodec(BlockEncoder, BlockDecoder):
"""A codec that can both encode and decode.
Concrete implementations should subclass this and provide
``name``, ``code``, ``encode``, and ``decode``.
"""
_codec_registry: dict[int, BlockCodec] = {}
[docs]
def register_codec(codec: BlockCodec) -> None:
"""Register a codec so it can be looked up by code."""
_codec_registry[codec.code] = codec
[docs]
def get_codec(code: int) -> BlockCodec:
"""Look up a registered codec by its multicodec code.
Raises ``KeyError`` if no codec is registered for *code*.
"""
if code not in _codec_registry:
raise KeyError(
f"No codec registered for code 0x{code:x}. Registered: {sorted(f'0x{c:x}' for c in _codec_registry)}"
)
return _codec_registry[code]
[docs]
def registered_codecs() -> dict[int, BlockCodec]:
"""Return a copy of all registered codecs."""
return dict(_codec_registry)
[docs]
def lookup_codec(name_or_code: str | int) -> BlockCodec:
"""Look up a codec by name or code.
Raises ``KeyError`` if not found.
"""
if isinstance(name_or_code, int):
return get_codec(name_or_code)
for codec in _codec_registry.values():
if codec.name == name_or_code:
return codec
raise KeyError(f"No codec registered with name {name_or_code!r}")