jam.types.base.choices

Choice type implementations for the JAM protocol.

Type Definitions

Choice[T]

  • Tagged union type

  • Discriminant + value encoding

  • Type-safe variant selection

  • Compile-time variant checking

Option[T]

  • Nullable type wrapper

  • Special case of Choice

  • None or Some(T)

  • Zero overhead for None

Encoding Format

Choice[T]

Tagged union format:

[Tag: varint][Value: varies]
  • Tag: Index of the variant (0-based)

  • Value: Encoded variant value

Option[T]

Special encoding:

None -> [00]  # Single byte
Some(value) -> [01][Encoded value]

Implementation Details

Memory Layout

  • Tag: Variable size integer

  • Value: Variable size

  • No padding between tag and value

  • Alignment preserved for value

Type Safety

  • Variant types checked at compile time

  • Runtime type validation

  • Tag range validation

  • Value type checking

Error Handling

Common error cases:

  1. Invalid variant:

    if tag >= len(variants):
        raise ValueError(f"Invalid variant tag: {tag}")
    
  2. Type mismatch:

    if not isinstance(value, variant_type):
        raise TypeError(f"Expected {variant_type}, got {type(value)}")
    
  3. Buffer overflow:

    if len(buffer) - offset < tag_size:
        raise BufferError(f"Buffer too small for tag")
    

Examples

Choice Type

from dataclasses import dataclass
from jam.types.base.choices import Choice, decodable_choice
from jam.types.base.integers import U32
from jam.types.base.string import String

# Define variants
@dataclass
class Number:
    value: U32

@dataclass
class Text:
    value: String

# Create choice type
@decodable_choice(Number, Text)
class Value:
    variant: Choice[Number, Text]

# Create and encode number variant
num = Value(Number(U32(42)))
encoded = num.encode()  # -> [00 2A 00 00 00]

# Create and encode text variant
text = Value(Text(String("hello")))
encoded = text.encode()  # -> [01 05 68 65 6C 6C 6F]

Option Type

from jam.types.base.choices import Option, decodable_option
from jam.types.base.integers import U32

# Define optional type
@decodable_option(U32)
class OptionalNumber:
    value: Option[U32]

# None case
none = OptionalNumber(None)
encoded = none.encode()  # -> [00]

# Some case
some = OptionalNumber(U32(42))
encoded = some.encode()  # -> [01 2A 00 00 00]

# Decode
decoded = OptionalNumber.decode(encoded)
assert decoded.value == U32(42)

API Reference

Classes

class jam.types.base.choices.Choice(initial: Dict[str, Codable[T]] | Codable[T])[source]

Bases: Codable[T], JsonSerde, Generic[T]

A choice is a value that can be one of several possible types.

A Choice represents a tagged union type that can hold a value of one of several possible Codable types. The actual type is determined by a tag byte during encoding/decoding.

To use a choice, you need to define all possible types:
>>> @decodable_choice([U8, U16])
>>> class MyChoice(Choice): ...
>>> my_choice: MyChoice = MyChoice(U8(1))
>>> assert my_choice.type == U8
>>> assert my_choice.value == U8(1)
To use a optional choice, we’d pair it with Nullable:
>>> @decodable_choice([U8, Nullable])
>>> class OptionalU8(Choice): ...
>>> my_choice: OptionalU8 = OptionalU8(U8(1))
>>> assert my_choice.type == U8
>>> assert my_choice.value == U8(1)
>>> my_choice: OptionalU8 = OptionalU8(Null)
>>> assert my_choice.type == Nullable
>>> assert my_choice.value is None
To use this as an enum:
>>> @decodable_choice([String, String, String])
>>> class OutputType(Choice): ...
__init__(initial: Dict[str, Codable[T]] | Codable[T])[source]

Initialize Choice.

Parameters:

initial – Mapping of initial choice name and its value. Should have only one key.

Raises:

ValueError – If types list is empty

__set_internal__(value: Dict[str, Codable[T]] | Codable[T]) None[source]

Set the choice value.

Parameters:

value – Value to set. Must be instance of one of the allowed types.

Raises:

ValueError – If value type is not in allowed types list

__get__() Codable[T] | None[source]

Get the current value.

Returns:

Current value or None if not set

__eq__(other: object) bool[source]

Compare for equality.

__bool__() bool[source]

Check if the choice has a value.

__repr__() str[source]

Get string representation.

classmethod from_json(data: Any) Choice[T][source]

Create from JSON representation.

class jam.types.base.choices.Option(initial: Codable = Null)[source]

Bases: Choice

An option is a choice that can be either None or a value.

__init__(initial: Codable = Null)[source]

Initialize Choice.

Parameters:

initial – Mapping of initial choice name and its value. Should have only one key.

Raises:

ValueError – If types list is empty

static option_to_choice(value: Codable | Nullable) Dict[str, Codable][source]
classmethod from_json(data: Any) Option[source]

Create from JSON representation.

to_json() Any[source]

Convert to JSON representation.

Decorators

jam.types.base.choices.decodable_choice(cls: Type[Choice]) Type[Choice][source]
jam.types.base.choices.decodable_option(optional_type: Type[Codable]) Type[Option][source]

Decodable choice