From 3e69ed171dd5a623ffa7f45dc3fb3373199db47a Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Thu, 2 Oct 2014 17:18:32 +0300 Subject: [PATCH] Added an optional LRU cache to the JSON encoder. --- rest_framework/compat.py | 13 +++++++++ rest_framework/settings.py | 3 +- rest_framework/utils/encoders.py | 49 +++++++++++++++++++------------- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 3993cee6d..8de0496c6 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -310,3 +310,16 @@ except ImportError: klass.__unicode__ = klass.__str__ klass.__str__ = lambda self: self.__unicode__().encode('utf-8') return klass + +try: + from functools import lru_cache +except ImportError: + try: + from backports.functools_lru_cache import lru_cache + except ImportError: + def lru_cache(maxsize=100, typed=False): # When the LRU cache decorator is not available replace with a stub. + def decorating_function(f): + def wrapper(*args, **kwargs): + return f(*args, **kwargs) + return wrapper + return decorating_function diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 1e8c27fc3..6085b8a46 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -111,7 +111,8 @@ DEFAULTS = { 'UNICODE_JSON': True, 'COMPACT_JSON': True, 'COERCE_DECIMAL_TO_STRING': True, - 'UPLOADED_FILES_USE_URL': True + 'UPLOADED_FILES_USE_URL': True, + 'ENCODER_LRU_CACHE_SIZE': 1024 } diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 486186c94..e02014ed8 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -2,16 +2,19 @@ Helper classes for parsers. """ from __future__ import unicode_literals -from django.db.models.query import QuerySet -from django.utils import six, timezone -from django.utils.datastructures import SortedDict -from django.utils.functional import Promise -from rest_framework.compat import force_text import datetime import decimal import types import json +from django.db.models.query import QuerySet +from django.utils import six, timezone +from django.utils.datastructures import SortedDict +from django.utils.functional import Promise + +from rest_framework.compat import force_text, lru_cache +from rest_framework.settings import api_settings + class JSONEncoder(json.JSONEncoder): """ @@ -19,11 +22,28 @@ class JSONEncoder(json.JSONEncoder): decimal types, generators and other basic python objects. """ def default(self, obj): - # For Date Time string spec, see ECMA 262 - # http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 if isinstance(obj, Promise): return force_text(obj) - elif isinstance(obj, datetime.datetime): + elif isinstance(obj, QuerySet): + return tuple(obj) + elif hasattr(obj, '__getitem__'): + try: + return dict(obj) + except: + pass + elif hasattr(obj, 'tolist'): + # Numpy arrays and array scalars. + return obj.tolist() + elif hasattr(obj, '__iter__'): + return tuple(item for item in obj) + + return self._default(obj) + + @lru_cache(typed=True, maxsize=api_settings.ENCODER_LRU_CACHE_SIZE) + def _default(self, obj): + # For Date Time string spec, see ECMA 262 + # http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 + if isinstance(obj, datetime.datetime): representation = obj.isoformat() if obj.microsecond: representation = representation[:23] + representation[26:] @@ -44,18 +64,7 @@ class JSONEncoder(json.JSONEncoder): elif isinstance(obj, decimal.Decimal): # Serializers will coerce decimals to strings by default. return float(obj) - elif isinstance(obj, QuerySet): - return tuple(obj) - elif hasattr(obj, 'tolist'): - # Numpy arrays and array scalars. - return obj.tolist() - elif hasattr(obj, '__getitem__'): - try: - return dict(obj) - except: - pass - elif hasattr(obj, '__iter__'): - return tuple(item for item in obj) + return super(JSONEncoder, self).default(obj)