django-rest-framework/djangorestframework/parsers.py

261 lines
7.2 KiB
Python
Raw Normal View History

2011-05-10 13:49:28 +04:00
"""
Django supports parsing the content of an HTTP request, but only for form POST requests.
That behavior is sufficient for dealing with standard HTML forms, but it doesn't map well
to general HTTP requests.
2011-03-04 13:28:20 +03:00
We need a method to be able to:
2011-03-04 13:28:20 +03:00
1.) Determine the parsed content on a request for methods other than POST (eg typically also PUT)
2.) Determine the parsed content on a request for media types other than application/x-www-form-urlencoded
and multipart/form-data. (eg also handle multipart/json)
"""
from django.http import QueryDict
2011-05-10 13:49:28 +04:00
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError
2011-05-10 13:49:28 +04:00
from django.utils import simplejson as json
from djangorestframework.compat import yaml
from djangorestframework.exceptions import ParseError
2011-05-10 13:49:28 +04:00
from djangorestframework.utils.mediatypes import media_type_matches
2011-12-11 22:27:40 +04:00
from xml.etree import ElementTree as ET
2012-02-25 22:45:17 +04:00
from djangorestframework.compat import ETParseError
from xml.parsers.expat import ExpatError
2011-12-11 22:27:40 +04:00
import datetime
import decimal
2012-09-03 18:57:43 +04:00
from io import BytesIO
2011-05-10 13:49:28 +04:00
2011-05-10 13:49:28 +04:00
__all__ = (
'BaseParser',
'JSONParser',
'PlainTextParser',
'FormParser',
'MultiPartParser',
'YAMLParser',
2012-01-11 17:44:11 +04:00
'XMLParser'
2011-05-10 13:49:28 +04:00
)
class DataAndFiles(object):
def __init__(self, data, files):
self.data = data
self.files = files
class BaseParser(object):
2011-05-10 13:49:28 +04:00
"""
All parsers should extend :class:`BaseParser`, specifying a :attr:`media_type` attribute,
and overriding the :meth:`parse` method.
2011-05-10 13:49:28 +04:00
"""
2011-05-19 11:49:57 +04:00
2011-01-26 23:31:47 +03:00
media_type = None
def can_handle_request(self, content_type):
"""
Returns :const:`True` if this parser is able to deal with the given *content_type*.
2011-12-29 17:31:12 +04:00
The default implementation for this function is to check the *content_type*
argument against the :attr:`media_type` attribute set on the class to see if
2011-05-10 13:49:28 +04:00
they match.
2011-12-29 17:31:12 +04:00
2011-05-10 13:49:28 +04:00
This may be overridden to provide for other behavior, but typically you'll
instead want to just set the :attr:`media_type` attribute on the class.
"""
return media_type_matches(self.media_type, content_type)
2012-09-03 18:57:43 +04:00
def parse(self, string_or_stream, **opts):
"""
The main entry point to parsers. This is a light wrapper around
`parse_stream`, that instead handles both string and stream objects.
"""
if isinstance(string_or_stream, basestring):
stream = BytesIO(string_or_stream)
else:
stream = string_or_stream
return self.parse_stream(stream, **opts)
def parse_stream(self, stream, **opts):
2011-05-10 13:49:28 +04:00
"""
Given a *stream* to read from, return the deserialized output.
2012-09-03 18:57:43 +04:00
Should return parsed data, or a DataAndFiles object consisting of the
parsed data and files.
2011-05-10 13:49:28 +04:00
"""
2012-09-03 18:57:43 +04:00
raise NotImplementedError(".parse_stream() Must be overridden to be implemented.")
class JSONParser(BaseParser):
"""
Parses JSON-serialized data.
"""
2011-05-19 11:49:57 +04:00
2011-05-10 13:49:28 +04:00
media_type = 'application/json'
2012-09-03 18:57:43 +04:00
def parse_stream(self, stream, **opts):
"""
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))
2011-01-26 11:58:09 +03:00
2012-02-25 22:45:17 +04:00
class YAMLParser(BaseParser):
"""
Parses YAML-serialized data.
"""
2011-12-29 17:31:12 +04:00
2012-02-25 22:45:17 +04:00
media_type = 'application/yaml'
2011-12-29 17:31:12 +04:00
2012-09-03 18:57:43 +04:00
def parse_stream(self, stream, **opts):
2012-02-25 22:45:17 +04:00
"""
Returns a 2-tuple of `(data, files)`.
2011-12-29 17:31:12 +04:00
2012-02-25 22:45:17 +04:00
`data` will be an object which is the parsed content of the response.
`files` will always be `None`.
"""
try:
return yaml.safe_load(stream)
2012-02-22 02:09:05 +04:00
except (ValueError, yaml.parser.ParserError), exc:
raise ParseError('YAML parse error - %s' % unicode(exc))
2012-01-21 22:33:34 +04:00
2011-04-11 14:54:26 +04:00
class PlainTextParser(BaseParser):
"""
Plain text parser.
"""
2011-05-19 11:49:57 +04:00
2011-05-10 13:49:28 +04:00
media_type = 'text/plain'
2011-04-11 14:54:26 +04:00
2012-09-03 18:57:43 +04:00
def parse_stream(self, stream, **opts):
"""
Returns a 2-tuple of `(data, files)`.
2011-12-29 17:31:12 +04:00
`data` will simply be a string representing the body of the request.
`files` will always be `None`.
"""
return stream.read()
2011-04-11 14:54:26 +04:00
class FormParser(BaseParser):
"""
Parser for form data.
2011-05-13 12:59:36 +04:00
"""
2011-03-10 17:49:11 +03:00
2011-05-10 13:49:28 +04:00
media_type = 'application/x-www-form-urlencoded'
2012-09-03 18:57:43 +04:00
def parse_stream(self, stream, **opts):
"""
Returns a 2-tuple of `(data, files)`.
2011-12-29 17:31:12 +04:00
2011-05-19 11:49:57 +04:00
`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.
"""
2011-05-10 13:49:28 +04:00
media_type = 'multipart/form-data'
2011-03-04 13:28:20 +03:00
2012-09-03 18:57:43 +04:00
def parse_stream(self, stream, **opts):
"""
Returns a DataAndFiles object.
2011-12-29 17:31:12 +04:00
`.data` will be a `QueryDict` containing all the form parameters.
`.files` will be a `QueryDict` containing all the form files.
"""
2012-08-28 18:46:38 +04:00
meta = opts['meta']
upload_handlers = opts['upload_handlers']
try:
2012-02-25 22:45:17 +04:00
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))
2012-01-19 07:59:30 +04:00
2011-12-11 22:27:40 +04:00
class XMLParser(BaseParser):
"""
XML parser.
"""
media_type = 'application/xml'
2012-09-03 18:57:43 +04:00
def parse_stream(self, stream, **opts):
2012-02-25 22:45:17 +04:00
try:
tree = ET.parse(stream)
except (ExpatError, ETParseError, ValueError), exc:
raise ParseError('XML parse error - %s' % unicode(exc))
2012-01-11 22:36:43 +04:00
data = self._xml_convert(tree.getroot())
2012-01-12 16:28:32 +04:00
return data
2012-01-12 16:28:32 +04:00
2012-01-11 22:36:43 +04:00
def _xml_convert(self, element):
"""
2012-01-21 22:33:34 +04:00
convert the xml `element` into the corresponding python object
2012-01-11 22:36:43 +04:00
"""
2012-01-21 22:33:34 +04:00
2012-01-11 22:36:43 +04:00
children = element.getchildren()
2012-01-21 22:33:34 +04:00
2012-01-11 22:36:43 +04:00
if len(children) == 0:
return self._type_convert(element.text)
else:
2012-01-12 16:28:32 +04:00
# if the fist child tag is list-item means all children are list-item
if children[0].tag == "list-item":
2012-01-11 22:36:43 +04:00
data = []
for child in children:
2012-01-21 22:33:34 +04:00
data.append(self._xml_convert(child))
else:
2012-01-12 16:28:32 +04:00
data = {}
for child in children:
data[child.tag] = self._xml_convert(child)
2012-01-11 22:36:43 +04:00
return data
2012-01-21 22:33:34 +04:00
2011-12-29 17:31:12 +04:00
def _type_convert(self, value):
2011-12-11 22:27:40 +04:00
"""
2011-12-29 17:31:12 +04:00
Converts the value returned by the XMl parse into the equivalent
2011-12-11 22:27:40 +04:00
Python type
2012-01-19 07:59:30 +04:00
"""
if value is None:
2011-12-11 22:27:40 +04:00
return value
2012-01-19 07:59:30 +04:00
2011-12-11 22:27:40 +04:00
try:
2012-01-21 22:33:34 +04:00
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
2011-12-11 22:27:40 +04:00
except ValueError:
pass
2012-01-19 07:59:30 +04:00
2011-12-11 22:27:40 +04:00
try:
return int(value)
except ValueError:
pass
2012-01-19 07:59:30 +04:00
2011-12-11 22:27:40 +04:00
try:
return decimal.Decimal(value)
except decimal.InvalidOperation:
pass
2012-01-19 07:59:30 +04:00
2011-12-11 22:27:40 +04:00
return value
2012-01-21 22:33:34 +04:00
DEFAULT_PARSERS = (
JSONParser,
FormParser,
MultiPartParser,
XMLParser
)
2012-02-25 22:45:17 +04:00
if yaml:
DEFAULT_PARSERS += (YAMLParser, )
else:
YAMLParser = None