Remove YAML support from core

This commit is contained in:
José Padilla 2014-11-29 14:43:05 -04:00
parent 3a5b3772fe
commit 731c8421af
16 changed files with 52 additions and 273 deletions

View File

@ -214,7 +214,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[docs]: http://www.django-rest-framework.org/ [docs]: http://www.django-rest-framework.org/
[urlobject]: https://github.com/zacharyvoase/urlobject [urlobject]: https://github.com/zacharyvoase/urlobject
[markdown]: http://pypi.python.org/pypi/Markdown/ [markdown]: http://pypi.python.org/pypi/Markdown/
[pyyaml]: http://pypi.python.org/pypi/PyYAML
[defusedxml]: https://pypi.python.org/pypi/defusedxml [defusedxml]: https://pypi.python.org/pypi/defusedxml
[django-filter]: http://pypi.python.org/pypi/django-filter [django-filter]: http://pypi.python.org/pypi/django-filter
[security-mail]: mailto:rest-framework-security@googlegroups.com [security-mail]: mailto:rest-framework-security@googlegroups.com

View File

@ -26,26 +26,26 @@ As an example, if you are sending `json` encoded data using jQuery with the [.aj
## Setting the parsers ## Setting the parsers
The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow requests with `YAML` content. The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow requests with `JSON` content.
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': ( 'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.YAMLParser', 'rest_framework.parsers.JSONParser',
) )
} }
You can also set the parsers used for an individual view, or viewset, You can also set the parsers used for an individual view, or viewset,
using the `APIView` class based views. using the `APIView` class based views.
from rest_framework.parsers import YAMLParser from rest_framework.parsers import JSONParser
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
class ExampleView(APIView): class ExampleView(APIView):
""" """
A view that can accept POST requests with YAML content. A view that can accept POST requests with JSON content.
""" """
parser_classes = (YAMLParser,) parser_classes = (JSONParser,)
def post(self, request, format=None): def post(self, request, format=None):
return Response({'received data': request.data}) return Response({'received data': request.data})
@ -53,10 +53,10 @@ using the `APIView` class based views.
Or, if you're using the `@api_view` decorator with function based views. Or, if you're using the `@api_view` decorator with function based views.
@api_view(['POST']) @api_view(['POST'])
@parser_classes((YAMLParser,)) @parser_classes((JSONParser,))
def example_view(request, format=None): def example_view(request, format=None):
""" """
A view that can accept POST requests with YAML content. A view that can accept POST requests with JSON content.
""" """
return Response({'received data': request.data}) return Response({'received data': request.data})
@ -70,14 +70,6 @@ Parses `JSON` request content.
**.media_type**: `application/json` **.media_type**: `application/json`
## YAMLParser
Parses `YAML` request content.
Requires the `pyyaml` package to be installed.
**.media_type**: `application/yaml`
## XMLParser ## XMLParser
Parses REST framework's default style of `XML` request content. Parses REST framework's default style of `XML` request content.

View File

@ -18,11 +18,11 @@ For more information see the documentation on [content negotiation][conneg].
## Setting the renderers ## Setting the renderers
The default set of renderers may be set globally, using the `DEFAULT_RENDERER_CLASSES` setting. For example, the following settings would use `YAML` as the main media type and also include the self describing API. The default set of renderers may be set globally, using the `DEFAULT_RENDERER_CLASSES` setting. For example, the following settings would use `JSON` as the main media type and also include the self describing API.
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( 'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.YAMLRenderer', 'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer', 'rest_framework.renderers.BrowsableAPIRenderer',
) )
} }
@ -31,15 +31,15 @@ You can also set the renderers used for an individual view, or viewset,
using the `APIView` class based views. using the `APIView` class based views.
from django.contrib.auth.models import User from django.contrib.auth.models import User
from rest_framework.renderers import JSONRenderer, YAMLRenderer from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
class UserCountView(APIView): class UserCountView(APIView):
""" """
A view that returns the count of active users, in JSON or YAML. A view that returns the count of active users in JSON.
""" """
renderer_classes = (JSONRenderer, YAMLRenderer) renderer_classes = (JSONRenderer, )
def get(self, request, format=None): def get(self, request, format=None):
user_count = User.objects.filter(active=True).count() user_count = User.objects.filter(active=True).count()
@ -113,38 +113,6 @@ The `jsonp` approach is essentially a browser hack, and is [only appropriate for
**.charset**: `utf-8` **.charset**: `utf-8`
## YAMLRenderer
Renders the request data into `YAML`.
Requires the `pyyaml` package to be installed.
Note that non-ascii characters will be rendered using `\uXXXX` character escape. For example:
unicode black star: "\u2605"
**.media_type**: `application/yaml`
**.format**: `'.yaml'`
**.charset**: `utf-8`
## UnicodeYAMLRenderer
Renders the request data into `YAML`.
Requires the `pyyaml` package to be installed.
Note that non-ascii characters will not be character escaped. For example:
unicode black star: ★
**.media_type**: `application/yaml`
**.format**: `'.yaml'`
**.charset**: `utf-8`
## XMLRenderer ## XMLRenderer
Renders REST framework's default style of `XML` response content. Renders REST framework's default style of `XML` response content.

View File

@ -12,10 +12,10 @@ For example your project's `settings.py` file might include something like this:
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( 'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.YAMLRenderer', 'rest_framework.renderers.JSONRenderer',
), ),
'DEFAULT_PARSER_CLASSES': ( 'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.YAMLParser', 'rest_framework.parsers.JSONParser',
) )
} }

