import dataclasses from dataclasses import dataclass, _MISSING_TYPE from munch import Munch EXPECTED = "___REQUIRED___" EXPECTED_TRAIN = "___REQUIRED_TRAIN___" # pylint: disable=invalid-field-call def nested_dto(x, raw=False): return dataclasses.field(default_factory=lambda: x if raw else Munch.fromDict(x)) @dataclass(frozen=True) class Base: training: bool = None def __new__(cls, **kwargs): training = kwargs.get('training', True) setteable_fields = cls.setteable_fields(**kwargs) mandatory_fields = cls.mandatory_fields(**kwargs) invalid_kwargs = [ {k: v} for k, v in kwargs.items() if k not in setteable_fields or v == EXPECTED or (v == EXPECTED_TRAIN and training is not False) ] print(mandatory_fields) assert ( len(invalid_kwargs) == 0 ), f"Invalid fields detected when initializing this DTO: {invalid_kwargs}.\nDeclare this field and set it to None or EXPECTED in order to make it setteable." missing_kwargs = [f for f in mandatory_fields if f not in kwargs] assert ( len(missing_kwargs) == 0 ), f"Required fields missing initializing this DTO: {missing_kwargs}." return object.__new__(cls) @classmethod def setteable_fields(cls, **kwargs): return [f.name for f in dataclasses.fields(cls) if f.default is None or isinstance(f.default, _MISSING_TYPE) or f.default == EXPECTED or f.default == EXPECTED_TRAIN] @classmethod def mandatory_fields(cls, **kwargs): training = kwargs.get('training', True) return [f.name for f in dataclasses.fields(cls) if isinstance(f.default, _MISSING_TYPE) and isinstance(f.default_factory, _MISSING_TYPE) or f.default == EXPECTED or (f.default == EXPECTED_TRAIN and training is not False)] @classmethod def from_dict(cls, kwargs): for k in kwargs: if isinstance(kwargs[k], (dict, list, tuple)): kwargs[k] = Munch.fromDict(kwargs[k]) return cls(**kwargs) def to_dict(self): # selfdict = dataclasses.asdict(self) # needs to pickle stuff, doesn't support some more complex classes selfdict = {} for k in dataclasses.fields(self): selfdict[k.name] = getattr(self, k.name) if isinstance(selfdict[k.name], Munch): selfdict[k.name] = selfdict[k.name].toDict() return selfdict