Skip to content

Inherited deserializer

from collections.abc import Iterator
from dataclasses import dataclass
from typing import TypeVar

from apischema import deserialize, deserializer
from apischema.conversions import Conversion

Foo_ = TypeVar("Foo_", bound="Foo")

# Use a dataclass in order to be easily testable with ==
@dataclass
class Foo:
    value: int

    @classmethod
    def deserialize(cls: type[Foo_], value: int) -> Foo_:
        return cls(value)

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        # Register subclasses' conversion in __init_subclass__
        deserializer(Conversion(cls.deserialize, target=cls))


# Register main conversion after the class definition
deserializer(Conversion(Foo.deserialize, target=Foo))


class Bar(Foo):
    pass


assert deserialize(Foo, 0) == Foo(0)
assert deserialize(Bar, 0) == Bar(0)


# For external types (defines in imported library)


@dataclass
class ForeignType:
    value: int


class ForeignSubtype(ForeignType):
    pass


T = TypeVar("T")
# Recursive implementation of type.__subclasses__
def rec_subclasses(cls: type[T]) -> Iterator[type[T]]:
    for sub_cls in cls.__subclasses__():
        yield sub_cls
        yield from rec_subclasses(sub_cls)


# Register deserializers for all subclasses
for cls in (ForeignType, *rec_subclasses(ForeignType)):
    # cls=cls is an lambda idiom to capture variable by value inside loop
    deserializer(Conversion(lambda value, cls=cls: cls(value), source=int, target=cls))

assert deserialize(ForeignType, 0) == ForeignType(0)
assert deserialize(ForeignSubtype, 0) == ForeignSubtype(0)