mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-27 00:19:53 +03:00
Merge 6ecf7599a9
into 12bb25b372
This commit is contained in:
commit
d8f20356fd
|
@ -35,6 +35,8 @@ There is also a sandbox API you can use for testing purposes, [available here][s
|
||||||
|
|
||||||
* [Markdown] - Markdown support for the self describing API.
|
* [Markdown] - Markdown support for the self describing API.
|
||||||
* [PyYAML] - YAML content type support.
|
* [PyYAML] - YAML content type support.
|
||||||
|
* [msgpack-python] - MessagePack content type support.
|
||||||
|
* [python-dateutil] - Date parsing for MessagePack.
|
||||||
* [django-filter] - Filtering support.
|
* [django-filter] - Filtering support.
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
@ -284,4 +286,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
[markdown]: http://pypi.python.org/pypi/Markdown/
|
[markdown]: http://pypi.python.org/pypi/Markdown/
|
||||||
[pyyaml]: http://pypi.python.org/pypi/PyYAML
|
[pyyaml]: http://pypi.python.org/pypi/PyYAML
|
||||||
[django-filter]: https://github.com/alex/django-filter
|
[django-filter]: https://github.com/alex/django-filter
|
||||||
|
[msgpack-python]: https://github.com/msgpack/msgpack-python
|
||||||
|
[python-dateutil]: http://labix.org/python-dateutil
|
||||||
|
|
|
@ -61,6 +61,12 @@ Parses `YAML` request content.
|
||||||
|
|
||||||
**.media_type**: `application/yaml`
|
**.media_type**: `application/yaml`
|
||||||
|
|
||||||
|
## MessagePackParser
|
||||||
|
|
||||||
|
Parses `MessagePack` request content.
|
||||||
|
|
||||||
|
**.media_type**: `application/msgpack`
|
||||||
|
|
||||||
## XMLParser
|
## XMLParser
|
||||||
|
|
||||||
Parses REST framework's default style of `XML` request content.
|
Parses REST framework's default style of `XML` request content.
|
||||||
|
|
|
@ -94,6 +94,14 @@ Renders the request data into `YAML`.
|
||||||
|
|
||||||
**.format**: `'.yaml'`
|
**.format**: `'.yaml'`
|
||||||
|
|
||||||
|
## MessagePackRenderer
|
||||||
|
|
||||||
|
Renders the request data into `MessagePack`.
|
||||||
|
|
||||||
|
**.media_type**: `application/msgpack`
|
||||||
|
|
||||||
|
**.format**: `'.msgpack'`
|
||||||
|
|
||||||
## XMLRenderer
|
## XMLRenderer
|
||||||
|
|
||||||
Renders REST framework's default style of `XML` response content.
|
Renders REST framework's default style of `XML` response content.
|
||||||
|
|
|
@ -35,6 +35,8 @@ The following packages are optional:
|
||||||
* [Markdown][markdown] (2.1.0+) - Markdown support for the browseable API.
|
* [Markdown][markdown] (2.1.0+) - Markdown support for the browseable API.
|
||||||
* [PyYAML][yaml] (3.10+) - YAML content-type support.
|
* [PyYAML][yaml] (3.10+) - YAML content-type support.
|
||||||
* [django-filter][django-filter] (0.5.4+) - Filtering support.
|
* [django-filter][django-filter] (0.5.4+) - Filtering support.
|
||||||
|
* [python-dateutil] - Date parsing for MessagePack.
|
||||||
|
* [django-filter] - Filtering support.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -167,6 +169,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
[markdown]: http://pypi.python.org/pypi/Markdown/
|
[markdown]: http://pypi.python.org/pypi/Markdown/
|
||||||
[yaml]: http://pypi.python.org/pypi/PyYAML
|
[yaml]: http://pypi.python.org/pypi/PyYAML
|
||||||
[django-filter]: https://github.com/alex/django-filter
|
[django-filter]: https://github.com/alex/django-filter
|
||||||
|
[msgpack-python]: https://github.com/msgpack/msgpack-python
|
||||||
|
[python-dateutil]: http://labix.org/python-dateutil
|
||||||
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
|
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
|
||||||
[image]: img/quickstart.png
|
[image]: img/quickstart.png
|
||||||
[sandbox]: http://restframework.herokuapp.com/
|
[sandbox]: http://restframework.herokuapp.com/
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
markdown>=2.1.0
|
markdown>=2.1.0
|
||||||
PyYAML>=3.10
|
PyYAML>=3.10
|
||||||
django-filter>=0.5.4
|
django-filter>=0.5.4
|
||||||
|
msgpack-python>=0.2.4
|
||||||
|
python-dateutil==2.1
|
||||||
|
|
|
@ -391,6 +391,19 @@ except ImportError:
|
||||||
yaml = None
|
yaml = None
|
||||||
|
|
||||||
|
|
||||||
|
# MessagePack is optional
|
||||||
|
try:
|
||||||
|
import msgpack
|
||||||
|
except ImportError:
|
||||||
|
msgpack = None
|
||||||
|
|
||||||
|
# dateutil is optional
|
||||||
|
try:
|
||||||
|
from dateutil import parser as dateutil_parser
|
||||||
|
except ImportError:
|
||||||
|
dateutil_parser = None
|
||||||
|
|
||||||
|
|
||||||
# xml.etree.parse only throws ParseError for python >= 2.7
|
# xml.etree.parse only throws ParseError for python >= 2.7
|
||||||
try:
|
try:
|
||||||
from xml.etree import ParseError as ETParseError
|
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 import QueryDict
|
||||||
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
|
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
|
||||||
from django.http.multipartparser import MultiPartParserError
|
from django.http.multipartparser import MultiPartParserError
|
||||||
from rest_framework.compat import yaml, ETParseError
|
from rest_framework.compat import yaml, msgpack, dateutil_parser, ETParseError
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
from xml.parsers.expat import ExpatError
|
from xml.parsers.expat import ExpatError
|
||||||
|
@ -80,6 +80,40 @@ class YAMLParser(BaseParser):
|
||||||
raise ParseError('YAML parse error - %s' % unicode(exc))
|
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.unpackb(stream,
|
||||||
|
use_list=True,
|
||||||
|
object_hook=self._decode_object)
|
||||||
|
except Exception, exc:
|
||||||
|
raise ParseError('MessagePack parse error - %s' % unicode(exc))
|
||||||
|
|
||||||
|
def _decode_object(self, obj):
|
||||||
|
if dateutil_parser:
|
||||||
|
if '__datetime__' in obj:
|
||||||
|
return dateutil_parser.parse(obj['as_str'])
|
||||||
|
elif b'__date__' in obj:
|
||||||
|
return dateutil_parser.parse(obj['as_str']).date()
|
||||||
|
elif b'__time__' in obj:
|
||||||
|
return dateutil_parser.parse(obj['as_str']).time()
|
||||||
|
if b'__decimal__' in obj:
|
||||||
|
return decimal.Decimal(obj['as_str'])
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class FormParser(BaseParser):
|
class FormParser(BaseParser):
|
||||||
"""
|
"""
|
||||||
Parser for form data.
|
Parser for form data.
|
||||||
|
|
|
@ -12,7 +12,7 @@ import json
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.http.multipartparser import parse_header
|
from django.http.multipartparser import parse_header
|
||||||
from django.template import RequestContext, loader, Template
|
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.exceptions import ConfigurationError
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
from rest_framework.request import clone_request
|
from rest_framework.request import clone_request
|
||||||
|
@ -139,6 +139,23 @@ class YAMLRenderer(BaseRenderer):
|
||||||
return yaml.dump(data, stream=None, Dumper=self.encoder)
|
return yaml.dump(data, stream=None, Dumper=self.encoder)
|
||||||
|
|
||||||
|
|
||||||
|
class MessagePackRenderer(BaseRenderer):
|
||||||
|
"""
|
||||||
|
Renderer which serializes to MessagePack.
|
||||||
|
"""
|
||||||
|
|
||||||
|
media_type = 'application/msgpack'
|
||||||
|
format = 'msgpack'
|
||||||
|
|
||||||
|
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=encoders.msgpack_encoder)
|
||||||
|
|
||||||
|
|
||||||
class TemplateHTMLRenderer(BaseRenderer):
|
class TemplateHTMLRenderer(BaseRenderer):
|
||||||
"""
|
"""
|
||||||
An HTML renderer for use with templates.
|
An HTML renderer for use with templates.
|
||||||
|
|
|
@ -6,12 +6,12 @@ from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
from rest_framework import status, permissions
|
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.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
||||||
XMLRenderer, JSONPRenderer, BrowsableAPIRenderer
|
XMLRenderer, JSONPRenderer, MessagePackRenderer, BrowsableAPIRenderer
|
||||||
from rest_framework.parsers import YAMLParser, XMLParser
|
from rest_framework.parsers import YAMLParser, XMLParser, MessagePackParser
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
@ -323,6 +323,38 @@ if yaml:
|
||||||
self.assertEquals(obj, data)
|
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(content)
|
||||||
|
self.assertEquals(obj, data)
|
||||||
|
|
||||||
|
|
||||||
class XMLRendererTestCase(TestCase):
|
class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests specific to the XML Renderer
|
Tests specific to the XML Renderer
|
||||||
|
|
|
@ -6,7 +6,7 @@ import decimal
|
||||||
import types
|
import types
|
||||||
import json
|
import json
|
||||||
from django.utils.datastructures import SortedDict
|
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
|
from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,3 +89,19 @@ else:
|
||||||
yaml.representer.SafeRepresenter.represent_dict)
|
yaml.representer.SafeRepresenter.represent_dict)
|
||||||
SafeDumper.add_representer(types.GeneratorType,
|
SafeDumper.add_representer(types.GeneratorType,
|
||||||
yaml.representer.SafeRepresenter.represent_list)
|
yaml.representer.SafeRepresenter.represent_list)
|
||||||
|
|
||||||
|
|
||||||
|
if msgpack:
|
||||||
|
def msgpack_encoder(obj):
|
||||||
|
if isinstance(obj, datetime.datetime):
|
||||||
|
return {'__datetime__': True, 'as_str': obj.isoformat()}
|
||||||
|
elif isinstance(obj, datetime.date):
|
||||||
|
return {'__date__': True, 'as_str': obj.isoformat()}
|
||||||
|
elif isinstance(obj, datetime.time):
|
||||||
|
return {'__time__': True, 'as_str': obj.isoformat()}
|
||||||
|
elif isinstance(obj, decimal.Decimal):
|
||||||
|
return {'__decimal__': True, 'as_str': str(obj)}
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
else:
|
||||||
|
msgpack_encoder = None
|
||||||
|
|
Loading…
Reference in New Issue
Block a user