Commit 7abef9ac by Tom Christie

Parsers may return raw data, or a DataAndFiles object

parent d180e984
......@@ -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):
"""
......
......@@ -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)
......
......@@ -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)
......@@ -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)
......
......@@ -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
......
"""
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:
......
......@@ -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`.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment