This commit is contained in:
lz_db
2025-11-16 12:31:03 +08:00
commit 0fab423a18
1451 changed files with 743213 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
from .array_serializer import ArraySerializer
from .bool_serializer import BoolSerializer
from .byte_array_serializer import ByteArraySerializer
from .cairo_data_serializer import CairoDataSerializer
from .felt_serializer import FeltSerializer
from .named_tuple_serializer import NamedTupleSerializer
from .payload_serializer import PayloadSerializer
from .struct_serializer import StructSerializer
from .tuple_serializer import TupleSerializer
from .uint256_serializer import Uint256Serializer

View File

@@ -0,0 +1,82 @@
# We have to use parametrised type from typing
from collections import OrderedDict as _OrderedDict
from typing import Dict, Generator, List, OrderedDict
from .._context import (
DeserializationContext,
SerializationContext,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
# The actual serialization logic is very similar among all serializers: they either serialize data based on
# position or their name. Having this logic reused adds indirection, but makes sure proper logic is used everywhere.
def deserialize_to_list(
deserializers: List[CairoDataSerializer], context: DeserializationContext
) -> List:
"""
Deserializes data from context to list. This logic is used in every sequential type (arrays and tuples).
"""
result = []
for index, serializer in enumerate(deserializers):
with context.push_entity(f"[{index}]"):
result.append(serializer.deserialize_with_context(context))
return result
def deserialize_to_dict(
deserializers: OrderedDict[str, CairoDataSerializer],
context: DeserializationContext,
) -> OrderedDict:
"""
Deserializes data from context to dictionary. This logic is used in every type with named fields (structs,
named tuples and payloads).
"""
result = _OrderedDict()
for key, serializer in deserializers.items():
with context.push_entity(key):
result[key] = serializer.deserialize_with_context(context)
return result
def serialize_from_list(
serializers: List[CairoDataSerializer], context: SerializationContext, values: List
) -> Generator[int, None, None]:
"""
Serializes data from list. This logic is used in every sequential type (arrays and tuples).
"""
context.ensure_valid_value(
len(serializers) == len(values),
f"expected {len(serializers)} elements, {len(values)} provided",
)
for index, (serializer, value) in enumerate(zip(serializers, values)):
with context.push_entity(f"[{index}]"):
yield from serializer.serialize_with_context(context, value)
def serialize_from_dict(
serializers: OrderedDict[str, CairoDataSerializer],
context: SerializationContext,
values: Dict,
) -> Generator[int, None, None]:
"""
Serializes data from dict. This logic is used in every type with named fields (structs, named tuples and payloads).
"""
excessive_keys = set(values.keys()).difference(serializers.keys())
context.ensure_valid_value(
not excessive_keys,
f"unexpected keys '{','.join(excessive_keys)}' were provided",
)
for name, serializer in serializers.items():
with context.push_entity(name):
context.ensure_valid_value(name in values, f"key '{name}' is missing")
yield from serializer.serialize_with_context(context, values[name])

View File

@@ -0,0 +1,43 @@
from dataclasses import dataclass
from typing import Generator, Iterable, List
from .._context import (
DeserializationContext,
SerializationContext,
)
from ..data_serializers._common import (
deserialize_to_list,
serialize_from_list,
)
from ..data_serializers.cairo_data_serializer import (
CairoDataSerializer,
)
@dataclass
class ArraySerializer(CairoDataSerializer[Iterable, List]):
"""
Serializer for arrays. In abi they are represented as a pointer to a type.
Can serialize any iterable and prepends its length to resulting list.
Deserializes data to a list.
Examples:
[1,2,3] => [3,1,2,3]
[] => [0]
"""
inner_serializer: CairoDataSerializer
def deserialize_with_context(self, context: DeserializationContext) -> List:
with context.push_entity("len"):
[size] = context.reader.read(1)
return deserialize_to_list([self.inner_serializer] * size, context)
def serialize_with_context(
self, context: SerializationContext, value: List
) -> Generator[int, None, None]:
yield len(value)
yield from serialize_from_list(
[self.inner_serializer] * len(value), context, value
)

View File

@@ -0,0 +1,37 @@
from dataclasses import dataclass
from typing import Generator
from .._context import (
Context,
DeserializationContext,
SerializationContext,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
@dataclass
class BoolSerializer(CairoDataSerializer[bool, int]):
"""
Serializer for boolean.
"""
def deserialize_with_context(self, context: DeserializationContext) -> bool:
[val] = context.reader.read(1)
self._ensure_bool(context, val)
return bool(val)
def serialize_with_context(
self, context: SerializationContext, value: bool
) -> Generator[int, None, None]:
context.ensure_valid_type(value, isinstance(value, bool), "bool")
self._ensure_bool(context, value)
yield int(value)
@staticmethod
def _ensure_bool(context: Context, value: int):
context.ensure_valid_value(
value in [0, 1],
f"invalid value '{value}' - must be in [0, 2) range",
)

View File

@@ -0,0 +1,66 @@
from dataclasses import dataclass
from typing import Generator
from ...cairo.felt import decode_shortstring, encode_shortstring
from .._context import (
DeserializationContext,
SerializationContext,
)
from ._common import (
deserialize_to_list,
serialize_from_list,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
from .felt_serializer import FeltSerializer
BYTES_31_SIZE = 31
@dataclass
class ByteArraySerializer(CairoDataSerializer[str, str]):
"""
Serializer for ByteArrays. Serializes to and deserializes from str values.
Examples:
"" => [0,0,0]
"hello" => [0,448378203247,5]
"""
def deserialize_with_context(self, context: DeserializationContext) -> str:
with context.push_entity("data_array_len"):
[size] = context.reader.read(1)
data = deserialize_to_list([FeltSerializer()] * size, context)
with context.push_entity("pending_word"):
[pending_word] = context.reader.read(1)
with context.push_entity("pending_word_len"):
[pending_word_len] = context.reader.read(1)
pending_word = decode_shortstring(pending_word)
context.ensure_valid_value(
len(pending_word) == pending_word_len,
f"Invalid length {pending_word_len} for pending word {pending_word}",
)
data_joined = "".join(map(decode_shortstring, data))
return data_joined + pending_word
def serialize_with_context(
self, context: SerializationContext, value: str
) -> Generator[int, None, None]:
context.ensure_valid_type(value, isinstance(value, str), "str")
data = [
value[i : i + BYTES_31_SIZE] for i in range(0, len(value), BYTES_31_SIZE)
]
pending_word = (
"" if len(data) == 0 or len(data[-1]) == BYTES_31_SIZE else data.pop(-1)
)
yield len(data)
yield from serialize_from_list([FeltSerializer()] * len(data), context, data)
yield encode_shortstring(pending_word)
yield len(pending_word)

View File

@@ -0,0 +1,71 @@
from abc import ABC, abstractmethod
from typing import Generator, Generic, List, TypeVar
from .._calldata_reader import CairoData
from .._context import (
DeserializationContext,
SerializationContext,
)
# Python type that is accepted by a serializer
# pylint: disable=invalid-name
SerializationType = TypeVar("SerializationType")
# Python type that will be returned from a serializer. Often same as SerializationType.
# pylint: disable=invalid-name
DeserializationType = TypeVar("DeserializationType")
class CairoDataSerializer(ABC, Generic[SerializationType, DeserializationType]):
"""
Base class for serializing/deserializing data to/from calldata.
"""
def deserialize(self, data: List[int]) -> DeserializationType:
"""
Transform calldata into python value.
:param data: calldata to deserialize.
:return: defined DeserializationType.
"""
with DeserializationContext.create(data) as context:
return self.deserialize_with_context(context)
def serialize(self, data: SerializationType) -> CairoData:
"""
Transform python data into calldata.
:param data: data to serialize.
:return: calldata.
"""
with SerializationContext.create() as context:
serialized_data = list(self.serialize_with_context(context, data))
return self.remove_units_from_serialized_data(serialized_data)
@abstractmethod
def deserialize_with_context(
self, context: DeserializationContext
) -> DeserializationType:
"""
Transform calldata into python value.
:param context: context of this deserialization.
:return: defined DeserializationType.
"""
@abstractmethod
def serialize_with_context(
self, context: SerializationContext, value: SerializationType
) -> Generator[int, None, None]:
"""
Transform python value into calldata.
:param context: context of this serialization.
:param value: python value to serialize.
:return: defined SerializationType.
"""
@staticmethod
def remove_units_from_serialized_data(serialized_data: List) -> List:
return [x for x in serialized_data if x is not None]

View File

@@ -0,0 +1,71 @@
from dataclasses import dataclass
from typing import Dict, Generator, OrderedDict, Tuple, Union
from .._context import (
DeserializationContext,
SerializationContext,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
from ..tuple_dataclass import TupleDataclass
@dataclass
class EnumSerializer(CairoDataSerializer[Union[Dict, TupleDataclass], TupleDataclass]):
"""
Serializer of enums.
Can serialize a dictionary and TupleDataclass.
Deserializes data to a TupleDataclass.
Example:
enum MyEnum {
a: u128,
b: u128
}
{"a": 1} => [0, 1]
{"b": 100} => [1, 100]
TupleDataclass(variant='a', value=100) => [0, 100]
"""
serializers: OrderedDict[str, CairoDataSerializer]
def deserialize_with_context(
self, context: DeserializationContext
) -> TupleDataclass:
[variant_index] = context.reader.read(1)
variant_name, serializer = self._get_variant(variant_index)
with context.push_entity("enum.variant: " + variant_name):
result_dict = {
"variant": variant_name,
"value": serializer.deserialize_with_context(context),
}
return TupleDataclass.from_dict(result_dict)
def serialize_with_context(
self, context: SerializationContext, value: Union[Dict, TupleDataclass]
) -> Generator[int, None, None]:
if isinstance(value, Dict):
items = list(value.items())
if len(items) != 1:
raise ValueError(
"Can serialize only one enum variant, got: " + str(len(items))
)
variant_name, variant_value = items[0]
else:
variant_name, variant_value = value
yield self._get_variant_index(variant_name)
yield from self.serializers[variant_name].serialize_with_context(
context, variant_value
)
def _get_variant(self, variant_index: int) -> Tuple[str, CairoDataSerializer]:
return list(self.serializers.items())[variant_index]
def _get_variant_index(self, variant_name: str) -> int:
return list(self.serializers.keys()).index(variant_name)

View File

@@ -0,0 +1,50 @@
import warnings
from dataclasses import dataclass
from typing import Generator
from ...cairo.felt import encode_shortstring, is_in_felt_range
from ...constants import FIELD_PRIME
from .._context import (
Context,
DeserializationContext,
SerializationContext,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
@dataclass
class FeltSerializer(CairoDataSerializer[int, int]):
"""
Serializer for field element. At the time of writing it is the only existing numeric type.
"""
def deserialize_with_context(self, context: DeserializationContext) -> int:
[val] = context.reader.read(1)
self._ensure_felt(context, val)
return val
def serialize_with_context(
self, context: SerializationContext, value: int
) -> Generator[int, None, None]:
if isinstance(value, str):
warnings.warn(
"Serializing shortstrings in FeltSerializer is deprecated. "
"Use starknet_py.cairo.felt.encode_shortstring instead.",
category=DeprecationWarning,
)
value = encode_shortstring(value)
yield value
return
context.ensure_valid_type(value, isinstance(value, int), "int")
self._ensure_felt(context, value)
yield value
@staticmethod
def _ensure_felt(context: Context, value: int):
context.ensure_valid_value(
is_in_felt_range(value),
f"invalid value '{value}' - must be in [0, {FIELD_PRIME}) range",
)

View File

@@ -0,0 +1,58 @@
from dataclasses import dataclass
from typing import Dict, Generator, NamedTuple, OrderedDict, Union
from .._context import (
DeserializationContext,
SerializationContext,
)
from ._common import (
deserialize_to_dict,
serialize_from_dict,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
from ..tuple_dataclass import TupleDataclass
@dataclass
class NamedTupleSerializer(
CairoDataSerializer[Union[Dict, NamedTuple, TupleDataclass], TupleDataclass]
):
"""
Serializer for tuples with named fields.
Can serialize a dictionary, a named tuple and TupleDataclass.
Deserializes data to a TupleDataclass.
Example:
{"a": 1, "b": 2} => [1,2]
"""
serializers: OrderedDict[str, CairoDataSerializer]
def deserialize_with_context(
self, context: DeserializationContext
) -> TupleDataclass:
as_dictionary = deserialize_to_dict(self.serializers, context)
return TupleDataclass.from_dict(as_dictionary)
def serialize_with_context(
self,
context: SerializationContext,
value: Union[Dict, NamedTuple, TupleDataclass],
) -> Generator[int, None, None]:
# We can't use isinstance(value, NamedTuple), because there is no NamedTuple type.
context.ensure_valid_type(
value,
isinstance(value, (dict, TupleDataclass)) or self._is_namedtuple(value),
"dict, NamedTuple or TupleDataclass",
)
# noinspection PyUnresolvedReferences, PyProtectedMember
values: Dict = value if isinstance(value, dict) else value._asdict()
yield from serialize_from_dict(self.serializers, context, values)
@staticmethod
def _is_namedtuple(value) -> bool:
return isinstance(value, tuple) and hasattr(value, "_fields")

View File

@@ -0,0 +1,43 @@
from dataclasses import dataclass
from typing import Any, Generator, Optional
from .._context import (
DeserializationContext,
SerializationContext,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
@dataclass
class OptionSerializer(CairoDataSerializer[Optional[Any], Optional[Any]]):
"""
Serializer for Option type.
Can serialize None and common CairoTypes.
Deserializes data to None or CairoType.
Example:
None => [1]
{"option1": 123, "option2": None} => [0, 123, 1]
"""
serializer: CairoDataSerializer
def deserialize_with_context(
self, context: DeserializationContext
) -> Optional[Any]:
(is_none,) = context.reader.read(1)
if is_none == 1:
return None
return self.serializer.deserialize_with_context(context)
def serialize_with_context(
self, context: SerializationContext, value: Optional[Any]
) -> Generator[int, None, None]:
if value is None:
yield 1
else:
yield 0
yield from self.serializer.serialize_with_context(context, value)

View File

@@ -0,0 +1,40 @@
from dataclasses import dataclass, field
from typing import Dict, Generator, List, Tuple
from .._context import (
DeserializationContext,
SerializationContext,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
@dataclass
class OutputSerializer(CairoDataSerializer[List, Tuple]):
"""
Serializer for function output.
Can't serialize anything.
Deserializes data to a Tuple.
Example:
[1, 1, 1] => (340282366920938463463374607431768211457)
"""
serializers: List[CairoDataSerializer] = field(init=True)
def deserialize_with_context(self, context: DeserializationContext) -> Tuple:
result = []
for index, serializer in enumerate(self.serializers):
with context.push_entity("output[" + str(index) + "]"):
result.append(serializer.deserialize_with_context(context))
return tuple(result)
def serialize_with_context(
self, context: SerializationContext, value: Dict
) -> Generator[int, None, None]:
raise ValueError(
"Output serializer can't be used to transform python data into calldata."
)

View File

@@ -0,0 +1,72 @@
from collections import OrderedDict as _OrderedDict
from dataclasses import InitVar, dataclass, field
from typing import Dict, Generator, OrderedDict
from .._context import (
DeserializationContext,
SerializationContext,
)
from ._common import (
deserialize_to_dict,
serialize_from_dict,
)
from .array_serializer import ArraySerializer
from .cairo_data_serializer import (
CairoDataSerializer,
)
from .felt_serializer import FeltSerializer
from ..tuple_dataclass import TupleDataclass
SIZE_SUFFIX = "_len"
SIZE_SUFFIX_LEN = len(SIZE_SUFFIX)
@dataclass
class PayloadSerializer(CairoDataSerializer[Dict, TupleDataclass]):
"""
Serializer for payloads like function arguments/function outputs/events.
Can serialize a dictionary.
Deserializes data to a TupleDataclass.
Example:
{"a": 1, "b": 2} => [1,2]
"""
# Value present only in constructor.
# We don't want to mutate the serializers received in constructor.
input_serializers: InitVar[OrderedDict[str, CairoDataSerializer]]
serializers: OrderedDict[str, CairoDataSerializer] = field(init=False)
def __post_init__(self, input_serializers):
"""
ABI adds ARG_len for every argument ARG that is an array. We parse length as a part of ArraySerializer, so we
need to remove those lengths from args.
"""
self.serializers = _OrderedDict(
(key, serializer)
for key, serializer in input_serializers.items()
if not self._is_len_arg(key, input_serializers)
)
def deserialize_with_context(
self, context: DeserializationContext
) -> TupleDataclass:
as_dictionary = deserialize_to_dict(self.serializers, context)
return TupleDataclass.from_dict(as_dictionary)
def serialize_with_context(
self, context: SerializationContext, value: Dict
) -> Generator[int, None, None]:
yield from serialize_from_dict(self.serializers, context, value)
@staticmethod
def _is_len_arg(arg_name: str, serializers: Dict[str, CairoDataSerializer]) -> bool:
return (
arg_name.endswith(SIZE_SUFFIX)
and isinstance(serializers[arg_name], FeltSerializer)
# There is an ArraySerializer under key that is arg_name without the size suffix
and isinstance(
serializers.get(arg_name[:-SIZE_SUFFIX_LEN]), ArraySerializer
)
)

View File

@@ -0,0 +1,36 @@
from dataclasses import dataclass
from typing import Dict, Generator, OrderedDict
from .._context import (
DeserializationContext,
SerializationContext,
)
from ._common import (
deserialize_to_dict,
serialize_from_dict,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
@dataclass
class StructSerializer(CairoDataSerializer[Dict, Dict]):
"""
Serializer of custom structures.
Can serialize a dictionary.
Deserializes data to a dictionary.
Example:
{"a": 1, "b": 2} => [1,2]
"""
serializers: OrderedDict[str, CairoDataSerializer]
def deserialize_with_context(self, context: DeserializationContext) -> Dict:
return deserialize_to_dict(self.serializers, context)
def serialize_with_context(
self, context: SerializationContext, value: Dict
) -> Generator[int, None, None]:
yield from serialize_from_dict(self.serializers, context, value)

View File

@@ -0,0 +1,36 @@
from dataclasses import dataclass
from typing import Generator, Iterable, List, Tuple
from .._context import (
DeserializationContext,
SerializationContext,
)
from ._common import (
deserialize_to_list,
serialize_from_list,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
@dataclass
class TupleSerializer(CairoDataSerializer[Iterable, Tuple]):
"""
Serializer for tuples without named fields.
Can serialize any iterable.
Deserializes data to a python tuple.
Example:
(1,2,(3,4)) => [1,2,3,4]
"""
serializers: List[CairoDataSerializer]
def deserialize_with_context(self, context: DeserializationContext) -> Tuple:
return tuple(deserialize_to_list(self.serializers, context))
def serialize_with_context(
self, context: SerializationContext, value: Iterable
) -> Generator[int, None, None]:
yield from serialize_from_list(self.serializers, context, [*value])

View File

@@ -0,0 +1,76 @@
from dataclasses import dataclass
from typing import Generator, TypedDict, Union
from ...cairo.felt import uint256_range_check
from .._context import (
Context,
DeserializationContext,
SerializationContext,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
U128_UPPER_BOUND = 2**128
class Uint256Dict(TypedDict):
low: int
high: int
@dataclass
class Uint256Serializer(CairoDataSerializer[Union[int, Uint256Dict], int]):
"""
Serializer of Uint256. In Cairo it is represented by structure {low: Uint128, high: Uint128}.
Can serialize an int.
Deserializes data to an int.
Examples:
0 => [0,0]
1 => [1,0]
2**128 => [0,1]
3 + 2**128 => [3,1]
"""
def deserialize_with_context(self, context: DeserializationContext) -> int:
[low, high] = context.reader.read(2)
# Checking if resulting value is in [0, 2**256) range is not enough. Uint256 should be made of two uint128.
with context.push_entity("low"):
self._ensure_valid_uint128(low, context)
with context.push_entity("high"):
self._ensure_valid_uint128(high, context)
return (high << 128) + low
def serialize_with_context(
self, context: SerializationContext, value: Union[int, Uint256Dict]
) -> Generator[int, None, None]:
context.ensure_valid_type(value, isinstance(value, (int, dict)), "int or dict")
if isinstance(value, int):
yield from self._serialize_from_int(value)
else:
yield from self._serialize_from_dict(context, value)
@staticmethod
def _serialize_from_int(value: int) -> Generator[int, None, None]:
uint256_range_check(value)
result = (value % 2**128, value // 2**128)
yield from result
def _serialize_from_dict(
self, context: SerializationContext, value: Uint256Dict
) -> Generator[int, None, None]:
with context.push_entity("low"):
self._ensure_valid_uint128(value["low"], context)
yield value["low"]
with context.push_entity("high"):
self._ensure_valid_uint128(value["high"], context)
yield value["high"]
@staticmethod
def _ensure_valid_uint128(value: int, context: Context):
context.ensure_valid_value(
0 <= value < U128_UPPER_BOUND, "expected value in range [0;2**128)"
)

View File

@@ -0,0 +1,100 @@
from dataclasses import dataclass
from typing import Generator, TypedDict, Union
from ...cairo.felt import uint256_range_check
from .._context import (
Context,
DeserializationContext,
SerializationContext,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
class Uint256Dict(TypedDict):
low: int
high: int
@dataclass
class UintSerializer(CairoDataSerializer[Union[int, Uint256Dict], int]):
"""
Serializer of uint. In Cairo there are few uints (u8, ..., u128 and u256).
u256 is represented by structure {low: u128, high: u128}.
Can serialize an int and dict.
Deserializes data to an int.
Examples:
if bits < 256:
0 => [0]
1 => [1]
2**128-1 => [2**128-1]
else:
0 => [0,0]
1 => [1,0]
2**128 => [0,1]
3 + 2**128 => [3,1]
"""
bits: int
def deserialize_with_context(self, context: DeserializationContext) -> int:
if self.bits < 256:
(uint,) = context.reader.read(1)
with context.push_entity("uint" + str(self.bits)):
self._ensure_valid_uint(uint, context, self.bits)
return uint
[low, high] = context.reader.read(2)
# Checking if resulting value is in [0, 2**256) range is not enough. Uint256 should be made of two uint128.
with context.push_entity("low"):
self._ensure_valid_uint(low, context, bits=128)
with context.push_entity("high"):
self._ensure_valid_uint(high, context, bits=128)
return (high << 128) + low
def serialize_with_context(
self, context: SerializationContext, value: Union[int, Uint256Dict]
) -> Generator[int, None, None]:
context.ensure_valid_type(value, isinstance(value, (int, dict)), "int or dict")
if isinstance(value, int):
yield from self._serialize_from_int(value, context, self.bits)
else:
yield from self._serialize_from_dict(context, value)
@staticmethod
def _serialize_from_int(
value: int, context: SerializationContext, bits: int
) -> Generator[int, None, None]:
if bits < 256:
UintSerializer._ensure_valid_uint(value, context, bits)
yield value
else:
uint256_range_check(value)
result = (value % 2**128, value >> 128)
yield from result
def _serialize_from_dict(
self, context: SerializationContext, value: Uint256Dict
) -> Generator[int, None, None]:
with context.push_entity("low"):
self._ensure_valid_uint(value["low"], context, bits=128)
yield value["low"]
with context.push_entity("high"):
self._ensure_valid_uint(value["high"], context, bits=128)
yield value["high"]
@staticmethod
def _ensure_valid_uint(value: int, context: Context, bits: int):
"""
Ensures that value is a valid uint on `bits` bits.
"""
context.ensure_valid_value(
0 <= value < 2**bits, "expected value in range [0;2**" + str(bits) + ")"
)

View File

@@ -0,0 +1,32 @@
from dataclasses import dataclass
from typing import Any, Generator, Optional
from .._context import (
DeserializationContext,
SerializationContext,
)
from .cairo_data_serializer import (
CairoDataSerializer,
)
@dataclass
class UnitSerializer(CairoDataSerializer[None, None]):
"""
Serializer for unit type.
Can only serialize None.
Deserializes data to None.
Example:
[] => None
"""
def deserialize_with_context(self, context: DeserializationContext) -> None:
return None
def serialize_with_context(
self, context: SerializationContext, value: Optional[Any]
) -> Generator[None, None, None]:
if value is not None:
raise ValueError("Can only serialize `None`.")
yield None