aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2012-09-03 14:28:40 +0100
committerTom Christie2012-09-03 14:28:40 +0100
commit7abef9ac3b3fb20a6cdef5d52c640e5725c93437 (patch)
tree2cd3270b7079f8b20eb3e3e90d8181c37c4199cb
parentd180e984e9779673c2d8aab3c65b4763432ba6b5 (diff)
downloaddjango-rest-framework-7abef9ac3b3fb20a6cdef5d52c640e5725c93437.tar.bz2
Parsers may return raw data, or a DataAndFiles object
-rw-r--r--djangorestframework/parsers.py31
-rw-r--r--djangorestframework/request.py16
-rw-r--r--djangorestframework/tests/parsers.py6
-rw-r--r--djangorestframework/tests/renderers.py4
-rw-r--r--djangorestframework/tests/request.py2
-rw-r--r--djangorestframework/utils/encoders.py9
-rw-r--r--docs/tutorial/1-serialization.md2
7 files changed, 40 insertions, 30 deletions
diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py
index 43ea0c4d..fc0260fc 100644
--- a/djangorestframework/parsers.py
+++ b/djangorestframework/parsers.py
@@ -36,6 +36,12 @@ __all__ = (
)
+class DataAndFiles(object):
+ def __init__(self, data, files):
+ self.data = data
+ self.files = files
+
+
class BaseParser(object):
"""
All parsers should extend :class:`BaseParser`, specifying a :attr:`media_type` attribute,
@@ -80,7 +86,7 @@ class JSONParser(BaseParser):
`files` will always be `None`.
"""
try:
- return (json.load(stream), None)
+ return json.load(stream)
except ValueError, exc:
raise ParseError('JSON parse error - %s' % unicode(exc))
@@ -100,7 +106,7 @@ class YAMLParser(BaseParser):
`files` will always be `None`.
"""
try:
- return (yaml.safe_load(stream), None)
+ return yaml.safe_load(stream)
except (ValueError, yaml.parser.ParserError), exc:
raise ParseError('YAML parse error - %s' % unicode(exc))
@@ -119,7 +125,7 @@ class PlainTextParser(BaseParser):
`data` will simply be a string representing the body of the request.
`files` will always be `None`.
"""
- return (stream.read(), None)
+ return stream.read()
class FormParser(BaseParser):
@@ -137,7 +143,7 @@ class FormParser(BaseParser):
`files` will always be :const:`None`.
"""
data = QueryDict(stream.read())
- return (data, None)
+ return data
class MultiPartParser(BaseParser):
@@ -149,16 +155,17 @@ class MultiPartParser(BaseParser):
def parse(self, stream, **opts):
"""
- Returns a 2-tuple of `(data, files)`.
+ Returns a DataAndFiles object.
- `data` will be a :class:`QueryDict` containing all the form parameters.
- `files` will be a :class:`QueryDict` containing all the form files.
+ `.data` will be a `QueryDict` containing all the form parameters.
+ `.files` will be a `QueryDict` containing all the form files.
"""
meta = opts['meta']
upload_handlers = opts['upload_handlers']
try:
parser = DjangoMultiPartParser(meta, stream, upload_handlers)
- return parser.parse()
+ data, files = parser.parse()
+ return DataAndFiles(data, files)
except MultiPartParserError, exc:
raise ParseError('Multipart form parse error - %s' % unicode(exc))
@@ -171,19 +178,13 @@ class XMLParser(BaseParser):
media_type = 'application/xml'
def parse(self, stream, **opts):
- """
- Returns a 2-tuple of `(data, files)`.
-
- `data` will simply be a string representing the body of the request.
- `files` will always be `None`.
- """
try:
tree = ET.parse(stream)
except (ExpatError, ETParseError, ValueError), exc:
raise ParseError('XML parse error - %s' % unicode(exc))
data = self._xml_convert(tree.getroot())
- return (data, None)
+ return data
def _xml_convert(self, element):
"""
diff --git a/djangorestframework/request.py b/djangorestframework/request.py
index 84ca0575..2e4e8909 100644
--- a/djangorestframework/request.py
+++ b/djangorestframework/request.py
@@ -146,7 +146,7 @@ class Request(object):
self._load_method_and_content_type()
if not _hasattr(self, '_data'):
- (self._data, self._files) = self._parse()
+ self._data, self._files = self._parse()
def _load_method_and_content_type(self):
"""
@@ -201,11 +201,11 @@ class Request(object):
self._CONTENTTYPE_PARAM in self._data):
self._content_type = self._data.pop(self._CONTENTTYPE_PARAM)[0]
self._stream = StringIO(self._data.pop(self._CONTENT_PARAM)[0])
- (self._data, self._files) = self._parse()
+ self._data, self._files = self._parse()
def _parse(self):
"""
- Parse the request content.
+ Parse the request content, returning a two-tuple of (data, files)
May raise an `UnsupportedMediaType`, or `ParseError` exception.
"""
@@ -214,8 +214,14 @@ class Request(object):
for parser in self.get_parsers():
if parser.can_handle_request(self.content_type):
- return parser.parse(self.stream, meta=self.META,
- upload_handlers=self.upload_handlers)
+ parsed = parser.parse(self.stream, meta=self.META,
+ upload_handlers=self.upload_handlers)
+ # Parser classes may return the raw data, or a
+ # DataAndFiles object. Unpack the result as required.
+ try:
+ return (parsed.data, parsed.files)
+ except AttributeError:
+ return (parsed, None)
raise UnsupportedMediaType(self._content_type)
diff --git a/djangorestframework/tests/parsers.py b/djangorestframework/tests/parsers.py
index a85409dc..c9b6afd0 100644
--- a/djangorestframework/tests/parsers.py
+++ b/djangorestframework/tests/parsers.py
@@ -153,7 +153,7 @@ class TestFormParser(TestCase):
parser = FormParser()
stream = StringIO(self.string)
- (data, files) = parser.parse(stream)
+ data = parser.parse(stream)
self.assertEqual(Form(data).is_valid(), True)
@@ -203,10 +203,10 @@ class TestXMLParser(TestCase):
def test_parse(self):
parser = XMLParser()
- (data, files) = parser.parse(self._input)
+ data = parser.parse(self._input)
self.assertEqual(data, self._data)
def test_complex_data_parse(self):
parser = XMLParser()
- (data, files) = parser.parse(self._complex_data_input)
+ data = parser.parse(self._complex_data_input)
self.assertEqual(data, self._complex_data)
diff --git a/djangorestframework/tests/renderers.py b/djangorestframework/tests/renderers.py
index adf8d8fa..dc30f487 100644
--- a/djangorestframework/tests/renderers.py
+++ b/djangorestframework/tests/renderers.py
@@ -301,7 +301,7 @@ if YAMLRenderer:
parser = YAMLParser()
content = renderer.render(obj, 'application/yaml')
- (data, files) = parser.parse(StringIO(content))
+ data = parser.parse(StringIO(content))
self.assertEquals(obj, data)
@@ -392,7 +392,7 @@ class XMLRendererTestCase(TestCase):
content = StringIO(renderer.render(self._complex_data, 'application/xml'))
parser = XMLParser()
- complex_data_out, dummy = parser.parse(content)
+ complex_data_out = parser.parse(content)
error_msg = "complex data differs!IN:\n %s \n\n OUT:\n %s" % (repr(self._complex_data), repr(complex_data_out))
self.assertEqual(self._complex_data, complex_data_out, error_msg)
diff --git a/djangorestframework/tests/request.py b/djangorestframework/tests/request.py
index 85b2f418..1d74ea32 100644
--- a/djangorestframework/tests/request.py
+++ b/djangorestframework/tests/request.py
@@ -4,7 +4,6 @@ Tests for content parsing, and form-overloaded content parsing.
from django.conf.urls.defaults import patterns
from django.contrib.auth.models import User
from django.test import TestCase, Client
-from django.utils import simplejson as json
from djangorestframework import status
from djangorestframework.authentication import UserLoggedInAuthentication
@@ -13,7 +12,6 @@ from djangorestframework.parsers import (
FormParser,
MultiPartParser,
PlainTextParser,
- JSONParser
)
from djangorestframework.request import Request
from djangorestframework.response import Response
diff --git a/djangorestframework/utils/encoders.py b/djangorestframework/utils/encoders.py
index 3cd2e8e1..ba7c8553 100644
--- a/djangorestframework/utils/encoders.py
+++ b/djangorestframework/utils/encoders.py
@@ -1,3 +1,6 @@
+"""
+Helper classes for parsers.
+"""
import datetime
import decimal
from django.utils import timezone
@@ -6,10 +9,12 @@ from django.utils import simplejson as json
class JSONEncoder(json.JSONEncoder):
"""
- JSONEncoder subclass that knows how to encode date/time and decimal types.
+ JSONEncoder subclass that knows how to encode date/time,
+ decimal types, and generators.
"""
def default(self, o):
- # See "Date Time String Format" in the ECMA-262 specification.
+ # 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:
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index 0b6eac9d..34bac155 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -127,7 +127,7 @@ We've now got a few comment instances to play with. Let's take a look at serial
serializer = CommentSerializer(instance=c1)
serializer.data
- # {'email': u'leila@example.com', 'content': u'nothing to say', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)}
+ # {'email': u'leila@example.com', 'content': u'nothing to say', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774, tzinfo=<UTC>)}
At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`.