├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── drf_ujson ├── __init__.py ├── parsers.py └── renderers.py ├── setup.py ├── test_requirements.txt └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | #PyCharm 39 | .idea/* 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.3" 5 | install: 6 | - pip install -r test_requirements.txt 7 | - pip install . --use-mirrors 8 | script: nosetests 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Gizmag 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Django Rest Framework UJSON Renderer 2 | ================== 3 | 4 | [![Build Status](https://travis-ci.org/gizmag/drf-ujson-renderer.png?branch=master)](https://travis-ci.org/gizmag/drf-ujson-renderer) 5 | 6 | Django Rest Framework renderer using [ujson](https://github.com/esnme/ultrajson) 7 | 8 | ## Installation 9 | 10 | `pip install drf_ujson` 11 | 12 | You can then set the `UJSONRenderer` class as your default renderer in your `settings.py` 13 | 14 | ```python 15 | REST_FRAMEWORK = { 16 | 'DEFAULT_RENDERER_CLASSES': ( 17 | 'drf_ujson.renderers.UJSONRenderer', 18 | ), 19 | ... 20 | } 21 | ``` 22 | 23 | Also you can set the `UJSONParser` class as your default parser in your `settings.py` 24 | 25 | ```python 26 | REST_FRAMEWORK = { 27 | 'DEFAULT_PARSER_CLASSES': ( 28 | 'drf_ujson.parsers.UJSONParser', 29 | ), 30 | ... 31 | } 32 | ``` 33 | 34 | ## Benchmarks 35 | This is on average 2.3x faster than the default JSON Serializer. 36 | 37 | ```python 38 | import timeit 39 | 40 | setup = ''' 41 | from proposals.models import Proposal 42 | from proposals.serializers import ProposalSerializer 43 | from rest_framework.renderers import JSONRenderer 44 | from drf_ujson.renderers import UJSONRenderer 45 | 46 | proposals = Proposal.objects.all() 47 | serialized = ProposalSerializer(proposals, many=True).data 48 | ''' 49 | 50 | stdlib_test = ''' 51 | JSONRenderer().render(serialized) 52 | ''' 53 | 54 | ujson_test = ''' 55 | UJSONRenderer().render(serialized) 56 | ''' 57 | 58 | stdlib_result = timeit.repeat(stdlib_test, setup=setup, number=1, repeat=10) 59 | ujson_result = timeit.repeat(ujson_test, setup=setup, number=1, repeat=10) 60 | 61 | print stdlib_result 62 | print sum(stdlib_result) / 10 63 | print ujson_result 64 | print sum(ujson_result) / 10 65 | 66 | # stdlib results 67 | [ 68 | 0.004502058029174805, 69 | 0.004289865493774414, 70 | 0.006896018981933594, 71 | 0.0048198699951171875, 72 | 0.004084110260009766, 73 | 0.007154941558837891, 74 | 0.003937959671020508, 75 | 0.004029035568237305, 76 | 0.004770040512084961, 77 | 0.004539966583251953 78 | ] 79 | # avg 80 | 0.00490238666534 81 | 82 | # ujson results 83 | [ 84 | 0.0016620159149169922, 85 | 0.001817941665649414, 86 | 0.0015261173248291016, 87 | 0.0040950775146484375, 88 | 0.0021469593048095703, 89 | 0.001798868179321289, 90 | 0.001569986343383789, 91 | 0.0019931793212890625, 92 | 0.0017120838165283203, 93 | 0.001814126968383789 94 | ] 95 | # avg 96 | 0.00201363563538 97 | ``` 98 | -------------------------------------------------------------------------------- /drf_ujson/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gizmag/drf-ujson-renderer/8d076e6c9b74f814bcac1618c873e911f783b291/drf_ujson/__init__.py -------------------------------------------------------------------------------- /drf_ujson/parsers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.conf import settings 5 | from rest_framework.compat import six 6 | from rest_framework.parsers import BaseParser, ParseError 7 | from rest_framework.renderers import JSONRenderer 8 | import ujson 9 | 10 | 11 | __author__ = 'y.gavenchuk aka murminathor' 12 | __all__ = ['UJSONParser', ] 13 | 14 | 15 | class UJSONParser(BaseParser): 16 | """ 17 | Parses JSON-serialized data by ujson parser. 18 | """ 19 | 20 | media_type = 'application/json' 21 | renderer_class = JSONRenderer 22 | 23 | def parse(self, stream, media_type=None, parser_context=None): 24 | """ 25 | Parses the incoming bytestream as JSON and returns the resulting data. 26 | """ 27 | parser_context = parser_context or {} 28 | encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 29 | 30 | try: 31 | data = stream.read().decode(encoding) 32 | return ujson.loads(data) 33 | except ValueError as exc: 34 | raise ParseError('JSON parse error - %s' % six.text_type(exc)) 35 | -------------------------------------------------------------------------------- /drf_ujson/renderers.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from rest_framework.compat import six 3 | from rest_framework.renderers import BaseRenderer 4 | import ujson 5 | 6 | 7 | class UJSONRenderer(BaseRenderer): 8 | """ 9 | Renderer which serializes to JSON. 10 | Applies JSON's backslash-u character escaping for non-ascii characters. 11 | Uses the blazing-fast ujson library for serialization. 12 | """ 13 | 14 | media_type = 'application/json' 15 | format = 'json' 16 | ensure_ascii = True 17 | charset = None 18 | 19 | def render(self, data, *args, **kwargs): 20 | 21 | if data is None: 22 | return bytes() 23 | 24 | ret = ujson.dumps(data, ensure_ascii=self.ensure_ascii) 25 | 26 | # force return value to unicode 27 | if isinstance(ret, six.text_type): 28 | return bytes(ret.encode('utf-8')) 29 | return ret 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name='drf_ujson', 7 | version='1.2', 8 | description='Django Rest Framework UJSON Renderer', 9 | author='Gizmag', 10 | author_email='tech@gizmag.com', 11 | url='https://github.com/gizmag/drf-ujson-renderer', 12 | packages=find_packages(), 13 | install_requires=['django', 'ujson', 'djangorestframework'] 14 | ) 15 | -------------------------------------------------------------------------------- /test_requirements.txt: -------------------------------------------------------------------------------- 1 | nose==1.3.0 2 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from io import BytesIO 3 | 4 | from django.conf import settings 5 | import ujson 6 | 7 | settings.configure() 8 | 9 | from drf_ujson.renderers import UJSONRenderer 10 | from drf_ujson.parsers import UJSONParser 11 | 12 | 13 | class UJSONRendererTests(TestCase): 14 | def setUp(self): 15 | self.renderer = UJSONRenderer() 16 | self.data = { 17 | 'a': [1, 2, 3], 18 | 'b': True, 19 | 'c': 1.23, 20 | 'd': 'test', 21 | 'e': {'foo': 'bar'}, 22 | } 23 | 24 | def test_basic_data_structures_rendered_correctly(self): 25 | 26 | rendered = self.renderer.render(self.data) 27 | reloaded = ujson.loads(rendered) 28 | 29 | self.assertEqual(reloaded, self.data) 30 | 31 | def test_renderer_works_correctly_when_media_type_and_context_provided(self): 32 | 33 | rendered = self.renderer.render( 34 | data=self.data, 35 | media_type='application/json', 36 | renderer_context={}, 37 | ) 38 | reloaded = ujson.loads(rendered) 39 | 40 | self.assertEqual(reloaded, self.data) 41 | 42 | 43 | class UJSONParserTests(TestCase): 44 | def setUp(self): 45 | self.parser = UJSONParser() 46 | self.data = { 47 | 'a': [1, 2, 3], 48 | 'b': True, 49 | 'c': 1.23, 50 | 'd': 'test', 51 | 'e': {'foo': 'bar'}, 52 | } 53 | 54 | def test_basic_data_structures_parsed_correctly(self): 55 | 56 | dumped = ujson.dumps(self.data) 57 | parsed = self.parser.parse(BytesIO(dumped.encode('utf-8'))) 58 | 59 | self.assertEqual(parsed, self.data) 60 | 61 | def test_parser_works_correctly_when_media_type_and_context_provided(self): 62 | dumped = ujson.dumps(self.data) 63 | parsed = self.parser.parse( 64 | stream=BytesIO(dumped.encode('utf-8')), 65 | media_type='application/json', 66 | parser_context={}, 67 | ) 68 | 69 | self.assertEqual(parsed, self.data) 70 | --------------------------------------------------------------------------------