aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/parsers.py
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework/parsers.py')
-rw-r--r--rest_framework/parsers.py189
1 files changed, 189 insertions, 0 deletions
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
new file mode 100644
index 00000000..4841676c
--- /dev/null
+++ b/rest_framework/parsers.py
@@ -0,0 +1,189 @@
+"""
+Parsers are used to parse the content of incoming HTTP requests.
+
+They give us a generic way of being able to handle various media types
+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 django.utils import simplejson as json
+from rest_framework.compat import yaml, ETParseError
+from rest_framework.exceptions import ParseError
+from xml.etree import ElementTree as ET
+from xml.parsers.expat import ExpatError
+import datetime
+import decimal
+
+
+class DataAndFiles(object):
+ def __init__(self, data, files):
+ self.data = data
+ self.files = files
+
+
+class BaseParser(object):
+ """
+ All parsers should extend `BaseParser`, specifying a `media_type`
+ attribute, and overriding the `.parse()` method.
+ """
+
+ media_type = None
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ """
+ Given a stream to read from, return the parsed representation.
+ Should return parsed data, or a `DataAndFiles` object consisting of the
+ parsed data and files.
+ """
+ raise NotImplementedError(".parse() must be overridden.")
+
+
+class JSONParser(BaseParser):
+ """
+ Parses JSON-serialized data.
+ """
+
+ media_type = 'application/json'
+
+ 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 json.load(stream)
+ except ValueError, exc:
+ raise ParseError('JSON parse error - %s' % unicode(exc))
+
+
+class YAMLParser(BaseParser):
+ """
+ Parses YAML-serialized data.
+ """
+
+ media_type = 'application/yaml'
+
+ 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 yaml.safe_load(stream)
+ except (ValueError, yaml.parser.ParserError), exc:
+ raise ParseError('YAML parse error - %s' % unicode(exc))
+
+
+class FormParser(BaseParser):
+ """
+ Parser for form data.
+ """
+
+ media_type = 'application/x-www-form-urlencoded'
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ """
+ Returns a 2-tuple of `(data, files)`.
+
+ `data` will be a :class:`QueryDict` containing all the form parameters.
+ `files` will always be :const:`None`.
+ """
+ data = QueryDict(stream.read())
+ return data
+
+
+class MultiPartParser(BaseParser):
+ """
+ Parser for multipart form data, which may include file data.
+ """
+
+ media_type = 'multipart/form-data'
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ """
+ Returns a DataAndFiles object.
+
+ `.data` will be a `QueryDict` containing all the form parameters.
+ `.files` will be a `QueryDict` containing all the form files.
+ """
+ parser_context = parser_context or {}
+ request = parser_context['request']
+ meta = request.META
+ upload_handlers = request.upload_handlers
+
+ try:
+ parser = DjangoMultiPartParser(meta, stream, upload_handlers)
+ data, files = parser.parse()
+ return DataAndFiles(data, files)
+ except MultiPartParserError, exc:
+ raise ParseError('Multipart form parse error - %s' % unicode(exc))
+
+
+class XMLParser(BaseParser):
+ """
+ XML parser.
+ """
+
+ media_type = 'application/xml'
+
+ def parse(self, stream, media_type=None, parser_context=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
+
+ def _xml_convert(self, element):
+ """
+ convert the xml `element` into the corresponding python object
+ """
+
+ children = element.getchildren()
+
+ if len(children) == 0:
+ return self._type_convert(element.text)
+ else:
+ # if the fist child tag is list-item means all children are list-item
+ if children[0].tag == "list-item":
+ data = []
+ for child in children:
+ data.append(self._xml_convert(child))
+ else:
+ data = {}
+ for child in children:
+ data[child.tag] = self._xml_convert(child)
+
+ return data
+
+ def _type_convert(self, value):
+ """
+ Converts the value returned by the XMl parse into the equivalent
+ Python type
+ """
+ if value is None:
+ return value
+
+ try:
+ return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
+ except ValueError:
+ pass
+
+ try:
+ return int(value)
+ except ValueError:
+ pass
+
+ try:
+ return decimal.Decimal(value)
+ except decimal.InvalidOperation:
+ pass
+
+ return value