"""The Serdio serialization and deserialization implementation.The ``serdio.serde`` spec is an extension of MessagePack that can handle some extraPython types, while also allowing users to supply their own hooks for seamlessencoding/decoding of user-defined types.**Usage** :: xyb_bytes = serdio.serialize(2, 3.0, b=2.0) x, y = serdio.deserialize(xyb_bytes) print(f"{x}, {y}") # 2, 3.0 args, kwargs = serdio.deserialize(xyb_bytes, as_signature=True) print(f"{args[0]}, {args[1]}, b={kwargs["b"]}") # 2, 3.0, b=2.0"""importdataclassesimportenumfromtypingimportAnyfromtypingimportCallablefromtypingimportDictfromtypingimportTupleimportmsgpackARGS_MARKER="_serdio_args_"KWARGS_MARKER="_serdio_kwargs_"class_MsgpackExtType(enum.IntEnum):"""Messagepack custom type ids."""native_complex=1native_tuple=2native_set=3native_frozenset=4def_default_encoder(x,custom_encoder=None):""" An extension of the default MessagePack encoder. Supports Python types not usually handled by MessagePack (`complex`, `tuple`, `set`, `frozenset`), as well as optional user-supplied types. Args: x: input value custom_encoder: optional callable that implements an encoder for user-defined types that might be encountered inside collection types. Returns: The extended MessagePack encoder. """ifcustom_encoderisNone:encoder=_default_encoder# noqa: E731else:defencoder(x):uncollected=_default_encoder(x,custom_encoder=custom_encoder)returncustom_encoder(uncollected)ifisinstance(x,complex):returnmsgpack.ExtType(_MsgpackExtType.native_complex,msgpack.packb((x.real,x.imag),default=encoder,strict_types=True),)elifisinstance(x,tuple):returnmsgpack.ExtType(_MsgpackExtType.native_tuple,msgpack.packb(list(x),default=encoder,strict_types=True),)elifisinstance(x,set):returnmsgpack.ExtType(_MsgpackExtType.native_set,msgpack.packb(list(x),default=encoder,strict_types=True),)elifisinstance(x,frozenset):returnmsgpack.ExtType(_MsgpackExtType.native_frozenset,msgpack.packb(list(x),default=encoder,strict_types=True),)returnxdef_msgpack_ext_unpack(code,data,custom_decoder=None):"""An extension of the default MessagePack decoder. This is the inverse of ``_default_encoder``. Args: code: Data type encoded as 1 (complex), 2 (tuple), 3 (set), or 4 (frozen set) data: Byte array to unpack custom_decoder: Optional callable that implements a decoder for user-defined types that might be encountered inside collection types. Returns: The extended MessagePack decoder. """ifcustom_decoderisNone:custom_decoder=lambdax:x# noqa: E731ext_hook=_msgpack_ext_unpackelse:ext_hook=lambdac,d:_msgpack_ext_unpack(# noqa: E731c,d,custom_decoder=custom_decoder)ifcode==_MsgpackExtType.native_complex:complex_tuple=msgpack.unpackb(data,ext_hook=ext_hook,object_hook=custom_decoder)returncomplex(complex_tuple[0],complex_tuple[1])elifcode==_MsgpackExtType.native_tuple:tuple_list=msgpack.unpackb(data,ext_hook=ext_hook,object_hook=custom_decoder)returntuple(tuple_list)elifcode==_MsgpackExtType.native_set:set_list=msgpack.unpackb(data,ext_hook=ext_hook,object_hook=custom_decoder)returnset(set_list)elifcode==_MsgpackExtType.native_frozenset:frozenset_list=msgpack.unpackb(data,ext_hook=ext_hook,object_hook=custom_decoder)returnfrozenset(frozenset_list)returnmsgpack.ExtType(code,data)
[docs]defserialize(*args:Any,encoder:Callable=None,**kwargs:Any)->bytes:"""Serializes a set of ``args` and ``kwargs`` into bytes with MessagePack. Args: *args: Positional arguments to include in the serialized bytes encoder: Optional callable specifying MessagePack encoder for user-defined types. See :class:`.SerdeHookBundle` for details. kwargs: Keyword arguments to include in the serialized bytes Returns: Dictionary of ``args`` and ``kwargs``, serialized with MessagePack and optional custom ``encoder``. Raises: TypeError: if ``encoder`` is not callable. Other errors can be raised by MessagePack during packing. """x={ARGS_MARKER:args}iflen(kwargs)>0:x[KWARGS_MARKER]=kwargsencode_hook=_default_encoderifencoderisnotNone:ifnotcallable(encoder):raiseTypeError(f"`encoder` arg needs to be callable, found type {type(encoder)}")encode_hook=lambdax:_default_encoder(# noqa: E731x,custom_encoder=encoder)returnmsgpack.packb(x,default=encode_hook,strict_types=True)
[docs]defdeserialize(serdio_bytes:bytes,decoder:Callable=None,as_signature:bool=False)->Any:"""Unpacks serdio-serialized bytes to an object Args: serdio_bytes: Byte array to deserialize. decoder: Optional callable specifying Messagepack decoder for user-defined types. See :class:`.SerdeHookBundle` for details. as_signature: Optional boolean determining return format. If True, unpack the serialized byte array into an ``args`` tuple and a ``kwargs`` dictionary. This argument is most useful when the user is trying to serialize the inputs to a function of unknown arity. Returns: The deserialized object. If ``as_signature=True``, assumes the resulting object is a dictionary with an ``args`` tuple and ``kwargs`` dict for values, and returns these two instead of the full dictionary. """ext_hook=_msgpack_ext_unpackifdecoderisnotNone:ifnotcallable(decoder):raiseTypeError(f"`decoder` needs to be a callable, found type {type(decoder)}")ext_hook=lambdac,d:_msgpack_ext_unpack(# noqa: E731c,d,custom_decoder=decoder)unpacked=msgpack.unpackb(serdio_bytes,ext_hook=ext_hook,object_hook=decoder)unpacked_args=unpacked.get(ARGS_MARKER)unpacked_kwargs=unpacked.get(KWARGS_MARKER,{})ifas_signature:returnunpacked_args,unpacked_kwargsreturn_vals=unpacked_argsiflen(return_vals)==1:returnreturn_vals[0]returnreturn_vals
[docs]@dataclasses.dataclassclassSerdeHookBundle:"""An encoder-decoder hook pair for user-defined types. The ``encoder_hook`` and ``decoder_hook`` specify how to convert from a user-defined type into an equivalent collection of Python-native values and back. Thus for any object ``X`` of user-defined type ``T``, the following relationship should hold: :: hook_bundle = SerdioHookBundle(f, g) native_X = hook_bundle.encoder_hook(X) # f(X) Y = hook_bundle.decoder_hook(native_X) # g(native_X) assert X == Y Note that ``native_X`` above needs to be some collection of native Python values, e.g. a simple dataclass can be represented as a dictionary of attributes mapping to values. Args: encoder_hook: An encoder function specifying how :func:`.serdio.serde.serialize` should break down any custom types into Python native types. decoder_hook: The inverse of ``encoder_hook``, specifying how :func:`.serdio.serde.deserialize` should re-assemble the ``encoder_hook`` output into user-defined types. """encoder_hook:Callabledecoder_hook:Callable
[docs]defto_dict(self)->Dict:"""Return the encoder-decoder hook pair as a dictionary."""returndataclasses.asdict(self)
[docs]defunbundle(self)->Tuple:"""Return the encoder-decoder hook pair as a tuple."""returndataclasses.astuple(self)
[docs]defbundle_serde_hooks(hook_bundle):"""Helper to lift an encoder-decoder hook pair into a :class:`.SerdeHookBundle`. Args: hook_bundle: A tuple, list, dict or :class:`.SerdeHookBundle` containing an encoder-decoder hook pair. If a tuple or list, the encoder_hook must come first. If a dictionary, must have exactly two keys ``"encoder_hook"`` and ``"decoder_hook"``. Returns: A :class:`.SerdeHookBundle` encapsulating the encoder-decoder hook pair. Raises: ValueError: if the ``hook_bundle`` dictionary is malformed. """ifisinstance(hook_bundle,(tuple,list)):hook_bundle=SerdeHookBundle(*hook_bundle)elifisinstance(hook_bundle,dict):_check_dict_hook_bundle(hook_bundle)hook_bundle=SerdeHookBundle(**hook_bundle)returnhook_bundle
def_check_dict_hook_bundle(hook_bundle):correct_size=len(hook_bundle)==2correct_keys="encoder_hook"inhook_bundleand"decoder_hook"inhook_bundleifnotcorrect_sizeornotcorrect_keys:raiseValueError("`hook_bundle` dict must have exactly two key-value pairs: 'encoder_hook'"f"and 'decoder_hook'. Found dict with keys: {list(hook_bundle.keys())}.")