From ff86f09f74a0f60b657576abc8cf805c308a3974 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 30 Apr 2019 22:44:33 -0700 Subject: [PATCH 01/14] Remove unnecessary compatibility shims from rest_framework/compat.py (#6631) For Python 3, collections.abc.Mapping and collections.abc.MutableMapping are always available from the stdlib. --- rest_framework/compat.py | 1 - rest_framework/fields.py | 3 ++- rest_framework/serializers.py | 3 ++- rest_framework/utils/serializer_helpers.py | 2 +- tests/test_renderers.py | 3 ++- tests/test_serializer.py | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index aad44e342..3068665a8 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -3,7 +3,6 @@ The `compat` module provides support for backwards compatibility with older versions of Django/Python, and compatibility wrappers around optional packages. """ import sys -from collections.abc import Mapping, MutableMapping # noqa from django.conf import settings from django.core import validators diff --git a/rest_framework/fields.py b/rest_framework/fields.py index ad9611e05..1cffdcc2d 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -6,6 +6,7 @@ import inspect import re import uuid from collections import OrderedDict +from collections.abc import Mapping from django.conf import settings from django.core.exceptions import ObjectDoesNotExist @@ -30,7 +31,7 @@ from pytz.exceptions import InvalidTimeError from rest_framework import ISO_8601 from rest_framework.compat import ( - Mapping, MaxLengthValidator, MaxValueValidator, MinLengthValidator, + MaxLengthValidator, MaxValueValidator, MinLengthValidator, MinValueValidator, ProhibitNullCharactersValidator ) from rest_framework.exceptions import ErrorDetail, ValidationError diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 90b31e068..651ca81cf 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -14,6 +14,7 @@ import copy import inspect import traceback from collections import OrderedDict +from collections.abc import Mapping from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ValidationError as DjangoValidationError @@ -25,7 +26,7 @@ from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import Mapping, postgres_fields +from rest_framework.compat import postgres_fields from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.fields import get_error_detail, set_value from rest_framework.settings import api_settings diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index 8709352f1..80aea27d3 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -1,8 +1,8 @@ from collections import OrderedDict +from collections.abc import MutableMapping from django.utils.encoding import force_text -from rest_framework.compat import MutableMapping from rest_framework.utils import json diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 54d1cb231..bc775547d 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -1,5 +1,6 @@ import re from collections import OrderedDict +from collections.abc import MutableMapping import pytest from django.conf.urls import include, url @@ -12,7 +13,7 @@ from django.utils.safestring import SafeText from django.utils.translation import ugettext_lazy as _ from rest_framework import permissions, serializers, status -from rest_framework.compat import MutableMapping, coreapi +from rest_framework.compat import coreapi from rest_framework.decorators import action from rest_framework.renderers import ( AdminRenderer, BaseRenderer, BrowsableAPIRenderer, DocumentationRenderer, diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 8f4d9bf63..33cc0b60c 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -2,12 +2,12 @@ import inspect import pickle import re from collections import ChainMap +from collections.abc import Mapping import pytest from django.db import models from rest_framework import exceptions, fields, relations, serializers -from rest_framework.compat import Mapping from rest_framework.fields import Field from .models import ( From b4e80ac721958f8cc2931b0e2b4d022946f6ad88 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 30 Apr 2019 22:45:16 -0700 Subject: [PATCH 02/14] Remove unnecessary coerce to str() in test_decorators.py (#6637) Was added only for Python 2 compatibility. --- tests/test_decorators.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 3f24e7ef0..bd30449e5 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -202,8 +202,7 @@ class ActionDecoratorTestCase(TestCase): def method(): raise NotImplementedError - # Python 2.x compatibility - cast __name__ to str - method.__name__ = str(name) + method.__name__ = name getattr(test_action.mapping, name)(method) # ensure the mapping returns the correct method name From 734ca7ca8c2ba6f0ca83ede015652720b2a7246d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 30 Apr 2019 22:46:30 -0700 Subject: [PATCH 03/14] Remove unneeded repo() test (#6632) --- tests/test_serializer.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 33cc0b60c..e0acf368b 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -386,23 +386,6 @@ class TestIncorrectlyConfigured: ) -class TestUnicodeRepr: - def test_repr(self): - class ExampleSerializer(serializers.Serializer): - example = serializers.CharField() - - class ExampleObject: - def __init__(self): - self.example = '한국' - - def __repr__(self): - return repr(self.example) - - instance = ExampleObject() - serializer = ExampleSerializer(instance) - repr(serializer) # Should not error. - - class TestNotRequiredOutput: def test_not_required_output_for_dict(self): """ From 513a49d63b6332e373c89fb0737a0745c1f0a734 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 30 Apr 2019 22:49:17 -0700 Subject: [PATCH 04/14] Drop default 'utf-8' to .encode()/.decode() (#6633) A Python 3 cleanup that allows for less noise in the code. https://docs.python.org/3/library/stdtypes.html#bytes.decode https://docs.python.org/3/library/stdtypes.html#str.encode --- rest_framework/fields.py | 7 ++---- .../management/commands/generateschema.py | 2 +- rest_framework/parsers.py | 2 +- rest_framework/renderers.py | 23 +++++++------------ rest_framework/utils/encoders.py | 2 +- tests/authentication/test_authentication.py | 2 +- tests/browsable_api/test_browsable_api.py | 12 +++++----- .../test_browsable_nested_api.py | 2 +- tests/test_generics.py | 6 ++--- tests/test_parsers.py | 6 ++--- tests/test_renderers.py | 22 +++++++++--------- tests/test_routers.py | 10 ++++---- tests/test_templates.py | 4 ++-- 13 files changed, 44 insertions(+), 56 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 1cffdcc2d..a41934ac1 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1754,7 +1754,7 @@ class JSONField(Field): try: if self.binary or getattr(data, 'is_json_string', False): if isinstance(data, bytes): - data = data.decode('utf-8') + data = data.decode() return json.loads(data) else: json.dumps(data) @@ -1765,10 +1765,7 @@ class JSONField(Field): def to_representation(self, value): if self.binary: value = json.dumps(value) - # On python 2.x the return type for json.dumps() is underspecified. - # On python 3.x json.dumps() returns unicode strings. - if isinstance(value, str): - value = bytes(value.encode('utf-8')) + value = value.encode() return value diff --git a/rest_framework/management/commands/generateschema.py b/rest_framework/management/commands/generateschema.py index 591073ba0..40909bd04 100644 --- a/rest_framework/management/commands/generateschema.py +++ b/rest_framework/management/commands/generateschema.py @@ -29,7 +29,7 @@ class Command(BaseCommand): renderer = self.get_renderer(options['format']) output = renderer.render(schema, renderer_context={}) - self.stdout.write(output.decode('utf-8')) + self.stdout.write(output.decode()) def get_renderer(self, format): renderer_cls = { diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 5b5e3f158..a48c31631 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -202,7 +202,7 @@ class FileUploadParser(BaseParser): try: meta = parser_context['request'].META - disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) + disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode()) filename_parm = disposition[1] if 'filename*' in filename_parm: return self.get_encoded_filename(filename_parm) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index eb5da008b..623702966 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -104,18 +104,11 @@ class JSONRenderer(BaseRenderer): allow_nan=not self.strict, separators=separators ) - # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True, - # but if ensure_ascii=False, the return type is underspecified, - # and may (or may not) be unicode. - # On python 3.x json.dumps() returns unicode strings. - if isinstance(ret, str): - # We always fully escape \u2028 and \u2029 to ensure we output JSON - # that is a strict javascript subset. If bytes were returned - # by json.dumps() then we don't have these characters in any case. - # See: http://timelessrepo.com/json-isnt-a-javascript-subset - ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029') - return bytes(ret.encode('utf-8')) - return ret + # We always fully escape \u2028 and \u2029 to ensure we output JSON + # that is a strict javascript subset. + # See: http://timelessrepo.com/json-isnt-a-javascript-subset + ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029') + return ret.encode() class TemplateHTMLRenderer(BaseRenderer): @@ -574,7 +567,7 @@ class BrowsableAPIRenderer(BaseRenderer): data.pop(name, None) content = renderer.render(data, accepted, context) # Renders returns bytes, but CharField expects a str. - content = content.decode('utf-8') + content = content.decode() else: content = None @@ -1032,7 +1025,7 @@ class OpenAPIRenderer(_BaseOpenAPIRenderer): def render(self, data, media_type=None, renderer_context=None): structure = self.get_structure(data) - return yaml.dump(structure, default_flow_style=False).encode('utf-8') + return yaml.dump(structure, default_flow_style=False).encode() class JSONOpenAPIRenderer(_BaseOpenAPIRenderer): @@ -1045,4 +1038,4 @@ class JSONOpenAPIRenderer(_BaseOpenAPIRenderer): def render(self, data, media_type=None, renderer_context=None): structure = self.get_structure(data) - return json.dumps(structure, indent=4).encode('utf-8') + return json.dumps(structure, indent=4).encode() diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index dee2f942e..a7875a868 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -47,7 +47,7 @@ class JSONEncoder(json.JSONEncoder): return tuple(obj) elif isinstance(obj, bytes): # Best-effort for binary blobs. See #4187. - return obj.decode('utf-8') + return obj.decode() elif hasattr(obj, 'tolist'): # Numpy arrays and array scalars. return obj.tolist() diff --git a/tests/authentication/test_authentication.py b/tests/authentication/test_authentication.py index f7e9fcf18..927989028 100644 --- a/tests/authentication/test_authentication.py +++ b/tests/authentication/test_authentication.py @@ -183,7 +183,7 @@ class SessionAuthTests(TestCase): cf. [#1810](https://github.com/encode/django-rest-framework/pull/1810) """ response = self.csrf_client.get('/auth/login/') - content = response.content.decode('utf8') + content = response.content.decode() assert '' in content def test_post_form_session_auth_failing_csrf(self): diff --git a/tests/browsable_api/test_browsable_api.py b/tests/browsable_api/test_browsable_api.py index 81090e223..17644c2ac 100644 --- a/tests/browsable_api/test_browsable_api.py +++ b/tests/browsable_api/test_browsable_api.py @@ -24,18 +24,18 @@ class DropdownWithAuthTests(TestCase): def test_name_shown_when_logged_in(self): self.client.login(username=self.username, password=self.password) response = self.client.get('/') - content = response.content.decode('utf8') + content = response.content.decode() assert 'john' in content def test_logout_shown_when_logged_in(self): self.client.login(username=self.username, password=self.password) response = self.client.get('/') - content = response.content.decode('utf8') + content = response.content.decode() assert '>Log out<' in content def test_login_shown_when_logged_out(self): response = self.client.get('/') - content = response.content.decode('utf8') + content = response.content.decode() assert '>Log in<' in content @@ -59,16 +59,16 @@ class NoDropdownWithoutAuthTests(TestCase): def test_name_shown_when_logged_in(self): self.client.login(username=self.username, password=self.password) response = self.client.get('/') - content = response.content.decode('utf8') + content = response.content.decode() assert 'john' in content def test_dropdown_not_shown_when_logged_in(self): self.client.login(username=self.username, password=self.password) response = self.client.get('/') - content = response.content.decode('utf8') + content = response.content.decode() assert '