Skip to content

Make type convert overridable #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions docs/parsers.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,52 @@ If you are considering using `XML` for your API, you may want to consider implem
Requires the `defusedxml` package to be installed.

**.media_type**: `application/xml`


# Converting values to native Python data types

N.B. This section describes behavior that has changed from version 2 upwards.

Values are *not* converted to native Python, which means that you'll get strings from the default parser. You can implement your own conversion to native data types with a custom Parser that implements a `type_convert(value)` method. This method accepts a single value and returns the converted value.

In version 1.x and earlier, conversions were done for you. To retain the original behavior, implement a Parser like below and enable this Parser in `DEFAULT_PARSER_CLASSES`:

import datetime
import decimal

from rest_framework_xml.parsers import XMLParser

class MyXMLParser(XMLParser):

def type_convert(self, value):
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

Enable this in your settings:

REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'path.to.MyXMLParser',
)
}

Or use it in `parser_classes` or the decorator for individual class based or function based views.

import path.to.MyXMLParser as XMLParser
28 changes: 4 additions & 24 deletions rest_framework_xml/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
Provides XML parsing support.
"""
from __future__ import unicode_literals
import datetime
import decimal

from django.conf import settings
from django.utils import six
Expand Down Expand Up @@ -45,7 +43,7 @@ def _xml_convert(self, element):
children = list(element)

if len(children) == 0:
return self._type_convert(element.text)
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":
Expand All @@ -59,27 +57,9 @@ def _xml_convert(self, element):

return data

def _type_convert(self, value):
def type_convert(self, value):
"""
Converts the value returned by the XMl parse into the equivalent
Python type
Converts the value returned by the XMl parse into the equivalent Python
type. Override this method in your own Parser to do the conversion.
"""
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
12 changes: 5 additions & 7 deletions tests/test_parsers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import datetime


from django.test import TestCase
from django.test.utils import skipUnless
Expand All @@ -22,10 +20,10 @@ def setUp(self):
'</root>'
)
self._data = {
'field_a': 121,
'field_a': '121.0',
'field_b': 'dasd',
'field_c': None,
'field_d': datetime.datetime(2011, 12, 25, 12, 45, 00)
'field_d': '2011-12-25 12:45:00'
}
self._complex_data_input = StringIO(
'<?xml version="1.0" encoding="utf-8"?>'
Expand All @@ -39,15 +37,15 @@ def setUp(self):
'</root>'
)
self._complex_data = {
"creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
"creation_date": "2011-12-25 12:45:00",
"name": "name",
"sub_data_list": [
{
"sub_id": 1,
"sub_id": "1",
"sub_name": "first"
},
{
"sub_id": 2,
"sub_id": "2",
"sub_name": "second"
}
]
Expand Down
19 changes: 17 additions & 2 deletions tests/test_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ class XMLRendererTestCase(TestCase):
}
]
}
_complex_data_out = {
"creation_date": "2011-12-25 12:45:00",
"name": "name",
"sub_data_list": [
{
"sub_id": "1",
"sub_name": "first"
},
{
"sub_id": "2",
"sub_name": "second"
}
]
}

def test_render_string(self):
"""
Expand Down Expand Up @@ -114,8 +128,9 @@ def test_render_and_parse_complex_data(self):

parser = XMLParser()
complex_data_out = parser.parse(content)
error_msg = "complex data differs!IN:\n %s \n\n OUT:\n %s" % (repr(self._complex_data), repr(complex_data_out))
self.assertEqual(self._complex_data, complex_data_out, error_msg)
error_msg = "complex data differs!IN:\n %s \n\n OUT:\n %s" % (
repr(complex_data_out), repr(self._complex_data_out))
self.assertEqual(complex_data_out, self._complex_data_out, error_msg)

def assertXMLContains(self, xml, string):
self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>'))
Expand Down