import json import pickle import datetime import struct import io def json_default_converter(o): """ Default formatter for json It's used when the json serializer does not know how to serialise a type :param o: :return: """ if isinstance(o, (datetime.date, datetime.datetime)): return o.isoformat() class Serializer: HEADER_FORMAT = "cH" def __init__(self): self._cache = [] # add builtin serializers self._cache.append(EventSerializer()) self._cache.append(PickleSerializer()) def register(self, serializer): """ Register the list of all know serializers :param serializer: :return: """ self._cache.append(serializer) def serialize(self, obj): """ Get the stream representation of an object :param obj: :return: """ serializers = [s for s in self._cache if s.match(obj)] if not serializers: raise TypeError(f"Don't know how to serialize {type(obj)}") serializer = serializers[0] stream = io.BytesIO() header = struct.pack(Serializer.HEADER_FORMAT, bytes(serializer.name, "utf-8"), serializer.version) stream.write(header) return serializer.dump(stream, obj) def deserialize(self, stream): """ Loads an object from its stream representation :param stream: :return: """ header = struct.unpack(Serializer.HEADER_FORMAT, stream.read(4)) serializers = [s for s in self._cache if s.name == header[0].decode("utf-8") and s.version == header[1]] if not serializers: raise TypeError(f"Don't know how serializer name={header[0]}, version={header[1]}") serializer = serializers[0] return serializer.load(stream) class BaseSerializer: def __init__(self, name, version): """ Create a serializer, given a name and a version :param name: :param version: :return: """ self.name = name self.version = version @staticmethod def match(obj): """ Returns true if self can serialize obj :param obj: :return: """ pass def dump(self, stream, obj): """ Returns the byte representation of how the object should be serialized :param stream: to write to :param obj: :return: stream of bytes """ pass def load(self, stream): """ From a stream of bytes, create the object :param stream: :return: object """ pass @staticmethod def get_class(kls): parts = kls.split('.') module = ".".join(parts[:-1]) m = __import__(module) for comp in parts[1:]: m = getattr(m, comp) return m @staticmethod def get_full_qualified_name(obj): module = obj.__class__.__module__ if module is None or module == str.__class__.__module__: return obj.__class__.__name__ # Avoid reporting __builtin__ else: return module + '.' + obj.__class__.__name__ class EventSerializer(BaseSerializer): @staticmethod def match(obj): return BaseSerializer.get_full_qualified_name(obj) == "sdp.sheerkaDataProvider.Event" def __init__(self): BaseSerializer.__init__(self, "E", 1) def dump(self, stream, obj): stream.write(obj.to_json().encode("utf-8")) stream.seek(0) return stream def load(self, stream): json_stream = stream.read().decode("utf-8") json_message = json.loads(json_stream) event = BaseSerializer.get_class("sdp.sheerkaDataProvider.Event")() event.from_json(json_message) return event class PickleSerializer(BaseSerializer): @staticmethod def match(obj): return BaseSerializer.get_full_qualified_name(obj) == "sdp.sheerkaDataProvider.State" def __init__(self): BaseSerializer.__init__(self, "P", 1) def dump(self, stream, obj): stream.write(pickle.dumps(obj)) stream.seek(0) return stream def load(self, stream): return pickle.loads(stream.read())