mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 09:57:55 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			261 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			261 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""
 | 
						|
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.
 | 
						|
 | 
						|
We need a method to be able to:
 | 
						|
 | 
						|
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
 | 
						|
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
 | 
						|
from django.http.multipartparser import MultiPartParserError
 | 
						|
from django.utils import simplejson as json
 | 
						|
from djangorestframework.compat import yaml
 | 
						|
from djangorestframework.exceptions import ParseError
 | 
						|
from djangorestframework.utils.mediatypes import media_type_matches
 | 
						|
from xml.etree import ElementTree as ET
 | 
						|
from djangorestframework.compat import ETParseError
 | 
						|
from xml.parsers.expat import ExpatError
 | 
						|
import datetime
 | 
						|
import decimal
 | 
						|
from io import BytesIO
 | 
						|
 | 
						|
 | 
						|
__all__ = (
 | 
						|
    'BaseParser',
 | 
						|
    'JSONParser',
 | 
						|
    'PlainTextParser',
 | 
						|
    'FormParser',
 | 
						|
    'MultiPartParser',
 | 
						|
    'YAMLParser',
 | 
						|
    'XMLParser'
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
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,
 | 
						|
    and overriding the :meth:`parse` method.
 | 
						|
    """
 | 
						|
 | 
						|
    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*.
 | 
						|
 | 
						|
        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
 | 
						|
        they match.
 | 
						|
 | 
						|
        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)
 | 
						|
 | 
						|
    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):
 | 
						|
        """
 | 
						|
        Given a *stream* to read from, return the deserialized output.
 | 
						|
        Should return parsed data, or a DataAndFiles object consisting of the
 | 
						|
        parsed data and files.
 | 
						|
        """
 | 
						|
        raise NotImplementedError(".parse_stream() must be overridden.")
 | 
						|
 | 
						|
 | 
						|
class JSONParser(BaseParser):
 | 
						|
    """
 | 
						|
    Parses JSON-serialized data.
 | 
						|
    """
 | 
						|
 | 
						|
    media_type = 'application/json'
 | 
						|
 | 
						|
    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))
 | 
						|
 | 
						|
 | 
						|
class YAMLParser(BaseParser):
 | 
						|
    """
 | 
						|
    Parses YAML-serialized data.
 | 
						|
    """
 | 
						|
 | 
						|
    media_type = 'application/yaml'
 | 
						|
 | 
						|
    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 yaml.safe_load(stream)
 | 
						|
        except (ValueError, yaml.parser.ParserError), exc:
 | 
						|
            raise ParseError('YAML parse error - %s' % unicode(exc))
 | 
						|
 | 
						|
 | 
						|
class PlainTextParser(BaseParser):
 | 
						|
    """
 | 
						|
    Plain text parser.
 | 
						|
    """
 | 
						|
 | 
						|
    media_type = 'text/plain'
 | 
						|
 | 
						|
    def parse_stream(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`.
 | 
						|
        """
 | 
						|
        return stream.read()
 | 
						|
 | 
						|
 | 
						|
class FormParser(BaseParser):
 | 
						|
    """
 | 
						|
    Parser for form data.
 | 
						|
    """
 | 
						|
 | 
						|
    media_type = 'application/x-www-form-urlencoded'
 | 
						|
 | 
						|
    def parse_stream(self, stream, **opts):
 | 
						|
        """
 | 
						|
        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_stream(self, stream, **opts):
 | 
						|
        """
 | 
						|
        Returns a DataAndFiles object.
 | 
						|
 | 
						|
        `.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)
 | 
						|
            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_stream(self, stream, **opts):
 | 
						|
        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
 | 
						|
 | 
						|
 | 
						|
DEFAULT_PARSERS = (
 | 
						|
    JSONParser,
 | 
						|
    FormParser,
 | 
						|
    MultiPartParser,
 | 
						|
    XMLParser
 | 
						|
)
 | 
						|
 | 
						|
if yaml:
 | 
						|
    DEFAULT_PARSERS += (YAMLParser, )
 | 
						|
else:
 | 
						|
    YAMLParser = None
 |