import re from abc import ABC, abstractmethod import json import dateutil.parser def _type_to_snake_case(type_): return re.sub(r'(?<!^)(?=[A-Z])', '_', type_.__name__).lower() class BaseSerializer(ABC): type_ = None def __init__(self, obj): if isinstance(obj, self.type_): self.obj = obj elif isinstance(obj, (str, dict)): self.obj = self.to_object(obj) else: raise TypeError('The "{}" object must be {}, str or dict, not {!r}.' .format(_type_to_snake_case(self.type_), self.type_.__name__, obj.__class__.__name__)) def get_object(self): return self.obj def get_json(self): return self.to_json(self.obj) @staticmethod def _remove_empty_values(data): return {k: v for k, v in data.items() if v is not None} @classmethod def to_json(cls, obj): cls.ensure_type(obj) return cls._to_json(obj) @staticmethod @abstractmethod def _to_json(obj): pass @classmethod def to_object(cls, json_): json_ = cls.ensure_dict(json_) return cls._to_object(json_) @staticmethod @abstractmethod def _to_object(json_): pass @staticmethod def ensure_dict(json_): if not isinstance(json_, (str, dict)): raise TypeError('The json object must be str or dict, not {!r}'.format(json_.__class__.__name__)) if isinstance(json_, str): return json.loads(json_) else: return json_ @classmethod def ensure_type(cls, obj): if not isinstance(obj, cls.type_): raise TypeError('The object must be {}, not {!r}.'.format(cls.type_, obj.__class__.__name__)) def __init_subclass__(cls, **kwargs): """Checks that "type_" is defined and that name of the argument in subclasses __init__ method is the name of the "type_" in lowercase. It ensures that error in __init__ function of BaseSerializer has a correct message. """ if cls.type_ is None: raise AssertionError('Subclass of BaseSerializer has to define class "type_" that is being serialized.') if cls.__init__.__code__.co_varnames != ('self', _type_to_snake_case(cls.type_)): raise AssertionError('Argument of the __init__ method has to have a name "{}".' .format(_type_to_snake_case(cls.type_))) @staticmethod def _get_datetime_from_string(s): return dateutil.parser.parse(s)