From d9d4c4353edca584b5227431d082e649f5652ed0 Mon Sep 17 00:00:00 2001 From: David Fischer Date: Wed, 26 Mar 2014 12:05:32 +0100 Subject: [PATCH 1/5] Fix deprecation warnings (replace SortedDict by OrderedDict) --- rest_framework/fields.py | 6 ++--- rest_framework/serializers.py | 33 ++++++++++++------------- rest_framework/tests/test_fields.py | 4 +-- rest_framework/tests/test_serializer.py | 6 ++--- rest_framework/utils/encoders.py | 12 ++++----- rest_framework/views.py | 4 +-- 6 files changed, 32 insertions(+), 33 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 2da895500..16035062d 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -10,6 +10,7 @@ import datetime import inspect import re import warnings +from collections import OrderedDict from decimal import Decimal, DecimalException from django import forms from django.core import validators @@ -20,7 +21,6 @@ from django.http import QueryDict from django.forms import widgets from django.utils.encoding import is_protected_type from django.utils.translation import ugettext_lazy as _ -from django.utils.datastructures import SortedDict from rest_framework import ISO_8601 from rest_framework.compat import ( timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, @@ -220,7 +220,7 @@ class Field(object): return [self.to_native(item) for item in value] elif isinstance(value, dict): # Make sure we preserve field ordering, if it exists - ret = SortedDict() + ret = OrderedDict() for key, val in value.items(): ret[key] = self.to_native(val) return ret @@ -235,7 +235,7 @@ class Field(object): return {} def metadata(self): - metadata = SortedDict() + metadata = OrderedDict() metadata['type'] = self.type_label metadata['required'] = getattr(self, 'required', False) optional_attrs = ['read_only', 'label', 'help_text', diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 87d20cfce..db1bc4fd6 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -20,9 +20,8 @@ from django.contrib.contenttypes.generic import GenericForeignKey from django.core.paginator import Page from django.db import models from django.forms import widgets -from django.utils.datastructures import SortedDict from django.core.exceptions import ObjectDoesNotExist -from rest_framework.compat import get_concrete_model, six +from rest_framework.compat import get_concrete_model, OrderedDict, six from rest_framework.settings import api_settings @@ -105,9 +104,9 @@ class DictWithMetadata(dict): return dict(self) -class SortedDictWithMetadata(SortedDict): +class OrderedDictWithMetadata(OrderedDict): """ - A sorted dict-like object, that can have additional properties attached. + An ordered dict-like object, that can have additional properties attached. """ def __getstate__(self): """ @@ -115,7 +114,7 @@ class SortedDictWithMetadata(SortedDict): Overriden to remove the metadata from the dict, since it shouldn't be pickle and may in some instances be unpickleable. """ - return SortedDict(self).__dict__ + return OrderedDict(self).__dict__ def _is_protected_type(obj): @@ -151,7 +150,7 @@ def _get_declared_fields(bases, attrs): if hasattr(base, 'base_fields'): fields = list(base.base_fields.items()) + fields - return SortedDict(fields) + return OrderedDict(fields) class SerializerMetaclass(type): @@ -179,7 +178,7 @@ class BaseSerializer(WritableField): pass _options_class = SerializerOptions - _dict_class = SortedDictWithMetadata + _dict_class = OrderedDictWithMetadata def __init__(self, instance=None, data=None, files=None, context=None, partial=False, many=None, @@ -225,7 +224,7 @@ class BaseSerializer(WritableField): This will be the set of any explicitly declared fields, plus the set of fields returned by get_default_fields(). """ - ret = SortedDict() + ret = OrderedDict() # Get the explicitly declared fields base_fields = copy.deepcopy(self.base_fields) @@ -241,7 +240,7 @@ class BaseSerializer(WritableField): # If 'fields' is specified, use those fields, in that order. if self.opts.fields: assert isinstance(self.opts.fields, (list, tuple)), '`fields` must be a list or tuple' - new = SortedDict() + new = OrderedDict() for key in self.opts.fields: new[key] = ret[key] ret = new @@ -605,7 +604,7 @@ class BaseSerializer(WritableField): Useful for things like responding to OPTIONS requests, or generating API schemas for auto-documentation. """ - return SortedDict( + return OrderedDict( [(field_name, field.metadata()) for field_name, field in six.iteritems(self.fields)] ) @@ -664,7 +663,7 @@ class ModelSerializer(Serializer): assert cls is not None, \ "Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__ opts = get_concrete_model(cls)._meta - ret = SortedDict() + ret = OrderedDict() nested = bool(self.opts.depth) # Deal with adding the primary key field @@ -759,9 +758,9 @@ class ModelSerializer(Serializer): field.read_only = True ret[accessor_name] = field - + # Ensure that 'read_only_fields' is an iterable - assert isinstance(self.opts.read_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple' + assert isinstance(self.opts.read_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple' # Add the `read_only` flag to any fields that have been specified # in the `read_only_fields` option @@ -776,10 +775,10 @@ class ModelSerializer(Serializer): "on serializer '%s'." % (field_name, self.__class__.__name__)) ret[field_name].read_only = True - + # Ensure that 'write_only_fields' is an iterable - assert isinstance(self.opts.write_only_fields, (list, tuple)), '`write_only_fields` must be a list or tuple' - + assert isinstance(self.opts.write_only_fields, (list, tuple)), '`write_only_fields` must be a list or tuple' + for field_name in self.opts.write_only_fields: assert field_name not in self.base_fields.keys(), ( "field '%s' on serializer '%s' specified in " @@ -790,7 +789,7 @@ class ModelSerializer(Serializer): "Non-existant field '%s' specified in `write_only_fields` " "on serializer '%s'." % (field_name, self.__class__.__name__)) - ret[field_name].write_only = True + ret[field_name].write_only = True return ret diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py index b04b947f2..2b2417fb6 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -10,8 +10,8 @@ from uuid import uuid4 from django.core import validators from django.db import models from django.test import TestCase -from django.utils.datastructures import SortedDict from rest_framework import serializers +from rest_framework.compat import OrderedDict from rest_framework.tests.models import RESTFrameworkModel @@ -95,7 +95,7 @@ class BasicFieldTests(TestCase): Field should preserve dictionary ordering, if it exists. See: https://github.com/tomchristie/django-rest-framework/issues/832 """ - ret = SortedDict() + ret = OrderedDict() ret['c'] = 1 ret['b'] = 1 ret['a'] = 1 diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index e688c8239..30a29ba74 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -1271,7 +1271,7 @@ class SerializerPickleTests(TestCase): def test_pickle_inner_serializer(self): """ - Test pickling a serializer whose resulting .data (a SortedDictWithMetadata) will + Test pickling a serializer whose resulting .data (a OrderedDictWithMetadata) will have unpickleable meta data--in order to make sure metadata doesn't get pulled into the pickle. See DictWithMetadata.__getstate__ """ @@ -1286,13 +1286,13 @@ class SerializerPickleTests(TestCase): Regression test for #645. """ data = serializers.DictWithMetadata({1: 1}) - self.assertEqual(data.__getstate__(), serializers.SortedDict({1: 1})) + self.assertEqual(data.__getstate__(), serializers.OrderedDict({1: 1})) def test_serializer_data_is_pickleable(self): """ Another regression test for #645. """ - data = serializers.SortedDictWithMetadata({1: 1}) + data = serializers.OrderedDictWithMetadata({1: 1}) repr(pickle.loads(pickle.dumps(data, 0))) diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index e5fa41947..e0ff53780 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -2,11 +2,11 @@ Helper classes for parsers. """ from __future__ import unicode_literals +from collections import OrderedDict from django.db.models.query import QuerySet -from django.utils.datastructures import SortedDict from django.utils.functional import Promise from rest_framework.compat import timezone, force_text -from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata +from rest_framework.serializers import DictWithMetadata, OrderedDictWithMetadata import datetime import decimal import types @@ -66,7 +66,7 @@ else: class SafeDumper(yaml.SafeDumper): """ Handles decimals as strings. - Handles SortedDicts as usual dicts, but preserves field order, rather + Handles OrderedDicts as usual dicts, but preserves field order, rather than the usual behaviour of sorting the keys. """ def represent_decimal(self, data): @@ -80,7 +80,7 @@ else: best_style = True if hasattr(mapping, 'items'): mapping = list(mapping.items()) - if not isinstance(mapping, SortedDict): + if not isinstance(mapping, OrderedDict): mapping.sort() for item_key, item_value in mapping: node_key = self.represent_data(item_key) @@ -100,11 +100,11 @@ else: SafeDumper.add_representer(decimal.Decimal, SafeDumper.represent_decimal) - SafeDumper.add_representer(SortedDict, + SafeDumper.add_representer(OrderedDict, yaml.representer.SafeRepresenter.represent_dict) SafeDumper.add_representer(DictWithMetadata, yaml.representer.SafeRepresenter.represent_dict) - SafeDumper.add_representer(SortedDictWithMetadata, + SafeDumper.add_representer(OrderedDictWithMetadata, yaml.representer.SafeRepresenter.represent_dict) SafeDumper.add_representer(types.GeneratorType, yaml.representer.SafeRepresenter.represent_list) diff --git a/rest_framework/views.py b/rest_framework/views.py index a2668f2c0..9d96b8014 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -3,9 +3,9 @@ Provides an APIView class that is the base of all views in REST framework. """ from __future__ import unicode_literals +from collections import OrderedDict from django.core.exceptions import PermissionDenied from django.http import Http404 -from django.utils.datastructures import SortedDict from django.views.decorators.csrf import csrf_exempt from rest_framework import status, exceptions from rest_framework.compat import smart_text, HttpResponseBase, View @@ -418,7 +418,7 @@ class APIView(View): # By default we can't provide any form-like information, however the # generic views override this implementation and add additional # information for POST and PUT methods, based on the serializer. - ret = SortedDict() + ret = OrderedDict() ret['name'] = self.get_view_name() ret['description'] = self.get_view_description() ret['renders'] = [renderer.media_type for renderer in self.renderer_classes] From 7c14952a45ecb450f71696e3e621dcf5ef167d9d Mon Sep 17 00:00:00 2001 From: David Fischer Date: Wed, 26 Mar 2014 14:00:28 +0100 Subject: [PATCH 2/5] Fix deprecation warning by using importlib when possible --- rest_framework/settings.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 38753c968..b4e4f1c3f 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -20,7 +20,11 @@ back to the defaults. from __future__ import unicode_literals from django.conf import settings -from django.utils import importlib +try: + # Available in Python 2.7+ + import importlib +except ImportError: + from django.utils import importlib from rest_framework import ISO_8601 from rest_framework.compat import six @@ -51,7 +55,7 @@ DEFAULTS = { 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation', - # Genric view behavior + # Generic view behavior 'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.serializers.ModelSerializer', 'DEFAULT_PAGINATION_SERIALIZER_CLASS': From 84d6893094ae4962fcf6b1bb09f739b2de026f4d Mon Sep 17 00:00:00 2001 From: David Fischer Date: Wed, 26 Mar 2014 14:11:11 +0100 Subject: [PATCH 3/5] Add ordereddict to requirements only if necessary --- setup.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 78cdb6289..cc9cc9083 100755 --- a/setup.py +++ b/setup.py @@ -51,6 +51,12 @@ if sys.argv[-1] == 'publish': print(" git push --tags") sys.exit() +requirements = [] +try: + from collections import OrderedDict +except ImportError: + # Back-port for Python < 2.7 + requirements.append('ordereddict') setup( name='djangorestframework', @@ -63,7 +69,7 @@ setup( packages=get_packages('rest_framework'), package_data=get_package_data('rest_framework'), test_suite='rest_framework.runtests.runtests.main', - install_requires=[], + install_requires=requirements, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', From 5e6f015ee3b22c03669141cdaa07a862bd898ea7 Mon Sep 17 00:00:00 2001 From: David Fischer Date: Thu, 27 Mar 2014 08:51:55 +0100 Subject: [PATCH 4/5] Handle import of OrderedDict from compat module --- rest_framework/compat.py | 5 +++++ rest_framework/fields.py | 3 +-- rest_framework/utils/encoders.py | 3 +-- rest_framework/views.py | 3 +-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index d155f5542..da276b40f 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -599,3 +599,8 @@ except ImportError: klass.__unicode__ = klass.__str__ klass.__str__ = lambda self: self.__unicode__().encode('utf-8') return klass + +if django.VERSION >= (1, 7): + from collections import OrderedDict +else: + from django.utils.datastructures import SortedDict as OrderedDict diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 16035062d..477a69826 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -10,7 +10,6 @@ import datetime import inspect import re import warnings -from collections import OrderedDict from decimal import Decimal, DecimalException from django import forms from django.core import validators @@ -24,7 +23,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import ISO_8601 from rest_framework.compat import ( timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, - force_text, is_non_str_iterable + force_text, is_non_str_iterable, OrderedDict ) from rest_framework.settings import api_settings diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index e0ff53780..26d0fa3bd 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -2,10 +2,9 @@ Helper classes for parsers. """ from __future__ import unicode_literals -from collections import OrderedDict from django.db.models.query import QuerySet from django.utils.functional import Promise -from rest_framework.compat import timezone, force_text +from rest_framework.compat import timezone, force_text, OrderedDict from rest_framework.serializers import DictWithMetadata, OrderedDictWithMetadata import datetime import decimal diff --git a/rest_framework/views.py b/rest_framework/views.py index 9d96b8014..17d7701f1 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -3,12 +3,11 @@ Provides an APIView class that is the base of all views in REST framework. """ from __future__ import unicode_literals -from collections import OrderedDict from django.core.exceptions import PermissionDenied from django.http import Http404 from django.views.decorators.csrf import csrf_exempt from rest_framework import status, exceptions -from rest_framework.compat import smart_text, HttpResponseBase, View +from rest_framework.compat import smart_text, HttpResponseBase, OrderedDict, View from rest_framework.request import Request from rest_framework.response import Response from rest_framework.settings import api_settings From ccf071f9a7e8e6c69f8853f4f37758bdc71dd872 Mon Sep 17 00:00:00 2001 From: David Fischer Date: Thu, 27 Mar 2014 13:07:47 +0100 Subject: [PATCH 5/5] Fallback to OrderedDict from ordereddict --- rest_framework/compat.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index da276b40f..7f9800202 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -601,6 +601,10 @@ except ImportError: return klass if django.VERSION >= (1, 7): - from collections import OrderedDict + try: + from collections import OrderedDict + except ImportError: + # Fall-back to OrderedDict from ordereddict module + from ordereddict import OrderedDict else: from django.utils.datastructures import SortedDict as OrderedDict