#
# 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.
This module is EXPERIMENTAL. You may not recognize the code here next time you
look at it.
"""
from __future__ import absolute_import
import logging
logger = logging.getLogger(__name__)
from spyne.util import six
import msgpack
from spyne.model.fault import Fault
from spyne.protocol.dictdoc import HierDictDocument
from spyne.model.primitive import Double
from spyne.model.primitive import Boolean
from spyne.model.primitive import Integer
class MessagePackDecodeError(Fault):
def __init__(self, data=None):
super(MessagePackDecodeError, self).__init__("Client.MessagePackDecodeError", data)
class MessagePackDocument(HierDictDocument):
[docs] """An integration class for the msgpack protocol."""
mime_type = 'application/x-msgpack'
type = set(HierDictDocument.type)
type.add('msgpack')
# 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):
super(MessagePackDocument, self).__init__(app, validator, mime_type,
ignore_uncap, ignore_wrappers, complex_as, ordered)
self._from_string_handlers[Double] = self._ret
self._from_string_handlers[Boolean] = self._ret
self._from_string_handlers[Integer] = self.integer_from_string
self._to_string_handlers[Double] = self._ret
self._to_string_handlers[Boolean] = self._ret
self._to_string_handlers[Integer] = self.integer_to_string
def _ret(self, cls, value):
return value
def create_in_document(self, ctx, in_string_encoding=None):
[docs] """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.
"""
try:
ctx.in_document = msgpack.unpackb(b''.join(ctx.in_string))
except ValueError as e:
raise MessagePackDecodeError(''.join(e.args))
if not isinstance(ctx.in_document, dict):
logger.debug("reqobj: %r", ctx.in_document)
raise MessagePackDecodeError("Request object must be a dictionary")
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_string(self, cls, value):
if isinstance(value, six.string_types):
return super(MessagePackDocument, self).integer_from_string(cls, value)
else:
return value
def integer_to_string(self, cls, value):
if -1<<63 <= value < 1<<64: # if it's inside the range msgpack can deal with
return value
else:
return super(MessagePackDocument, self).integer_to_string(cls, value)
class MessagePackRpc(MessagePackDocument):
[docs] """An integration class for the msgpack-rpc protocol."""
mime_type = 'application/x-msgpack'
MSGPACK_REQUEST = 0
MSGPACK_RESPONSE = 1
MSGPACK_NOTIFY = 2
def create_in_document(self, ctx, in_string_encoding=None):
[docs] """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))
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 = ctx.in_document
else:
msgtype, msgid, msgname, msgparams = ctx.in_document[:4]
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)
ctx.in_header_doc = None # MessagePackRpc does not seem to have Header support
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 body_class:
ctx.in_object = body_class.get_serialization_instance(
ctx.in_body_doc)
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_RESPONSE, 0,
Fault.to_dict(ctx.out_error.__class__, ctx.out_error)]
else:
# get the result message
if message is self.REQUEST:
out_type = ctx.descriptor.in_message
elif message is self.RESPONSE:
out_type = ctx.descriptor.out_message
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 in range(len(out_type_info)):
attr_name = out_type_info.keys()[i]
setattr(out_instance, attr_name, ctx.out_object[i])
# transform the results into a dict:
if out_type.Attributes.max_occurs > 1:
ctx.out_document = [[MessagePackRpc.MSGPACK_RESPONSE, 0, None,
(self._to_value(out_type, inst) for inst in out_instance)]]
else:
ctx.out_document = [[MessagePackRpc.MSGPACK_RESPONSE, 0, None,
self._to_value(out_type, out_instance)]]
self.event_manager.fire_event('after_serialize', ctx)