View File

@ -255,14 +255,14 @@ The default format used to make test requests may be set using the `TEST_REQUEST
If you need to test requests using something other than multipart or json requests, you can do so by setting the `TEST_REQUEST_RENDERER_CLASSES` setting. If you need to test requests using something other than multipart or json requests, you can do so by setting the `TEST_REQUEST_RENDERER_CLASSES` setting.
For example, to add support for using `format='yaml'` in test requests, you might have something like this in your `settings.py` file. For example, to add support for using `format='html'` in test requests, you might have something like this in your `settings.py` file.
REST_FRAMEWORK = { REST_FRAMEWORK = {
... ...
'TEST_REQUEST_RENDERER_CLASSES': ( 'TEST_REQUEST_RENDERER_CLASSES': (
'rest_framework.renderers.MultiPartRenderer', 'rest_framework.renderers.MultiPartRenderer',
'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.YAMLRenderer' 'rest_framework.renderers.TemplateHTMLRenderer'
) )
} }

View File

@ -54,7 +54,6 @@ REST framework requires the following:
The following packages are optional: The following packages are optional:
* [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API. * [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API.
* [PyYAML][yaml] (3.10+) - YAML content-type support.
* [defusedxml][defusedxml] (0.3+) - XML content-type support. * [defusedxml][defusedxml] (0.3+) - XML content-type support.
* [django-filter][django-filter] (0.5.4+) - Filtering support. * [django-filter][django-filter] (0.5.4+) - Filtering support.
* [django-oauth-plus][django-oauth-plus] (2.0+) and [oauth2][oauth2] (1.5.211+) - OAuth 1.0a support. * [django-oauth-plus][django-oauth-plus] (2.0+) and [oauth2][oauth2] (1.5.211+) - OAuth 1.0a support.
@ -258,7 +257,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[mozilla]: http://www.mozilla.org/en-US/about/ [mozilla]: http://www.mozilla.org/en-US/about/
[eventbrite]: https://www.eventbrite.co.uk/about/ [eventbrite]: https://www.eventbrite.co.uk/about/
[markdown]: http://pypi.python.org/pypi/Markdown/ [markdown]: http://pypi.python.org/pypi/Markdown/
[yaml]: http://pypi.python.org/pypi/PyYAML
[defusedxml]: https://pypi.python.org/pypi/defusedxml [defusedxml]: https://pypi.python.org/pypi/defusedxml
[django-filter]: http://pypi.python.org/pypi/django-filter [django-filter]: http://pypi.python.org/pypi/django-filter
[oauth2]: https://github.com/simplegeo/python-oauth2 [oauth2]: https://github.com/simplegeo/python-oauth2

View File

@ -92,7 +92,7 @@ Here is the view for an individual snippet, in the `views.py` module.
This should all feel very familiar - it is not a lot different from working with regular Django views. This should all feel very familiar - it is not a lot different from working with regular Django views.
Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.data` can handle incoming `json` requests, but it can also handle `yaml` and other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us. Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.data` can handle incoming `json` requests, but it can also handle other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us.
## Adding optional format suffixes to our URLs ## Adding optional format suffixes to our URLs

View File

@ -6,7 +6,6 @@ flake8==2.2.2
# Optional packages # Optional packages
markdown>=2.1.0 markdown>=2.1.0
PyYAML>=3.10
defusedxml>=0.3 defusedxml>=0.3
django-guardian==1.2.4 django-guardian==1.2.4
django-filter>=0.5.4 django-filter>=0.5.4

View File

@ -237,13 +237,6 @@ except ImportError:
apply_markdown = None apply_markdown = None
# Yaml is optional
try:
import yaml
except ImportError:
yaml = None
# XML is optional # XML is optional
try: try:
import defusedxml.ElementTree as etree import defusedxml.ElementTree as etree

View File

@ -12,7 +12,7 @@ from django.http import QueryDict
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter
from django.utils import six from django.utils import six
from rest_framework.compat import etree, yaml, force_text, urlparse from rest_framework.compat import etree, force_text, urlparse
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
from rest_framework import renderers from rest_framework import renderers
import json import json
@ -65,29 +65,6 @@ class JSONParser(BaseParser):
raise ParseError('JSON parse error - %s' % six.text_type(exc)) raise ParseError('JSON parse error - %s' % six.text_type(exc))
class YAMLParser(BaseParser):
"""
Parses YAML-serialized data.
"""
media_type = 'application/yaml'
def parse(self, stream, media_type=None, parser_context=None):
"""
Parses the incoming bytestream as YAML and returns the resulting data.
"""
assert yaml, 'YAMLParser requires pyyaml to be installed'
parser_context = parser_context or {}
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
try:
data = stream.read().decode(encoding)
return yaml.safe_load(data)
except (ValueError, yaml.parser.ParserError) as exc:
raise ParseError('YAML parse error - %s' % six.text_type(exc))
class FormParser(BaseParser): class FormParser(BaseParser):
""" """
Parser for form data. Parser for form data.

View File

@ -19,7 +19,7 @@ from django.utils import six
from django.utils.xmlutils import SimplerXMLGenerator from django.utils.xmlutils import SimplerXMLGenerator
from rest_framework import exceptions, serializers, status, VERSION from rest_framework import exceptions, serializers, status, VERSION
from rest_framework.compat import ( from rest_framework.compat import (
SHORT_SEPARATORS, LONG_SEPARATORS, StringIO, smart_text, yaml SHORT_SEPARATORS, LONG_SEPARATORS, StringIO, smart_text
) )
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
@ -189,29 +189,6 @@ class XMLRenderer(BaseRenderer):
xml.characters(smart_text(data)) xml.characters(smart_text(data))
class YAMLRenderer(BaseRenderer):
"""
Renderer which serializes to YAML.
"""
media_type = 'application/yaml'
format = 'yaml'
encoder = encoders.SafeDumper
charset = 'utf-8'
ensure_ascii = False
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Renders `data` into serialized YAML.
"""
assert yaml, 'YAMLRenderer requires pyyaml to be installed'
if data is None:
return ''
return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder, allow_unicode=not self.ensure_ascii)
class TemplateHTMLRenderer(BaseRenderer): class TemplateHTMLRenderer(BaseRenderer):
""" """
An HTML renderer for use with templates. An HTML renderer for use with templates.

View File

@ -5,11 +5,11 @@ For example your project's `settings.py` file might look like this:
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( 'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.YAMLRenderer', 'rest_framework.renderers.TemplateHTMLRenderer',
) )
'DEFAULT_PARSER_CLASSES': ( 'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser', 'rest_framework.parsers.JSONParser',
'rest_framework.parsers.YAMLParser', 'rest_framework.parsers.TemplateHTMLRenderer',
) )
} }

View File

@ -5,10 +5,9 @@ from __future__ import unicode_literals
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.utils import six, timezone from django.utils import six, timezone
from django.utils.functional import Promise from django.utils.functional import Promise
from rest_framework.compat import force_text, OrderedDict from rest_framework.compat import force_text
import datetime import datetime
import decimal import decimal
import types
import json import json
@ -56,65 +55,3 @@ class JSONEncoder(json.JSONEncoder):
elif hasattr(obj, '__iter__'): elif hasattr(obj, '__iter__'):
return tuple(item for item in obj) return tuple(item for item in obj)
return super(JSONEncoder, self).default(obj) return super(JSONEncoder, self).default(obj)
try:
import yaml
except ImportError:
SafeDumper = None
else:
# Adapted from http://pyyaml.org/attachment/ticket/161/use_ordered_dict.py
class SafeDumper(yaml.SafeDumper):
"""
Handles decimals as strings.
Handles OrderedDicts as usual dicts, but preserves field order, rather
than the usual behaviour of sorting the keys.
"""
def represent_decimal(self, data):
return self.represent_scalar('tag:yaml.org,2002:str', six.text_type(data))
def represent_mapping(self, tag, mapping, flow_style=None):
value = []
node = yaml.MappingNode(tag, value, flow_style=flow_style)
if self.alias_key is not None:
self.represented_objects[self.alias_key] = node
best_style = True
if hasattr(mapping, 'items'):
mapping = list(mapping.items())
if not isinstance(mapping, OrderedDict):
mapping.sort()
for item_key, item_value in mapping:
node_key = self.represent_data(item_key)
node_value = self.represent_data(item_value)
if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style):
best_style = False
if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style):
best_style = False
value.append((node_key, node_value))
if flow_style is None:
if self.default_flow_style is not None:
node.flow_style = self.default_flow_style
else:
node.flow_style = best_style
return node
SafeDumper.add_representer(
decimal.Decimal,
SafeDumper.represent_decimal
)
SafeDumper.add_representer(
OrderedDict,
yaml.representer.SafeRepresenter.represent_dict
)
# SafeDumper.add_representer(
# DictWithMetadata,
# yaml.representer.SafeRepresenter.represent_dict
# )
# SafeDumper.add_representer(
# OrderedDictWithMetadata,
# yaml.representer.SafeRepresenter.represent_dict
# )
SafeDumper.add_representer(
types.GeneratorType,
yaml.representer.SafeRepresenter.represent_list
)

View File

@ -9,12 +9,12 @@ from django.test import TestCase
from django.utils import six, unittest from django.utils import six, unittest
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import status, permissions from rest_framework import status, permissions
from rest_framework.compat import yaml, etree, StringIO, BytesIO from rest_framework.compat import etree, StringIO
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ from rest_framework.renderers import BaseRenderer, JSONRenderer, XMLRenderer, \
XMLRenderer, JSONPRenderer, BrowsableAPIRenderer JSONPRenderer, BrowsableAPIRenderer
from rest_framework.parsers import YAMLParser, XMLParser from rest_framework.parsers import XMLParser
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
from collections import MutableMapping from collections import MutableMapping
@ -452,55 +452,6 @@ class JSONPRendererTests(TestCase):
) )
if yaml:
_yaml_repr = 'foo: [bar, baz]\n'
class YAMLRendererTests(TestCase):
"""
Tests specific to the YAML Renderer
"""
def test_render(self):
"""
Test basic YAML rendering.
"""
obj = {'foo': ['bar', 'baz']}
renderer = YAMLRenderer()
content = renderer.render(obj, 'application/yaml')
self.assertEqual(content.decode('utf-8'), _yaml_repr)
def test_render_and_parse(self):
"""
Test rendering and then parsing returns the original object.
IE obj -> render -> parse -> obj.
"""
obj = {'foo': ['bar', 'baz']}
renderer = YAMLRenderer()
parser = YAMLParser()
content = renderer.render(obj, 'application/yaml')
data = parser.parse(BytesIO(content))
self.assertEqual(obj, data)
def test_render_decimal(self):
"""
Test YAML decimal rendering.
"""
renderer = YAMLRenderer()
content = renderer.render({'field': Decimal('111.2')}, 'application/yaml')
self.assertYAMLContains(content.decode('utf-8'), "field: '111.2'")
def assertYAMLContains(self, content, string):
self.assertTrue(string in content, '%r not in %r' % (string, content))
def test_proper_encoding(self):
obj = {'countries': ['United Kingdom', 'France', 'España']}
renderer = YAMLRenderer()
content = renderer.render(obj, 'application/yaml')
self.assertEqual(content.strip(), 'countries: [United Kingdom, France, España]'.encode('utf-8'))
class XMLRendererTestCase(TestCase): class XMLRendererTestCase(TestCase):
""" """
Tests specific to the XML Renderer Tests specific to the XML Renderer

View File

@ -54,7 +54,7 @@ class Issue1386Tests(TestCase):
class URLizerTests(TestCase): class URLizerTests(TestCase):
""" """
Test if both JSON and YAML URLs are transformed into links well Test if JSON URLs are transformed into links well
""" """
def _urlize_dict_check(self, data): def _urlize_dict_check(self, data):
""" """
@ -73,14 +73,3 @@ class URLizerTests(TestCase):
data['"foo_set": [\n "http://api/foos/1/"\n], '] = \ data['"foo_set": [\n "http://api/foos/1/"\n], '] = \
'&quot;foo_set&quot;: [\n &quot;<a href="http://api/foos/1/">http://api/foos/1/</a>&quot;\n], ' '&quot;foo_set&quot;: [\n &quot;<a href="http://api/foos/1/">http://api/foos/1/</a>&quot;\n], '
self._urlize_dict_check(data) self._urlize_dict_check(data)
def test_yaml_with_url(self):
"""
Test if YAML URLs are transformed into links well
"""
data = {}
data['''{users: 'http://api/users/'}'''] = \
'''{users: &#39;<a href="http://api/users/">http://api/users/</a>&#39;}'''
data['''foo_set: ['http://api/foos/1/']'''] = \
'''foo_set: [&#39;<a href="http://api/foos/1/">http://api/foos/1/</a>&#39;]'''
self._urlize_dict_check(data)

View File

@ -24,7 +24,6 @@ deps =
django-filter==0.7 django-filter==0.7
defusedxml==0.3 defusedxml==0.3
markdown>=2.1.0 markdown>=2.1.0
PyYAML>=3.10
[testenv:py27-flake8] [testenv:py27-flake8]
deps = deps =