You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-lib/pym/calculate/contrib/spyne/protocol/msgpack.py

362 lines
12 KiB

#
# spyne - Copyright (C) Spyne contributors.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
#
"""The ``spyne.protocol.msgpack`` module contains implementations for protocols
that use MessagePack as serializer.
Initially released in 2.8.0-rc.
"""
import logging
logger = logging.getLogger(__name__)
import msgpack
from spyne import ValidationError
from spyne.util import six
from spyne.model.fault import Fault
from spyne.model.primitive import Double
from spyne.model.primitive import Boolean
from spyne.model.primitive import Integer
from spyne.protocol.dictdoc import HierDictDocument
class MessagePackDecodeError(Fault):
CODE = "Client.MessagePackDecodeError"
def __init__(self, data=None):
super(MessagePackDecodeError, self) \
.__init__(self.CODE, data)
NON_NUMBER_TYPES = tuple({list, dict, six.text_type, six.binary_type})
class MessagePackDocument(HierDictDocument):
"""An integration class for the msgpack protocol."""
mime_type = 'application/x-msgpack'
text_based = False
type = set(HierDictDocument.type)
type.add('msgpack')
default_string_encoding = 'UTF-8'
from_serstr = HierDictDocument.from_bytes
to_serstr = HierDictDocument.to_bytes
# flags to be used in tests
_decimal_as_string = True
_huge_numbers_as_string = True
def __init__(self, app=None, validator=None, mime_type=None,
ignore_uncap=False,
# DictDocument specific
ignore_wrappers=True,
complex_as=dict,
ordered=False,
polymorphic=False,
key_encoding='utf8',
# MessagePackDocument specific
mw_packer=msgpack.Unpacker,
mw_unpacker=msgpack.Unpacker,
use_list=False,
raw=False,
use_bin_type=True,
**kwargs):
super(MessagePackDocument, self).__init__(app, validator, mime_type,
ignore_uncap, ignore_wrappers, complex_as, ordered, polymorphic,
key_encoding)
self.mw_packer = mw_packer
self.mw_unpacker = mw_unpacker
# unpacker
if not raw:
self.from_serstr = self.from_unicode
if use_bin_type:
self.from_serstr = self.from_unicode
self.kwargs = kwargs
if raw != False:
kwargs['raw'] = raw
if use_list != True:
kwargs['use_list'] = use_list
if use_bin_type != True:
kwargs['use_bin_type'] = use_bin_type
self._from_bytes_handlers[Double] = self._ret_number
self._from_bytes_handlers[Boolean] = self._ret_bool
self._from_bytes_handlers[Integer] = self.integer_from_bytes
self._from_unicode_handlers[Double] = self._ret_number
self._from_unicode_handlers[Boolean] = self._ret_bool
self._from_unicode_handlers[Integer] = self.integer_from_bytes
self._to_bytes_handlers[Double] = self._ret_number
self._to_bytes_handlers[Boolean] = self._ret_bool
self._to_bytes_handlers[Integer] = self.integer_to_bytes
self._to_unicode_handlers[Double] = self._ret_number
self._to_unicode_handlers[Boolean] = self._ret_bool
self._to_unicode_handlers[Integer] = self.integer_to_bytes
def _ret(self, _, value):
return value
def _ret_number(self, _, value):
if isinstance(value, NON_NUMBER_TYPES):
raise ValidationError(value)
if value in (True, False):
return int(value)
return value
def _ret_bool(self, _, value):
if value is None or value in (True, False):
return value
raise ValidationError(value)
def get_class_name(self, cls):
class_name = cls.get_type_name()
if not six.PY2:
if not isinstance(class_name, bytes):
class_name = class_name.encode(self.default_string_encoding)
return class_name
def create_in_document(self, ctx, in_string_encoding=None):
"""Sets ``ctx.in_document``, using ``ctx.in_string``.
:param ctx: The MethodContext object
:param in_string_encoding: MessagePack is a binary protocol. So this
argument is ignored.
"""
# handle mmap objects from in ctx.in_string as returned by
# TwistedWebResource.handle_rpc.
if isinstance(ctx.in_string, (list, tuple)) \
and len(ctx.in_string) == 1 \
and isinstance(ctx.in_string[0], memoryview):
unpacker = self.mw_unpacker(**self.kwargs)
unpacker.feed(ctx.in_string[0])
ctx.in_document = next(x for x in unpacker)
else:
try:
ctx.in_document = msgpack.unpackb(b''.join(ctx.in_string))
except ValueError as e:
raise MessagePackDecodeError(' '.join(e.args))
def gen_method_request_string(self, ctx):
"""Uses information in context object to return a method_request_string.
Returns a string in the form of "{namespaces}method name".
"""
mrs, = ctx.in_body_doc.keys()
if not six.PY2 and isinstance(mrs, bytes):
mrs = mrs.decode(self.key_encoding)
return '{%s}%s' % (self.app.interface.get_tns(), mrs)
def create_out_string(self, ctx, out_string_encoding='utf8'):
ctx.out_string = (msgpack.packb(o) for o in ctx.out_document)
def integer_from_bytes(self, cls, value):
if isinstance(value, (six.text_type, six.binary_type)):
return super(MessagePackDocument, self) \
.integer_from_bytes(cls, value)
return value
def integer_to_bytes(self, cls, value, **_):
# if it's inside the range msgpack can deal with
if -1<<63 <= value < 1<<64:
return value
else:
return super(MessagePackDocument, self).integer_to_bytes(cls, value)
class MessagePackRpc(MessagePackDocument):
"""An integration class for the msgpack-rpc protocol."""
mime_type = 'application/x-msgpack'
MSGPACK_REQUEST = 0
MSGPACK_RESPONSE = 1
MSGPACK_NOTIFY = 2
MSGPACK_ERROR = 3
def create_out_string(self, ctx, out_string_encoding='utf8'):
ctx.out_string = (msgpack.packb(o) for o in ctx.out_document)
def create_in_document(self, ctx, in_string_encoding=None):
"""Sets ``ctx.in_document``, using ``ctx.in_string``.
:param ctx: The MethodContext object
:param in_string_encoding: MessagePack is a binary protocol. So this
argument is ignored.
"""
# TODO: Use feed api
try:
ctx.in_document = msgpack.unpackb(b''.join(ctx.in_string),
**self.kwargs)
except ValueError as e:
raise MessagePackDecodeError(''.join(e.args))
try:
len(ctx.in_document)
except TypeError:
raise MessagePackDecodeError("Input must be a sequence.")
if not (3 <= len(ctx.in_document) <= 4):
raise MessagePackDecodeError("Length of input iterable must be "
"either 3 or 4")
def decompose_incoming_envelope(self, ctx, message):
# FIXME: For example: {0: 0, 1: 0, 2: "some_call", 3: [1,2,3]} will also
# work. Is this a problem?
# FIXME: Msgid is ignored. Is this a problem?
msgparams = []
if len(ctx.in_document) == 3:
msgtype, msgid, msgname_or_error = ctx.in_document
else:
msgtype, msgid, msgname_or_error, msgparams = ctx.in_document
if not six.PY2:
if isinstance(msgname_or_error, bytes):
msgname_or_error = msgname_or_error.decode(
self.default_string_encoding)
if msgtype == MessagePackRpc.MSGPACK_REQUEST:
assert message == MessagePackRpc.REQUEST
elif msgtype == MessagePackRpc.MSGPACK_RESPONSE:
assert message == MessagePackRpc.RESPONSE
elif msgtype == MessagePackRpc.MSGPACK_NOTIFY:
raise NotImplementedError()
else:
raise MessagePackDecodeError("Unknown message type %r" % msgtype)
ctx.method_request_string = '{%s}%s' % (self.app.interface.get_tns(),
msgname_or_error)
# MessagePackRpc does not seem to have Header support
ctx.in_header_doc = None
if isinstance(msgname_or_error, dict) and msgname_or_error:
# we got an error
ctx.in_error = msgname_or_error
else:
ctx.in_body_doc = msgparams
# logger.debug('\theader : %r', ctx.in_header_doc)
# logger.debug('\tbody : %r', ctx.in_body_doc)
def deserialize(self, ctx, message):
assert message in (self.REQUEST, self.RESPONSE)
self.event_manager.fire_event('before_deserialize', ctx)
if ctx.descriptor is None:
raise Fault("Client", "Method %r not found." %
ctx.method_request_string)
# instantiate the result message
if message is self.REQUEST:
body_class = ctx.descriptor.in_message
elif message is self.RESPONSE:
body_class = ctx.descriptor.out_message
else:
raise Exception("what?")
if ctx.in_error:
ctx.in_error = Fault(**ctx.in_error)
elif body_class:
ctx.in_object = self._doc_to_object(ctx,
body_class, ctx.in_body_doc, self.validator)
else:
ctx.in_object = []
self.event_manager.fire_event('after_deserialize', ctx)
def serialize(self, ctx, message):
assert message in (self.REQUEST, self.RESPONSE)
self.event_manager.fire_event('before_serialize', ctx)
if ctx.out_error is not None:
ctx.out_document = [
[MessagePackRpc.MSGPACK_ERROR, 0,
Fault.to_dict(ctx.out_error.__class__, ctx.out_error)]
]
return
# get the result message
if message is self.REQUEST:
out_type = ctx.descriptor.in_message
msgtype = MessagePackRpc.MSGPACK_REQUEST
method_name_or_error = ctx.descriptor.operation_name
elif message is self.RESPONSE:
out_type = ctx.descriptor.out_message
msgtype = MessagePackRpc.MSGPACK_RESPONSE
method_name_or_error = None
else:
raise Exception("what?")
if out_type is None:
return
out_type_info = out_type._type_info
# instantiate the result message
out_instance = out_type()
# assign raw result to its wrapper, result_message
for i, (k, v) in enumerate(out_type_info.items()):
attrs = self.get_cls_attrs(v)
out_instance._safe_set(k, ctx.out_object[i], v, attrs)
# transform the results into a dict:
if out_type.Attributes.max_occurs > 1:
params = (self._to_dict_value(out_type, inst, set())
for inst in out_instance)
else:
params = self._to_dict_value(out_type, out_instance, set())
ctx.out_document = [[msgtype, 0, method_name_or_error, params]]
self.event_manager.fire_event('after_serialize', ctx)