mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-27 08:29:59 +03:00
Support MessagePack
This commit is contained in:
parent
a061e3d9e2
commit
1fa750a0d7
|
@ -1,3 +1,4 @@
|
|||
markdown>=2.1.0
|
||||
PyYAML>=3.10
|
||||
django-filter>=0.5.4
|
||||
msgpack-python==0.3.0dev1
|
||||
|
|
|
@ -391,6 +391,13 @@ except ImportError:
|
|||
yaml = None
|
||||
|
||||
|
||||
# MessagePack is optional
|
||||
try:
|
||||
import msgpack
|
||||
except ImportError:
|
||||
msgpack = None
|
||||
|
||||
|
||||
# xml.etree.parse only throws ParseError for python >= 2.7
|
||||
try:
|
||||
from xml.etree import ParseError as ETParseError
|
||||
|
|
|
@ -8,7 +8,7 @@ on the request, such as form content or json encoded data.
|
|||
from django.http import QueryDict
|
||||
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
|
||||
from django.http.multipartparser import MultiPartParserError
|
||||
from rest_framework.compat import yaml, ETParseError
|
||||
from rest_framework.compat import yaml, msgpack, ETParseError
|
||||
from rest_framework.exceptions import ParseError
|
||||
from xml.etree import ElementTree as ET
|
||||
from xml.parsers.expat import ExpatError
|
||||
|
@ -80,6 +80,42 @@ class YAMLParser(BaseParser):
|
|||
raise ParseError('YAML parse error - %s' % unicode(exc))
|
||||
|
||||
|
||||
class MessagePackParser(BaseParser):
|
||||
"""
|
||||
Parses MessagePack-serialized data.
|
||||
"""
|
||||
|
||||
media_type = 'application/msgpack'
|
||||
|
||||
def parse(self, stream, media_type=None, parser_context=None):
|
||||
"""
|
||||
Returns a 2-tuple of `(data, files)`.
|
||||
|
||||
`data` will be an object which is the parsed content of the response.
|
||||
`files` will always be `None`.
|
||||
"""
|
||||
try:
|
||||
return msgpack.Unpacker(
|
||||
stream,
|
||||
use_list=True,
|
||||
object_hook=self._decode_object).unpack()
|
||||
except Exception, exc:
|
||||
raise ParseError('MessagePack parse error - %s' % unicode(exc))
|
||||
|
||||
def _decode_object(self, o):
|
||||
# TODO(juanriaza): decode objects
|
||||
if b'__datetime__' in o:
|
||||
return o['as_str']
|
||||
elif b'__date__' in o:
|
||||
return o['as_str']
|
||||
elif b'__time__' in o:
|
||||
return o['as_str']
|
||||
elif b'__decimal__' in o:
|
||||
return o['as_str']
|
||||
else:
|
||||
return o
|
||||
|
||||
|
||||
class FormParser(BaseParser):
|
||||
"""
|
||||
Parser for form data.
|
||||
|
|
|
@ -12,7 +12,7 @@ import json
|
|||
from django import forms
|
||||
from django.http.multipartparser import parse_header
|
||||
from django.template import RequestContext, loader, Template
|
||||
from rest_framework.compat import yaml
|
||||
from rest_framework.compat import yaml, msgpack
|
||||
from rest_framework.exceptions import ConfigurationError
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.request import clone_request
|
||||
|
@ -139,6 +139,24 @@ class YAMLRenderer(BaseRenderer):
|
|||
return yaml.dump(data, stream=None, Dumper=self.encoder)
|
||||
|
||||
|
||||
class MessagePackRenderer(BaseRenderer):
|
||||
"""
|
||||
Renderer which serializes to MessagePack.
|
||||
"""
|
||||
|
||||
media_type = 'application/msgpack'
|
||||
format = 'msgpack'
|
||||
encoder = encoders.msgpack_encoder
|
||||
|
||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||
"""
|
||||
Renders *obj* into serialized MessagePack.
|
||||
"""
|
||||
if data is None:
|
||||
return ''
|
||||
return msgpack.packb(data, default=self.encoder)
|
||||
|
||||
|
||||
class TemplateHTMLRenderer(BaseRenderer):
|
||||
"""
|
||||
An HTML renderer for use with templates.
|
||||
|
|
|
@ -6,12 +6,12 @@ from django.test import TestCase
|
|||
from django.test.client import RequestFactory
|
||||
|
||||
from rest_framework import status, permissions
|
||||
from rest_framework.compat import yaml, patterns, url, include
|
||||
from rest_framework.compat import yaml, msgpack, patterns, url, include
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
||||
XMLRenderer, JSONPRenderer, BrowsableAPIRenderer
|
||||
from rest_framework.parsers import YAMLParser, XMLParser
|
||||
XMLRenderer, JSONPRenderer, MessagePackRenderer, BrowsableAPIRenderer
|
||||
from rest_framework.parsers import YAMLParser, XMLParser, MessagePackParser
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
from StringIO import StringIO
|
||||
|
@ -323,6 +323,38 @@ if yaml:
|
|||
self.assertEquals(obj, data)
|
||||
|
||||
|
||||
if msgpack:
|
||||
_msgpack_repr = '\x81\xa3foo\x92\xa3bar\xa3baz'
|
||||
|
||||
class MessagePackRendererTests(TestCase):
|
||||
"""
|
||||
Tests specific to the MessagePack Renderer
|
||||
"""
|
||||
|
||||
def test_render(self):
|
||||
"""
|
||||
Test basic MessagePack rendering.
|
||||
"""
|
||||
obj = {'foo': ['bar', 'baz']}
|
||||
renderer = MessagePackRenderer()
|
||||
content = renderer.render(obj, 'application/msgpack')
|
||||
self.assertEquals(content, _msgpack_repr)
|
||||
|
||||
def test_render_and_parse(self):
|
||||
"""
|
||||
Test rendering and then parsing returns the original object.
|
||||
IE obj -> render -> parse -> obj.
|
||||
"""
|
||||
obj = {'foo': ['bar', 'baz']}
|
||||
|
||||
renderer = MessagePackRenderer()
|
||||
parser = MessagePackParser()
|
||||
|
||||
content = renderer.render(obj, 'application/msgpack')
|
||||
data = parser.parse(StringIO(content))
|
||||
self.assertEquals(obj, data)
|
||||
|
||||
|
||||
class XMLRendererTestCase(TestCase):
|
||||
"""
|
||||
Tests specific to the XML Renderer
|
||||
|
|
|
@ -6,7 +6,7 @@ import decimal
|
|||
import types
|
||||
import json
|
||||
from django.utils.datastructures import SortedDict
|
||||
from rest_framework.compat import timezone
|
||||
from rest_framework.compat import timezone, msgpack
|
||||
from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
|
||||
|
||||
|
||||
|
@ -89,3 +89,32 @@ else:
|
|||
yaml.representer.SafeRepresenter.represent_dict)
|
||||
SafeDumper.add_representer(types.GeneratorType,
|
||||
yaml.representer.SafeRepresenter.represent_list)
|
||||
|
||||
|
||||
if msgpack:
|
||||
def msgpack_encoder(o):
|
||||
# For Date Time string spec, see ECMA 262
|
||||
# http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
|
||||
if isinstance(o, datetime.datetime):
|
||||
r = o.isoformat()
|
||||
if o.microsecond:
|
||||
r = r[:23] + r[26:]
|
||||
if r.endswith('+00:00'):
|
||||
r = r[:-6] + 'Z'
|
||||
return {'__datetime__': True, 'as_str': r}
|
||||
elif isinstance(o, datetime.date):
|
||||
r = o.isoformat()
|
||||
return {'__date__': True, 'as_str': r}
|
||||
elif isinstance(o, datetime.time):
|
||||
if timezone and timezone.is_aware(o):
|
||||
raise ValueError("MessagePack can't represent timezone-aware times.")
|
||||
r = o.isoformat()
|
||||
if o.microsecond:
|
||||
r = r[:12]
|
||||
return {'__time__': True, 'as_str': r}
|
||||
elif isinstance(o, decimal.Decimal):
|
||||
return {'__decimal__': True, 'as_str': str(o)}
|
||||
else:
|
||||
return o
|
||||
else:
|
||||
msgpack_encoder = None
|
||||
|
|
Loading…
Reference in New Issue
Block a user