from __future__ import absolute_import, print_function, unicode_literals
from itertools import chain
from wolframclient.serializers.base import FormatSerializer
from wolframclient.serializers.utils import py_encode_decimal, safe_len
from wolframclient.serializers.wxfencoder.constants import (
WXF_CONSTANTS,
WXF_HEADER_COMPRESS,
WXF_HEADER_SEPARATOR,
WXF_VERSION,
)
from wolframclient.serializers.wxfencoder.utils import (
float_to_bytes,
integer_size,
integer_to_bytes,
numeric_array_to_wxf,
packed_array_to_wxf,
varint_bytes,
)
from wolframclient.utils import six
from wolframclient.utils.api import zlib
from wolframclient.utils.encoding import force_bytes, force_text
[docs]def get_length(iterable, length=None):
if length is not None:
return iterable, length
length = safe_len(iterable)
if length is not None:
return iterable, length
iterable = tuple(iterable)
return iterable, len(iterable)
[docs]class WXFSerializer(FormatSerializer):
""" Serialize python objects to WXF. """
def __init__(self, normalizer=None, compress=False, **opts):
super(WXFSerializer, self).__init__(normalizer=normalizer, **opts)
self.compress = compress
[docs] def generate_bytes(self, data):
yield WXF_VERSION
if self.compress:
yield WXF_HEADER_COMPRESS
yield WXF_HEADER_SEPARATOR
if self.compress:
compressor = zlib.compressobj()
if six.PY2:
for payload in self.encode(data):
yield compressor.compress(six.binary_type(payload))
else:
for payload in self.encode(data):
yield compressor.compress(payload)
yield compressor.flush()
else:
for payload in self.encode(data):
yield payload
[docs] def serialize_symbol(self, name):
yield WXF_CONSTANTS.Symbol
yield varint_bytes(len(name))
yield force_bytes(name)
[docs] def serialize_function(self, head, args, **opts):
iterable, length = get_length(args, **opts)
return chain(
(WXF_CONSTANTS.Function, varint_bytes(length)), head, chain.from_iterable(iterable)
)
# numeric
[docs] def serialize_int(self, number):
try:
wxf_type, int_size = integer_size(number)
yield wxf_type
yield integer_to_bytes(number, int_size)
except ValueError:
# WXFExprInteger is raising a ValueError if the integer is not in the appropriate bounds.
# that check needs to be done in case, it's better to do it only once.
number = b"%i" % number
yield WXF_CONSTANTS.BigInteger
yield varint_bytes(len(number))
yield number
[docs] def serialize_float(self, number):
yield WXF_CONSTANTS.Real64
yield float_to_bytes(number)
[docs] def serialize_decimal(self, number):
number = py_encode_decimal(number)
yield WXF_CONSTANTS.BigReal
yield varint_bytes(len(number))
yield number
# text / bytes
[docs] def serialize_string(self, string):
string = force_bytes(string)
yield WXF_CONSTANTS.String
yield varint_bytes(len(string))
yield string
[docs] def serialize_bytes(self, bytes, as_byte_array=not six.PY2):
if as_byte_array:
yield WXF_CONSTANTS.BinaryString
yield varint_bytes(len(bytes))
yield bytes
else:
for token in self.serialize_string(force_text(bytes, encoding="iso8859-1")):
yield token
[docs] def serialize_mapping(self, keyvalue, **opts):
# the normalizer is always sending an generator key, value
iterable, length = get_length(keyvalue, **opts)
return chain(
(WXF_CONSTANTS.Association, varint_bytes(length)),
chain.from_iterable(
chain((WXF_CONSTANTS.Rule,), key, value) for key, value in iterable
),
)
[docs] def serialize_numeric_array(self, data, dimensions, wl_type):
return numeric_array_to_wxf(data, dimensions, wl_type)
[docs] def serialize_packed_array(self, data, dimensions, wl_type):
return packed_array_to_wxf(data, dimensions, wl_type)