Merge branch 'jpadilla-yaml' into version-3.1

This commit is contained in:
Tom Christie 2014-12-03 23:24:32 +00:00
commit 5e60528117
16 changed files with 28 additions and 246 deletions

View File

@ -214,6 +214,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[docs]: http://www.django-rest-framework.org/
[urlobject]: https://github.com/zacharyvoase/urlobject
[markdown]: http://pypi.python.org/pypi/Markdown/
[pyyaml]: http://pypi.python.org/pypi/PyYAML
[django-filter]: http://pypi.python.org/pypi/django-filter
[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
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 = {
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.YAMLParser',
'rest_framework.parsers.JSONParser',
)
}
You can also set the parsers used for an individual view, or viewset,
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.views import 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):
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.
@api_view(['POST'])
@parser_classes((YAMLParser,))
@parser_classes((JSONParser,))
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})
@ -70,14 +70,6 @@ Parses `JSON` request content.
**.media_type**: `application/json`
## YAMLParser
Parses `YAML` request content.
Requires the `pyyaml` package to be installed.
**.media_type**: `application/yaml`
## FormParser
Parses HTML form content. `request.data` will be populated with a `QueryDict` of data.

View File

@ -18,11 +18,11 @@ For more information see the documentation on [content negotiation][conneg].
## 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 = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.YAMLRenderer',
'rest_framework.renderers.JSONRenderer',
'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.
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.views import 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):
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
**.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
Renders data to HTML, using Django's standard template rendering.

View File

@ -12,10 +12,10 @@ For example your project's `settings.py` file might include something like this:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.YAMLRenderer',
'rest_framework.renderers.JSONRenderer',
),
'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.
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 = {
...
'TEST_REQUEST_RENDERER_CLASSES': (
'rest_framework.renderers.MultiPartRenderer',
'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:
* [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-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.
@ -254,7 +253,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[mozilla]: http://www.mozilla.org/en-US/about/
[eventbrite]: https://www.eventbrite.co.uk/about/
[markdown]: http://pypi.python.org/pypi/Markdown/
[yaml]: http://pypi.python.org/pypi/PyYAML
[django-filter]: http://pypi.python.org/pypi/django-filter
[django-restframework-oauth]: https://github.com/jlafon/django-rest-framework-oauth
[django-guardian]: https://github.com/lukaszb/django-guardian

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.
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

View File

@ -6,7 +6,11 @@ flake8==2.2.2
# Optional packages
markdown>=2.1.0
<<<<<<< HEAD
PyYAML>=3.10
=======
defusedxml>=0.3
>>>>>>> 731c8421afe3093a78cdabb9c3cc28fa52cd1c8e
django-guardian==1.2.4
django-filter>=0.5.4
Pillow==2.3.0

View File

@ -237,13 +237,6 @@ except ImportError:
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
# See: http://bugs.python.org/issue22767
if six.PY3:

View File

@ -12,7 +12,7 @@ from django.http import QueryDict
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter
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 import renderers
import json
@ -63,29 +63,6 @@ class JSONParser(BaseParser):
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):
"""
Parser for form data.

View File

@ -17,7 +17,7 @@ from django.template import Context, RequestContext, loader, Template
from django.test.client import encode_multipart
from django.utils import six
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.settings import api_settings
from rest_framework.request import is_form_media_type, override_method
@ -103,29 +103,6 @@ class JSONRenderer(BaseRenderer):
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):
"""
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 = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.YAMLRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
)
'DEFAULT_PARSER_CLASSES': (
'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.utils import six, timezone
from django.utils.functional import Promise
from rest_framework.compat import force_text, OrderedDict
from rest_framework.compat import force_text
import datetime
import decimal
import types
import json
@ -56,65 +55,3 @@ class JSONEncoder(json.JSONEncoder):
elif hasattr(obj, '__iter__'):
return tuple(item for item in 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,11 +9,9 @@ from django.test import TestCase
from django.utils import six
from django.utils.translation import ugettext_lazy as _
from rest_framework import status, permissions
from rest_framework.compat import yaml, BytesIO
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, BrowsableAPIRenderer
from rest_framework.parsers import YAMLParser
from rest_framework.renderers import BaseRenderer, JSONRenderer, BrowsableAPIRenderer
from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory
from collections import MutableMapping
@ -394,55 +392,6 @@ class AsciiJSONRendererTests(TestCase):
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
class CacheRenderTest(TestCase):
"""

View File

@ -54,7 +54,7 @@ class Issue1386Tests(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):
"""
@ -73,14 +73,3 @@ class URLizerTests(TestCase):
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], '
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

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