import uuid
from cis_interface import backwards, tools, serialize
CIS_MSG_HEAD = backwards.unicode2bytes('CIS_MSG_HEAD')
HEAD_VAL_SEP = backwards.unicode2bytes(':CIS:')
HEAD_KEY_SEP = backwards.unicode2bytes(',CIS,')
[docs]class DefaultSerialize(object):
r"""Default class for serializing/deserializing a python object into/from
a bytes message.
Args:
format_str (str, optional): If provided, this string will be used to
format messages from a list of arguments and parse messages to
get a list of arguments in C printf/scanf style. Defaults to
None and messages are assumed to already be bytes.
as_array (bool, optional): If True, each of the arguments being
serialized/deserialized will be arrays that are converted to/from
bytes in column major ('F') order. Otherwise, each argument should
be a scalar. Defaults to False.
field_names (list, optional): The names of fields in the format string.
If not provided, names are set based on the order of the fields in
the format string.
field_units (list, optional): The units of fields in the format string.
If not provided, all fields are assumed to be dimensionless.
func_serialize (func, optional): Callable object that takes python
objects as input and returns a bytes string representation. Defaults
to None.
func_deserialize (func, optional): Callable object that takes a bytes
string as input and returns a deserialized python object. Defaults
to None.
Attributes:
format_str (str): Format string used to format/parse bytes messages
from/to a list of arguments in C printf/scanf style.
as_array (bool): True or False depending if serialized/deserialized
python objects will be arrays or scalars.
field_names (list): The names of fields in the format string.
field_units (list): The units of fields in the format string.
func_serialize (func): Callable object that takes python object as input
and returns a bytes string representation.
func_deserialize (func): Callable object that takes a bytes string as
input and returns a deserialized python object.
"""
def __init__(self, format_str=None, as_array=False,
field_names=None, field_units=None,
func_serialize=None, func_deserialize=None, **kwargs):
self.format_str = format_str
self.as_array = as_array
if field_names is not None:
field_names = [backwards.unicode2bytes(n) for n in field_names]
self.field_names = field_names
if field_units is not None:
field_units = [backwards.unicode2bytes(n) for n in field_units]
self.field_units = field_units
if isinstance(func_serialize, DefaultSerialize):
self._func_serialize = func_serialize.func_serialize
else:
self._func_serialize = func_serialize
if isinstance(func_deserialize, DefaultSerialize):
self._func_deserialize = func_deserialize.func_deserialize
else:
self._func_deserialize = func_deserialize
# @property
# def serializer_class(self):
# r"""str: String representation of the class."""
# cls = str(self.__class__).split("'")
# print(cls)
# return cls
@property
def is_user_defined(self):
r"""bool: True if serialization or deserialization function was user
defined."""
return ((self._func_serialize is not None) or
(self._func_deserialize is not None))
@property
def serializer_type(self):
r"""int: Type of serializer."""
if self.is_user_defined:
out = -1
elif self.format_str is None:
out = 0
elif not self.as_array:
out = 1
else:
out = 2
return out
@property
def empty_msg(self):
r"""obj: Object indicating empty message."""
stype = self.serializer_type
if stype <= 0:
out = backwards.unicode2bytes('')
else:
out = tuple()
return out
@property
def serializer_info(self):
r"""dict: Information about serializer required to reconstruct it."""
if self.is_user_defined:
raise RuntimeError("Cannot get serializer info for user " +
"defined functions.")
out = dict(stype=self.serializer_type)
if self.format_str:
out['format_str'] = self.format_str
if self.as_array:
out['as_array'] = int(self.as_array)
if self.field_names:
out['field_names'] = self.field_names
if self.field_units:
out['field_units'] = self.field_units
return out
@property
def field_formats(self):
r"""list: Format codes for each field in the format string."""
if self.format_str is None:
return []
return serialize.extract_formats(self.format_str)
@property
def nfields(self):
r"""int: Number of fields in the format string."""
return len(self.field_formats)
@property
def numpy_dtype(self):
r"""np.dtype: Data type associated with the format string."""
if self.format_str is None:
return None
return serialize.cformat2nptype(self.format_str, names=self.field_names)
@property
def scanf_format_str(self):
r"""str: Simplified format string for scanf."""
if self.format_str is None:
return None
return serialize.cformat2pyscanf(self.format_str)
[docs] def func_serialize(self, args):
r"""Default method for serializing object into message.
Args:
args (obj): List of arguments to be formatted or a ready made message.
Returns:
bytes, str: Serialized message.
Raises:
Exception: If there is no format string and more than one argument
is provided.
"""
if self._func_serialize is not None:
# Return directly to check and raise TypeError
return self._func_serialize(args)
elif self.format_str is not None:
if self.as_array:
out = serialize.array_to_bytes(args, dtype=self.numpy_dtype,
order='F')
else:
out = serialize.format_message(args, self.format_str)
else:
if isinstance(args, (list, tuple)):
if len(args) != 1:
raise Exception("No format string and more than one " +
"argument provided.")
out = args[0]
else:
out = args
out = backwards.unicode2bytes(out)
return out
[docs] def func_deserialize(self, msg):
r"""Default method for deseserializing a message.
Args:
msg (str, bytes): Message to be deserialized.
Returns:
tuple(obj, dict): Deserialized message and header information.
"""
if self._func_deserialize is not None:
out = self._func_deserialize(msg)
elif self.format_str is not None:
if self.as_array:
if len(msg) == 0:
out = self.empty_msg
# out = np.empty(0, self.numpy_dtype)
else:
out = serialize.bytes_to_array(msg, self.numpy_dtype, order='F')
else:
if len(msg) == 0:
out = self.empty_msg
else:
out = serialize.process_message(msg, self.format_str)
else:
out = msg
return out
[docs] def serialize(self, args, header_kwargs=None, add_serializer_info=False):
r"""Serialize a message.
Args:
args (obj): List of arguments to be formatted or a ready made message.
header_kwargs (dict, optional): Keyword arguments that should be
added to the header. Defaults to None and no header is added.
add_serializer_info (bool, optional): If True, add enough information
about this serializer to the header that the message can be
recovered.
Returns:
bytes, str: Serialized message.
Raises:
TypeError: If returned msg is not bytes type (str on Python 2).
"""
if isinstance(args, backwards.bytes_type) and (args == tools.CIS_MSG_EOF):
return args
else:
out = self.func_serialize(args)
if not isinstance(out, backwards.bytes_type):
raise TypeError("Serialization function did not yield bytes type.")
if add_serializer_info:
if header_kwargs is None:
header_kwargs = dict()
header_kwargs.update(**self.serializer_info)
if header_kwargs is not None:
header_kwargs.setdefault('size', len(out))
header_kwargs.setdefault('id', str(uuid.uuid4()))
out = self.format_header(header_kwargs) + out
return out
[docs] def update_from_message(self, msg, **kwargs):
kwargs.update(**self.serializer_info)
sinfo = serialize.guess_serializer(msg, **kwargs)
self.update_serializer(**sinfo)
[docs] def update_serializer(self, **kwargs):
r"""Update serializer with provided information."""
key_list = ['format_str', 'as_array', 'field_names', 'field_units']
for k in key_list:
setattr(self, k, kwargs.get(k, getattr(self, k)))
if 'stype' in kwargs:
stype = kwargs['stype']
if (self.serializer_type != stype) and (stype != 0):
sinfo = self.serializer_info
sinfo['stype'] = stype
# Monkey patch class (unadvised, but child classes are simple)
print('monkey patch', sinfo)
self = serialize.get_serializer(**sinfo)
assert(self.serializer_type == stype)
[docs] def deserialize(self, msg):
r"""Deserialize a message.
Args:
msg (str, bytes): Message to be deserialized.
Returns:
tuple(obj, dict): Deserialized message and header information.
Raises:
TypeError: If msg is not bytes type (str on Python 2).
"""
if not isinstance(msg, backwards.bytes_type):
raise TypeError("Message to be deserialized is not bytes type.")
header_info = self.parse_header(msg)
# Update parameters based on header info
if 'stype' in header_info:
self.update_serializer(**header_info)
body = header_info.pop('body')
if len(body) < header_info['size']:
header_info['incomplete'] = True
return body, header_info
if body == tools.CIS_MSG_EOF:
header_info['eof'] = True
out = body
else:
out = self.func_deserialize(body)
return out, header_info