Custom JSONParser - extending Django-Rest-Framework JSONParser?
Why would you even need to extend JSONParser ? Well... I want to have a list of Points at the end of parsing, not a json-type data.
In that way I can easily manipulate this data and check information on it, i.e. using endorphine-algorithms to find out if badge can be applied to specific route!
How to start working on custom Parser from Djagno-Rest-Framework?
TDD approach of-course! But how? Well I didn't know myself when I started to figure it out, but then I thought to myself- why not check at source-code of Djagno-Rest-Framework - Django-Rest-Framework Parsers Test-case- But unfortunatelly this code does not provided me any valid answers.
Then I've looked into source code of parser and realized that the parse
method is the one used. So I've went that way to extend functionality with mine.
I've firstly made an test for checking if only extending JSONParse without changing content and picking parse
method will create at the end the same output as input (as a StringIO stream).
Then I've changed tests so they fit my assumptions about behaviour of this custom JSON parser.
Initial tests with source-code:
class RouteJSONParser(JSONParser):
""" Route JSON Parser - parses JSON file into list of Points"""
pass
class TestRouteJSONParser(TestCase):
" Tests for RouteJSONParser class parse method overloaded"
def setUp(self):
self.parser = RouteJSONParser()
def test_parse(self):
" tests parser "
input_dict = [
{
"lat": "53.126699",
"lon": "18.072322",
"time": "2000-01-01T07:47:02Z",
"ele": 107.0
},
{
"lat": "53.126699",
"lon": "18.072322",
"time": "2001-01-01T07:47:02Z",
"ele": 99.0
}
]
input_stream = StringIO(json.dumps(input_dict))
output = self.parser.parse(input_stream)
assert str(output) == str(input_dict)
Final Source code of RoutesJSONParser with Tests
Tests:
class TestRouteJSONParser(TestCase):
" Tests for RouteJSONParser class parse method overloaded"
def setUp(self):
self.parser = RouteJSONParser()
def test_parse(self):
" tests parser "
input_dict = [
{
"lat": "53.126699",
"lon": "18.072322",
"time": "2000-01-01T07:47:02Z",
"ele": 107.0
},
{
"lat": "53.126699",
"lon": "18.072322",
"time": "2001-01-01T07:47:02Z",
"ele": 99.0
}
]
expected = [
Point(
lat="53.126699",
lon="18.072322",
time="2000-01-01T07:47:02Z",
ele=107.0
),
Point(
lat="53.126699",
lon="18.072322",
time="2001-01-01T07:47:02Z",
ele=99.0
)
]
input_stream = StringIO(json.dumps(input_dict))
output = self.parser.parse(input_stream)
assert str(output) == str(expected)
Source-code - that resides in a test-module for now. I will definitely move that into source-files.
# pylint: disable=too-few-public-methods
class Point(object):
""" Point object with lat/lon time and ele """
def __init__(self, lat, lon, time, ele):
self.lat = lat
self.lon = lon
self.time = time
self.ele = ele
def __repr__(self):
return "{},{},{},{}".format(
self.lat,
self.lon,
self.time,
self.ele,
)
# pylint: disable=too-few-public-methods
class RouteJSONParser(JSONParser):
""" Route JSON Parser - parses JSON file into list of Points"""
def parse(self, stream, media_type=None, parser_context=None):
json_content = super(RouteJSONParser, self).parse(stream, media_type, parser_context)
points = []
for point in json_content:
points.append(Point(point['lat'], point['lon'], point['time'], point['ele']))
return points
File uploading via REST-API.
I wanted to follow TDD approach at this, but first I had to check how to create FileUploading solution cause I don't remember when I've done such thing!
So I've broke my "first test than production code", and first created a production code to check how it will look like, So I could find out how I should test it.
Fortunatelly for me Django-Rest-Framework Documentation on that subject.
After struggling for few hours with having still a "Not found" feedback information from source-code in tests, I've passed for now.
I'll figure this out someway, but this will not be today unfortunatelly :(
When checking manually source-code seems to be working, but I'm not sure if it does in all cases because of lack of passing tests... :/
Source-Code:
class FileUploadView(APIView):
" A view for file upload "
parser_classes = (JSONParser,)
def post(self, request, format='json'):
return Response(request.data)
Tests that is not passing yet:
class TestFileUploadView(APIGeneralTestCase):
"File Upload tests"
def setUp(self):
super(self.__class__, self).setUp()
self.api = APIRequestFactory().post
self.view = FileUploadView.as_view()
def _create_test_file(self, path):
f = open(path, 'w')
f.write('test123\n')
f.close()
f = open(path, 'rb')
return {'datafile': f}
def test_update_user(self):
" Tests if making api request updates user with new name"
import os
dir_path = os.path.dirname(os.path.realpath(__file__))
file_ = open(dir_path + '/samples/routesample.json', 'rb')
api_data = {'filename': file_ }
self.client.credentials(HTTP_AUTHORIZATION='Token {}'.format(self.token))
request = APIRequestFactory().post(
"/api/file/",
api_data,
format='json'
)
response = self.view(request, format='multipart')
assert response.status_code == 200
Bugs /Features I've found while working
Bugs:
Non so-far.
Features:
Code commits done for this post:
In branch feature/55-parser-for-routes-in-json-data-format: - Adds tests with RouteJSONParser
In branch feature/46-Endpoint-for-adding-new-routes-should-take-json-content-in-body:
Acknowledgements
StackOverFlow:
- Understanding Python super() with init() methods [duplicate]
- Django Rest Framework File Upload
- How to fix: “UnicodeDecodeError: 'ascii' codec can't decode byte”
- JSON ValueError: Expecting property name: line 1 column 2 (char 1)
- python: comparing 2 lists of instances
- Compare object instances for equality by their attributes in Python
Other:
Djagno-Rest-Framework Documentation:
What's next
Contest is almost over. But working on project is not :)
Anndd.....
Monty Python is here again!
Thanks! See you soon!
Comments
comments powered by Disqus