Commit 5e605281 by Tom Christie

Merge branch 'jpadilla-yaml' into version-3.1

parents cd4d8660 3d6620c7
...@@ -214,6 +214,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -214,6 +214,5 @@ 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
[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
...@@ -26,26 +26,26 @@ As an example, if you are sending `json` encoded data using jQuery with the [.aj ...@@ -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. ...@@ -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. ...@@ -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`
## FormParser ## FormParser
Parses HTML form content. `request.data` will be populated with a `QueryDict` of data. Parses HTML form content. `request.data` will be populated with a `QueryDict` of data.
......
...@@ -18,11 +18,11 @@ For more information see the documentation on [content negotiation][conneg]. ...@@ -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, ...@@ -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()
...@@ -93,38 +93,6 @@ The default JSON encoding style can be altered using the `UNICODE_JSON` and `COM ...@@ -93,38 +93,6 @@ The default JSON encoding style can be altered using the `UNICODE_JSON` and `COM
**.charset**: `None` **.charset**: `None`
## 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`
## TemplateHTMLRenderer ## TemplateHTMLRenderer
Renders data to HTML, using Django's standard template rendering. Renders data to HTML, using Django's standard template rendering.
......
...@@ -12,10 +12,10 @@ For example your project's `settings.py` file might include something like this: ...@@ -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',
) )
} }
......
...@@ -255,14 +255,14 @@ The default format used to make test requests may be set using the `TEST_REQUEST ...@@ -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'
) )
} }
......
...@@ -54,7 +54,6 @@ REST framework requires the following: ...@@ -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.
* [django-filter][django-filter] (0.5.4+) - Filtering support. * [django-filter][django-filter] (0.5.4+) - Filtering support.
* [django-restframework-oauth][django-restframework-oauth] package for OAuth 1.0a and 2.0 support. * [django-restframework-oauth][django-restframework-oauth] package for OAuth 1.0a and 2.0 support.
* [django-guardian][django-guardian] (1.1.1+) - Object level permissions support. * [django-guardian][django-guardian] (1.1.1+) - Object level permissions support.
...@@ -254,7 +253,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -254,7 +253,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
[django-filter]: http://pypi.python.org/pypi/django-filter [django-filter]: http://pypi.python.org/pypi/django-filter
[django-restframework-oauth]: https://github.com/jlafon/django-rest-framework-oauth [django-restframework-oauth]: https://github.com/jlafon/django-rest-framework-oauth
[django-guardian]: https://github.com/lukaszb/django-guardian [django-guardian]: https://github.com/lukaszb/django-guardian
......
...@@ -92,7 +92,7 @@ Here is the view for an individual snippet, in the `views.py` module. ...@@ -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
......
...@@ -6,7 +6,11 @@ flake8==2.2.2 ...@@ -6,7 +6,11 @@ flake8==2.2.2
# Optional packages # Optional packages
markdown>=2.1.0 markdown>=2.1.0
<<<<<<< HEAD
PyYAML>=3.10 PyYAML>=3.10
=======
defusedxml>=0.3
>>>>>>> 731c8421afe3093a78cdabb9c3cc28fa52cd1c8e
django-guardian==1.2.4 django-guardian==1.2.4
django-filter>=0.5.4 django-filter>=0.5.4
Pillow==2.3.0 Pillow==2.3.0
...@@ -237,13 +237,6 @@ except ImportError: ...@@ -237,13 +237,6 @@ except ImportError:
apply_markdown = None apply_markdown = None
# Yaml is optional
try:
import yaml
except ImportError:
yaml = None
# `seperators` argument to `json.dumps()` differs between 2.x and 3.x # `seperators` argument to `json.dumps()` differs between 2.x and 3.x
# See: http://bugs.python.org/issue22767 # See: http://bugs.python.org/issue22767
if six.PY3: if six.PY3:
......
...@@ -12,7 +12,7 @@ from django.http import QueryDict ...@@ -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 yaml, force_text, urlparse from rest_framework.compat import 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
...@@ -63,29 +63,6 @@ class JSONParser(BaseParser): ...@@ -63,29 +63,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.
......
...@@ -17,7 +17,7 @@ from django.template import Context, RequestContext, loader, Template ...@@ -17,7 +17,7 @@ from django.template import Context, RequestContext, loader, Template
from django.test.client import encode_multipart from django.test.client import encode_multipart
from django.utils import six from django.utils import six
from rest_framework import exceptions, serializers, status, VERSION from rest_framework import exceptions, serializers, status, VERSION
from rest_framework.compat import SHORT_SEPARATORS, LONG_SEPARATORS, yaml from rest_framework.compat import SHORT_SEPARATORS, LONG_SEPARATORS
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
from rest_framework.request import is_form_media_type, override_method from rest_framework.request import is_form_media_type, override_method
...@@ -103,29 +103,6 @@ class JSONRenderer(BaseRenderer): ...@@ -103,29 +103,6 @@ class JSONRenderer(BaseRenderer):
return ret return ret
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.
......
...@@ -5,11 +5,11 @@ For example your project's `settings.py` file might look like this: ...@@ -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',
) )
} }
......
...@@ -5,10 +5,9 @@ from __future__ import unicode_literals ...@@ -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): ...@@ -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
)
...@@ -9,11 +9,9 @@ from django.test import TestCase ...@@ -9,11 +9,9 @@ from django.test import TestCase
from django.utils import six from django.utils import six
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, BytesIO
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, BrowsableAPIRenderer from rest_framework.renderers import BaseRenderer, JSONRenderer, BrowsableAPIRenderer
from rest_framework.parsers import YAMLParser
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
...@@ -394,55 +392,6 @@ class AsciiJSONRendererTests(TestCase): ...@@ -394,55 +392,6 @@ class AsciiJSONRendererTests(TestCase):
self.assertEqual(content, '{"countries":["United Kingdom","France","Espa\\u00f1a"]}'.encode('utf-8')) self.assertEqual(content, '{"countries":["United Kingdom","France","Espa\\u00f1a"]}'.encode('utf-8'))
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'))
# Tests for caching issue, #346 # Tests for caching issue, #346
class CacheRenderTest(TestCase): class CacheRenderTest(TestCase):
""" """
......
...@@ -54,7 +54,7 @@ class Issue1386Tests(TestCase): ...@@ -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): ...@@ -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)
...@@ -20,7 +20,6 @@ deps = ...@@ -20,7 +20,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 =
......
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