jam.utils.codec.codable

Base class for types that can encode and decode themselves.

Interface

Codable[T]

class Codable(Generic[T]):
    def __init__(self,
                codec: Optional[Codec[T]] = None,
                enc_sequence: Optional[Callable[[], list[Any]]] = None):
        self.codec = codec
        self.enc_sequence = enc_sequence

Usage Patterns

Direct Codec

Use a specific codec for encoding/decoding:

class Point(Codable[tuple[int, int]]):
    def __init__(self, x: int, y: int):
        super().__init__(codec=TupleCodec(IntCodec(), IntCodec()))
        self.x = x
        self.y = y

    def value(self) -> tuple[int, int]:
        return (self.x, self.y)

Sequence Encoding

Encode as a sequence of values:

class Point(Codable[tuple[int, int]]):
    def __init__(self, x: int, y: int):
        super().__init__(enc_sequence=lambda: [self.x, self.y])
        self.x = x
        self.y = y

Custom Encoding

Implement custom encode/decode logic:

class Point(Codable[tuple[int, int]]):
    def encode(self) -> bytes:
        return bytes([self.x, self.y])

    @classmethod
    def decode(cls, buffer: bytes) -> 'Point':
        return cls(buffer[0], buffer[1])

Implementation Details

Value Property

The value property provides the raw value for encoding:

@property
def value(self) -> T:
    """Get the value to encode."""
    if hasattr(self, 'get_value'):
        return self.get_value()
    return self._value

Encoding Process

  1. Get value via property

  2. Use codec if provided

  3. Use sequence if provided

  4. Call custom encode()

Decoding Process

  1. Use codec if provided

  2. Use sequence if provided

  3. Call custom decode()

  4. Set value property

Error Handling

Common error cases:

  1. Missing codec/sequence:

    if not self.codec and not self.enc_sequence:
        raise CodecError("No codec or sequence provided")
    
  2. Invalid sequence:

    if not all(isinstance(x, Codable) for x in sequence):
        raise CodecError("Sequence contains non-Codable values")
    

Examples

Basic Usage

class Point(Codable[tuple[int, int]]):
    def __init__(self, x: int, y: int):
        super().__init__(codec=TupleCodec(IntCodec(), IntCodec()))
        self.x = x
        self.y = y

    def value(self) -> tuple[int, int]:
        return (self.x, self.y)

point = Point(1, 2)
encoded = point.encode()  # -> [01 00 00 00 02 00 00 00]
decoded = Point.decode(encoded)  # -> Point(1, 2)

Sequence Example

class Vector(Codable[list[int]]):
    def __init__(self, *components: int):
        super().__init__(enc_sequence=lambda: list(components))
        self.components = components

vec = Vector(1, 2, 3)
encoded = vec.encode()  # -> [03 01 02 03]
decoded = Vector.decode(encoded)  # -> Vector(1, 2, 3)

API Reference

Base class for types that can encode/decode themselves.

class jam.utils.codec.codable.Codable(codec: Codec[Any] | None = None)[source]

Bases: Generic[T]

Base class for all codable types.

Can be used in three ways: 1. With a codec: Initialize with codec=some_codec 2. With sequence: Initialize with enc_sequence=lambda: [field1, field2, …] 3. With JSON: Use to_json() and from_json() for JSON serialization

value: Any = None
__init__(codec: Codec[Any] | None = None)[source]

Initialize the Codable.

Parameters:
  • codec – Optional codec to use for encoding/decoding

  • enc_sequence – Optional function that returns sequence of fields to encode

codec: Codec[Any] | None = None
encode_size() int[source]

Calculate number of bytes needed to encode.

encode() bytes[source]

Encode into bytes.

encode_into(buffer: bytearray, offset: int = 0) int[source]

Encode into provided buffer.

static decode_from(buffer: bytes | bytearray | memoryview, offset: int = 0) Tuple[Any, int][source]

Decode from buffer. Must be implemented by subclasses or added via decorator.

Parameters:
  • buffer – Buffer to decode from

  • offset – Starting position in buffer

Returns:

  • The decoded value

  • Number of bytes read

Return type:

Tuple containing