import pydantic

class BaseModel(pydantic.BaseModel):

    def __init__(self, **data):
        for key, value in data.items():
            if isinstance(value, dict):
                if 'type' in value:
                    data[key] = TypedModel.parse_obj(value)
        super().__init__(**data)

# Adapted from https://github.com/pydantic/pydantic/discussions/3091
class TypedModel(BaseModel):

    _subtypes_ = []

    def __init_subclass__(cls, type=None):
        cls._subtypes_.append([type, cls])

    @classmethod
    def get_cls(_cls, type):
        for t, cls in _cls._subtypes_:
            if t == type:
                return cls
        raise ValueError(f'Unknown type {type}')
    
    @classmethod
    def get_type(_cls, cls_name):
        for t, cls in _cls._subtypes_:
            if cls.__name__ == cls_name:
                return t
        raise ValueError(f'Unknown class {cls_name}')
    
    @classmethod
    def parse_obj(cls, obj):
        data_type = obj.get('type')
        if data_type is None:
            raise ValueError(f'type is required for {cls.__name__}')
    
        sub = cls.get_cls(data_type)
        if sub is None:
            raise ValueError(f'Unknown type {data_type}')
        return sub(**obj)

    def _iter(self, **kwargs):
        yield 'type', self.get_type(self.__class__.__name__)
        yield from super()._iter(**kwargs)

    @property
    def type(self):
        return self.get_type(self.__class__.__name__)

