diff --git a/.travis.yml b/.travis.yml
index 9543cb45256..04a5ff99ea7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,6 @@ dist: xenial
matrix:
fast_finish: true
include:
- - { python: "2.7", env: DJANGO=1.11 }
- { python: "3.4", env: DJANGO=1.11 }
- { python: "3.4", env: DJANGO=2.0 }
@@ -26,8 +25,8 @@ matrix:
- { python: "3.7", env: DJANGO=master }
- { python: "3.7", env: TOXENV=base }
- - { python: "2.7", env: TOXENV=lint }
- - { python: "2.7", env: TOXENV=docs }
+ - { python: "3.7", env: TOXENV=lint }
+ - { python: "3.7", env: TOXENV=docs }
- python: "3.7"
env: TOXENV=dist
diff --git a/README.md b/README.md
index 66079edf073..7d0bdd2ad4c 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements
-* Python (2.7, 3.4, 3.5, 3.6, 3.7)
+* Python (3.4, 3.5, 3.6, 3.7)
* Django (1.11, 2.0, 2.1, 2.2)
We **highly recommend** and only officially support the latest patch release of
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index ede4f15ad55..d371bb8fd70 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -629,7 +629,7 @@ Our `ColorField` class above currently does not perform any data validation.
To indicate invalid data, we should raise a `serializers.ValidationError`, like so:
def to_internal_value(self, data):
- if not isinstance(data, six.text_type):
+ if not isinstance(data, str):
msg = 'Incorrect type. Expected a string, but got %s'
raise ValidationError(msg % type(data).__name__)
@@ -653,7 +653,7 @@ The `.fail()` method is a shortcut for raising `ValidationError` that takes a me
}
def to_internal_value(self, data):
- if not isinstance(data, six.text_type):
+ if not isinstance(data, str):
self.fail('incorrect_type', input_type=type(data).__name__)
if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data):
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
index 25150d5255c..0612563e470 100644
--- a/rest_framework/authentication.py
+++ b/rest_framework/authentication.py
@@ -1,14 +1,11 @@
"""
Provides various authentication policies.
"""
-from __future__ import unicode_literals
-
import base64
import binascii
from django.contrib.auth import authenticate, get_user_model
from django.middleware.csrf import CsrfViewMiddleware
-from django.utils.six import text_type
from django.utils.translation import ugettext_lazy as _
from rest_framework import HTTP_HEADER_ENCODING, exceptions
@@ -21,7 +18,7 @@ def get_authorization_header(request):
Hide some test client ickyness where the header can be unicode.
"""
auth = request.META.get('HTTP_AUTHORIZATION', b'')
- if isinstance(auth, text_type):
+ if isinstance(auth, str):
# Work around django test client oddness
auth = auth.encode(HTTP_HEADER_ENCODING)
return auth
@@ -33,7 +30,7 @@ def _reject(self, request, reason):
return reason
-class BaseAuthentication(object):
+class BaseAuthentication:
"""
All authentication classes should extend BaseAuthentication.
"""
diff --git a/rest_framework/authtoken/management/commands/drf_create_token.py b/rest_framework/authtoken/management/commands/drf_create_token.py
index 8e06812db67..3d65392442e 100644
--- a/rest_framework/authtoken/management/commands/drf_create_token.py
+++ b/rest_framework/authtoken/management/commands/drf_create_token.py
@@ -38,8 +38,8 @@ def handle(self, *args, **options):
token = self.create_user_token(username, reset_token)
except UserModel.DoesNotExist:
raise CommandError(
- 'Cannot create the Token: user {0} does not exist'.format(
+ 'Cannot create the Token: user {} does not exist'.format(
username)
)
self.stdout.write(
- 'Generated token {0} for user {1}'.format(token.key, username))
+ 'Generated token {} for user {}'.format(token.key, username))
diff --git a/rest_framework/authtoken/migrations/0001_initial.py b/rest_framework/authtoken/migrations/0001_initial.py
index 75780fedf27..6a46ccfffe8 100644
--- a/rest_framework/authtoken/migrations/0001_initial.py
+++ b/rest_framework/authtoken/migrations/0001_initial.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
from django.conf import settings
from django.db import migrations, models
diff --git a/rest_framework/authtoken/migrations/0002_auto_20160226_1747.py b/rest_framework/authtoken/migrations/0002_auto_20160226_1747.py
index 9f7e58e2268..43119099a36 100644
--- a/rest_framework/authtoken/migrations/0002_auto_20160226_1747.py
+++ b/rest_framework/authtoken/migrations/0002_auto_20160226_1747.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
from django.conf import settings
from django.db import migrations, models
diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py
index 7e96eff93b9..0ed02c41549 100644
--- a/rest_framework/authtoken/models.py
+++ b/rest_framework/authtoken/models.py
@@ -3,11 +3,9 @@
from django.conf import settings
from django.db import models
-from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
-@python_2_unicode_compatible
class Token(models.Model):
"""
The default authorization token model.
@@ -32,7 +30,7 @@ class Meta:
def save(self, *args, **kwargs):
if not self.key:
self.key = self.generate_key()
- return super(Token, self).save(*args, **kwargs)
+ return super().save(*args, **kwargs)
def generate_key(self):
return binascii.hexlify(os.urandom(20)).decode()
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index d61ca5dbba8..aad44e34211 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -2,23 +2,13 @@
The `compat` module provides support for backwards compatibility with older
versions of Django/Python, and compatibility wrappers around optional packages.
"""
-
-from __future__ import unicode_literals
-
import sys
+from collections.abc import Mapping, MutableMapping # noqa
from django.conf import settings
from django.core import validators
-from django.utils import six
from django.views.generic import View
-try:
- # Python 3
- from collections.abc import Mapping, MutableMapping # noqa
-except ImportError:
- # Python 2.7
- from collections import Mapping, MutableMapping # noqa
-
try:
from django.urls import ( # noqa
URLPattern,
@@ -36,11 +26,6 @@
except ImportError:
ProhibitNullCharactersValidator = None
-try:
- from unittest import mock
-except ImportError:
- mock = None
-
def get_original_route(urlpattern):
"""
@@ -89,23 +74,6 @@ def make_url_resolver(regex, urlpatterns):
return URLResolver(regex, urlpatterns)
-def unicode_repr(instance):
- # Get the repr of an instance, but ensure it is a unicode string
- # on both python 3 (already the case) and 2 (not the case).
- if six.PY2:
- return repr(instance).decode('utf-8')
- return repr(instance)
-
-
-def unicode_to_repr(value):
- # Coerce a unicode string to the correct repr return type, depending on
- # the Python version. We wrap all our `__repr__` implementations with
- # this and then use unicode throughout internally.
- if six.PY2:
- return value.encode('utf-8')
- return value
-
-
def unicode_http_header(value):
# Coerce HTTP header value to unicode.
if isinstance(value, bytes):
@@ -168,15 +136,6 @@ def is_guardian_installed():
"""
django-guardian is optional and only imported if in INSTALLED_APPS.
"""
- try:
- import guardian
- except ImportError:
- guardian = None
-
- if six.PY2 and (not guardian or guardian.VERSION >= (1, 5)):
- # Guardian 1.5.0, for Django 2.2 is NOT compatible with Python 2.7.
- # Remove when dropping PY2.
- return False
return 'guardian' in settings.INSTALLED_APPS
@@ -289,17 +248,12 @@ def md_filter_add_syntax_highlight(md):
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
# See: https://bugs.python.org/issue22767
-if six.PY3:
- SHORT_SEPARATORS = (',', ':')
- LONG_SEPARATORS = (', ', ': ')
- INDENT_SEPARATORS = (',', ': ')
-else:
- SHORT_SEPARATORS = (b',', b':')
- LONG_SEPARATORS = (b', ', b': ')
- INDENT_SEPARATORS = (b',', b': ')
+SHORT_SEPARATORS = (',', ':')
+LONG_SEPARATORS = (', ', ': ')
+INDENT_SEPARATORS = (',', ': ')
-class CustomValidatorMessage(object):
+class CustomValidatorMessage:
"""
We need to avoid evaluation of `lazy` translated `message` in `django.core.validators.BaseValidator.__init__`.
https://github.com/django/django/blob/75ed5900321d170debef4ac452b8b3cf8a1c2384/django/core/validators.py#L297
@@ -309,7 +263,7 @@ class CustomValidatorMessage(object):
def __init__(self, *args, **kwargs):
self.message = kwargs.pop('message', self.message)
- super(CustomValidatorMessage, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
class MinValueValidator(CustomValidatorMessage, validators.MinValueValidator):
diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py
index 30bfcc4e53e..5d7bd14a3f0 100644
--- a/rest_framework/decorators.py
+++ b/rest_framework/decorators.py
@@ -6,13 +6,10 @@
based views, as well as the `@detail_route` and `@list_route` decorators, which are
used to annotate methods on viewsets that should be included by routers.
"""
-from __future__ import unicode_literals
-
import types
import warnings
from django.forms.utils import pretty_name
-from django.utils import six
from rest_framework import RemovedInDRF310Warning
from rest_framework.views import APIView
@@ -28,7 +25,7 @@ def api_view(http_method_names=None):
def decorator(func):
WrappedAPIView = type(
- six.PY3 and 'WrappedAPIView' or b'WrappedAPIView',
+ 'WrappedAPIView',
(APIView,),
{'__doc__': func.__doc__}
)
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index f79b1612940..8fbdfcd084b 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -4,18 +4,14 @@
In addition Django's built in 403 and 404 exceptions are handled.
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
"""
-from __future__ import unicode_literals
-
import math
from django.http import JsonResponse
-from django.utils import six
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext
from rest_framework import status
-from rest_framework.compat import unicode_to_repr
from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
@@ -64,19 +60,19 @@ def _get_full_details(detail):
}
-class ErrorDetail(six.text_type):
+class ErrorDetail(str):
"""
A string-like object that can additionally have a code.
"""
code = None
def __new__(cls, string, code=None):
- self = super(ErrorDetail, cls).__new__(cls, string)
+ self = super().__new__(cls, string)
self.code = code
return self
def __eq__(self, other):
- r = super(ErrorDetail, self).__eq__(other)
+ r = super().__eq__(other)
try:
return r and self.code == other.code
except AttributeError:
@@ -86,10 +82,10 @@ def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
- return unicode_to_repr('ErrorDetail(string=%r, code=%r)' % (
- six.text_type(self),
+ return 'ErrorDetail(string=%r, code=%r)' % (
+ str(self),
self.code,
- ))
+ )
def __hash__(self):
return hash(str(self))
@@ -113,7 +109,7 @@ def __init__(self, detail=None, code=None):
self.detail = _get_error_details(detail, code)
def __str__(self):
- return six.text_type(self.detail)
+ return str(self.detail)
def get_codes(self):
"""
@@ -196,7 +192,7 @@ class MethodNotAllowed(APIException):
def __init__(self, method, detail=None, code=None):
if detail is None:
detail = force_text(self.default_detail).format(method=method)
- super(MethodNotAllowed, self).__init__(detail, code)
+ super().__init__(detail, code)
class NotAcceptable(APIException):
@@ -206,7 +202,7 @@ class NotAcceptable(APIException):
def __init__(self, detail=None, code=None, available_renderers=None):
self.available_renderers = available_renderers
- super(NotAcceptable, self).__init__(detail, code)
+ super().__init__(detail, code)
class UnsupportedMediaType(APIException):
@@ -217,7 +213,7 @@ class UnsupportedMediaType(APIException):
def __init__(self, media_type, detail=None, code=None):
if detail is None:
detail = force_text(self.default_detail).format(media_type=media_type)
- super(UnsupportedMediaType, self).__init__(detail, code)
+ super().__init__(detail, code)
class Throttled(APIException):
@@ -238,7 +234,7 @@ def __init__(self, wait=None, detail=None, code=None):
self.extra_detail_plural.format(wait=wait),
wait))))
self.wait = wait
- super(Throttled, self).__init__(detail, code)
+ super().__init__(detail, code)
def server_error(request, *args, **kwargs):
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index c8f65db0e5e..ad9611e056e 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
import copy
import datetime
import decimal
@@ -17,7 +15,7 @@
)
from django.forms import FilePathField as DjangoFilePathField
from django.forms import ImageField as DjangoImageField
-from django.utils import six, timezone
+from django.utils import timezone
from django.utils.dateparse import (
parse_date, parse_datetime, parse_duration, parse_time
)
@@ -33,8 +31,7 @@
from rest_framework import ISO_8601
from rest_framework.compat import (
Mapping, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
- MinValueValidator, ProhibitNullCharactersValidator, unicode_repr,
- unicode_to_repr
+ MinValueValidator, ProhibitNullCharactersValidator
)
from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.settings import api_settings
@@ -51,39 +48,21 @@ class empty:
pass
-if six.PY3:
- def is_simple_callable(obj):
- """
- True if the object is a callable that takes no arguments.
- """
- if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)):
- return False
-
- sig = inspect.signature(obj)
- params = sig.parameters.values()
- return all(
- param.kind == param.VAR_POSITIONAL or
- param.kind == param.VAR_KEYWORD or
- param.default != param.empty
- for param in params
- )
-
-else:
- def is_simple_callable(obj):
- function = inspect.isfunction(obj)
- method = inspect.ismethod(obj)
-
- if not (function or method):
- return False
-
- if method:
- is_unbound = obj.im_self is None
-
- args, _, _, defaults = inspect.getargspec(obj)
+def is_simple_callable(obj):
+ """
+ True if the object is a callable that takes no arguments.
+ """
+ if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)):
+ return False
- len_args = len(args) if function or is_unbound else len(args) - 1
- len_defaults = len(defaults) if defaults else 0
- return len_args <= len_defaults
+ sig = inspect.signature(obj)
+ params = sig.parameters.values()
+ return all(
+ param.kind == param.VAR_POSITIONAL or
+ param.kind == param.VAR_KEYWORD or
+ param.default != param.empty
+ for param in params
+ )
def get_attribute(instance, attrs):
@@ -108,7 +87,7 @@ def get_attribute(instance, attrs):
# If we raised an Attribute or KeyError here it'd get treated
# as an omitted field in `Field.get_attribute()`. Instead we
# raise a ValueError to ensure the exception is not masked.
- raise ValueError('Exception raised in callable attribute "{0}"; original exception was: {1}'.format(attr, exc))
+ raise ValueError('Exception raised in callable attribute "{}"; original exception was: {}'.format(attr, exc))
return instance
@@ -185,18 +164,18 @@ def iter_options(grouped_choices, cutoff=None, cutoff_text=None):
"""
Helper function for options and option groups in templates.
"""
- class StartOptionGroup(object):
+ class StartOptionGroup:
start_option_group = True
end_option_group = False
def __init__(self, label):
self.label = label
- class EndOptionGroup(object):
+ class EndOptionGroup:
start_option_group = False
end_option_group = True
- class Option(object):
+ class Option:
start_option_group = False
end_option_group = False
@@ -251,7 +230,7 @@ def get_error_detail(exc_info):
}
-class CreateOnlyDefault(object):
+class CreateOnlyDefault:
"""
This class may be used to provide default values that are only used
for create operations, but that do not return any value for update
@@ -273,12 +252,10 @@ def __call__(self):
return self.default
def __repr__(self):
- return unicode_to_repr(
- '%s(%s)' % (self.__class__.__name__, unicode_repr(self.default))
- )
+ return '%s(%s)' % (self.__class__.__name__, repr(self.default))
-class CurrentUserDefault(object):
+class CurrentUserDefault:
def set_context(self, serializer_field):
self.user = serializer_field.context['request'].user
@@ -286,7 +263,7 @@ def __call__(self):
return self.user
def __repr__(self):
- return unicode_to_repr('%s()' % self.__class__.__name__)
+ return '%s()' % self.__class__.__name__
class SkipField(Exception):
@@ -305,7 +282,7 @@ class SkipField(Exception):
)
-class Field(object):
+class Field:
_creation_counter = 0
default_error_messages = {
@@ -618,7 +595,7 @@ def __new__(cls, *args, **kwargs):
When a field is instantiated, we store the arguments that were used,
so that we can present a helpful representation of the object.
"""
- instance = super(Field, cls).__new__(cls)
+ instance = super().__new__(cls)
instance._args = args
instance._kwargs = kwargs
return instance
@@ -647,7 +624,7 @@ def __repr__(self):
This allows us to create descriptive representations for serializer
instances that show all the declared fields on the serializer.
"""
- return unicode_to_repr(representation.field_repr(self))
+ return representation.field_repr(self)
# Boolean types...
@@ -724,7 +701,7 @@ class NullBooleanField(Field):
def __init__(self, **kwargs):
assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.'
kwargs['allow_null'] = True
- super(NullBooleanField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def to_internal_value(self, data):
try:
@@ -764,17 +741,14 @@ def __init__(self, **kwargs):
self.trim_whitespace = kwargs.pop('trim_whitespace', True)
self.max_length = kwargs.pop('max_length', None)
self.min_length = kwargs.pop('min_length', None)
- super(CharField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
if self.max_length is not None:
- message = lazy(
- self.error_messages['max_length'].format,
- six.text_type)(max_length=self.max_length)
+ message = lazy(self.error_messages['max_length'].format, str)(max_length=self.max_length)
self.validators.append(
MaxLengthValidator(self.max_length, message=message))
if self.min_length is not None:
message = lazy(
- self.error_messages['min_length'].format,
- six.text_type)(min_length=self.min_length)
+ self.error_messages['min_length'].format, str)(min_length=self.min_length)
self.validators.append(
MinLengthValidator(self.min_length, message=message))
@@ -786,23 +760,23 @@ def run_validation(self, data=empty):
# Test for the empty string here so that it does not get validated,
# and so that subclasses do not need to handle it explicitly
# inside the `to_internal_value()` method.
- if data == '' or (self.trim_whitespace and six.text_type(data).strip() == ''):
+ if data == '' or (self.trim_whitespace and str(data).strip() == ''):
if not self.allow_blank:
self.fail('blank')
return ''
- return super(CharField, self).run_validation(data)
+ return super().run_validation(data)
def to_internal_value(self, data):
# We're lenient with allowing basic numerics to be coerced into strings,
# but other types should fail. Eg. unclear if booleans should represent as `true` or `True`,
# and composites such as lists are likely user error.
- if isinstance(data, bool) or not isinstance(data, six.string_types + six.integer_types + (float,)):
+ if isinstance(data, bool) or not isinstance(data, (str, int, float,)):
self.fail('invalid')
- value = six.text_type(data)
+ value = str(data)
return value.strip() if self.trim_whitespace else value
def to_representation(self, value):
- return six.text_type(value)
+ return str(value)
class EmailField(CharField):
@@ -811,7 +785,7 @@ class EmailField(CharField):
}
def __init__(self, **kwargs):
- super(EmailField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
validator = EmailValidator(message=self.error_messages['invalid'])
self.validators.append(validator)
@@ -822,7 +796,7 @@ class RegexField(CharField):
}
def __init__(self, regex, **kwargs):
- super(RegexField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
validator = RegexValidator(regex, message=self.error_messages['invalid'])
self.validators.append(validator)
@@ -834,7 +808,7 @@ class SlugField(CharField):
}
def __init__(self, allow_unicode=False, **kwargs):
- super(SlugField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.allow_unicode = allow_unicode
if self.allow_unicode:
validator = RegexValidator(re.compile(r'^[-\w]+\Z', re.UNICODE), message=self.error_messages['invalid_unicode'])
@@ -849,7 +823,7 @@ class URLField(CharField):
}
def __init__(self, **kwargs):
- super(URLField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
validator = URLValidator(message=self.error_messages['invalid'])
self.validators.append(validator)
@@ -866,16 +840,16 @@ def __init__(self, **kwargs):
if self.uuid_format not in self.valid_formats:
raise ValueError(
'Invalid format for uuid representation. '
- 'Must be one of "{0}"'.format('", "'.join(self.valid_formats))
+ 'Must be one of "{}"'.format('", "'.join(self.valid_formats))
)
- super(UUIDField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def to_internal_value(self, data):
if not isinstance(data, uuid.UUID):
try:
- if isinstance(data, six.integer_types):
+ if isinstance(data, int):
return uuid.UUID(int=data)
- elif isinstance(data, six.string_types):
+ elif isinstance(data, str):
return uuid.UUID(hex=data)
else:
self.fail('invalid', value=data)
@@ -900,12 +874,12 @@ class IPAddressField(CharField):
def __init__(self, protocol='both', **kwargs):
self.protocol = protocol.lower()
self.unpack_ipv4 = (self.protocol == 'both')
- super(IPAddressField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
validators, error_message = ip_address_validators(protocol, self.unpack_ipv4)
self.validators.extend(validators)
def to_internal_value(self, data):
- if not isinstance(data, six.string_types):
+ if not isinstance(data, str):
self.fail('invalid', value=data)
if ':' in data:
@@ -915,7 +889,7 @@ def to_internal_value(self, data):
except DjangoValidationError:
self.fail('invalid', value=data)
- return super(IPAddressField, self).to_internal_value(data)
+ return super().to_internal_value(data)
# Number types...
@@ -933,22 +907,20 @@ class IntegerField(Field):
def __init__(self, **kwargs):
self.max_value = kwargs.pop('max_value', None)
self.min_value = kwargs.pop('min_value', None)
- super(IntegerField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
if self.max_value is not None:
message = lazy(
- self.error_messages['max_value'].format,
- six.text_type)(max_value=self.max_value)
+ self.error_messages['max_value'].format, str)(max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
if self.min_value is not None:
message = lazy(
- self.error_messages['min_value'].format,
- six.text_type)(min_value=self.min_value)
+ self.error_messages['min_value'].format, str)(min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
def to_internal_value(self, data):
- if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
+ if isinstance(data, str) and len(data) > self.MAX_STRING_LENGTH:
self.fail('max_string_length')
try:
@@ -973,23 +945,23 @@ class FloatField(Field):
def __init__(self, **kwargs):
self.max_value = kwargs.pop('max_value', None)
self.min_value = kwargs.pop('min_value', None)
- super(FloatField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
if self.max_value is not None:
message = lazy(
self.error_messages['max_value'].format,
- six.text_type)(max_value=self.max_value)
+ str)(max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
if self.min_value is not None:
message = lazy(
self.error_messages['min_value'].format,
- six.text_type)(min_value=self.min_value)
+ str)(min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
def to_internal_value(self, data):
- if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
+ if isinstance(data, str) and len(data) > self.MAX_STRING_LENGTH:
self.fail('max_string_length')
try:
@@ -1031,18 +1003,17 @@ def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=
else:
self.max_whole_digits = None
- super(DecimalField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
if self.max_value is not None:
message = lazy(
self.error_messages['max_value'].format,
- six.text_type)(max_value=self.max_value)
+ str)(max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
if self.min_value is not None:
message = lazy(
- self.error_messages['min_value'].format,
- six.text_type)(min_value=self.min_value)
+ self.error_messages['min_value'].format, str)(min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
@@ -1121,7 +1092,7 @@ def to_representation(self, value):
coerce_to_string = getattr(self, 'coerce_to_string', api_settings.COERCE_DECIMAL_TO_STRING)
if not isinstance(value, decimal.Decimal):
- value = decimal.Decimal(six.text_type(value).strip())
+ value = decimal.Decimal(str(value).strip())
quantized = self.quantize(value)
@@ -1130,7 +1101,7 @@ def to_representation(self, value):
if self.localize:
return localize_input(quantized)
- return '{0:f}'.format(quantized)
+ return '{:f}'.format(quantized)
def quantize(self, value):
"""
@@ -1167,7 +1138,7 @@ def __init__(self, format=empty, input_formats=None, default_timezone=None, *arg
self.input_formats = input_formats
if default_timezone is not None:
self.timezone = default_timezone
- super(DateTimeField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def enforce_timezone(self, value):
"""
@@ -1226,7 +1197,7 @@ def to_representation(self, value):
output_format = getattr(self, 'format', api_settings.DATETIME_FORMAT)
- if output_format is None or isinstance(value, six.string_types):
+ if output_format is None or isinstance(value, str):
return value
value = self.enforce_timezone(value)
@@ -1251,7 +1222,7 @@ def __init__(self, format=empty, input_formats=None, *args, **kwargs):
self.format = format
if input_formats is not None:
self.input_formats = input_formats
- super(DateField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def to_internal_value(self, value):
input_formats = getattr(self, 'input_formats', api_settings.DATE_INPUT_FORMATS)
@@ -1288,7 +1259,7 @@ def to_representation(self, value):
output_format = getattr(self, 'format', api_settings.DATE_FORMAT)
- if output_format is None or isinstance(value, six.string_types):
+ if output_format is None or isinstance(value, str):
return value
# Applying a `DateField` to a datetime value is almost always
@@ -1317,7 +1288,7 @@ def __init__(self, format=empty, input_formats=None, *args, **kwargs):
self.format = format
if input_formats is not None:
self.input_formats = input_formats
- super(TimeField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def to_internal_value(self, value):
input_formats = getattr(self, 'input_formats', api_settings.TIME_INPUT_FORMATS)
@@ -1351,7 +1322,7 @@ def to_representation(self, value):
output_format = getattr(self, 'format', api_settings.TIME_FORMAT)
- if output_format is None or isinstance(value, six.string_types):
+ if output_format is None or isinstance(value, str):
return value
# Applying a `TimeField` to a datetime value is almost always
@@ -1378,24 +1349,24 @@ class DurationField(Field):
def __init__(self, **kwargs):
self.max_value = kwargs.pop('max_value', None)
self.min_value = kwargs.pop('min_value', None)
- super(DurationField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
if self.max_value is not None:
message = lazy(
self.error_messages['max_value'].format,
- six.text_type)(max_value=self.max_value)
+ str)(max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
if self.min_value is not None:
message = lazy(
self.error_messages['min_value'].format,
- six.text_type)(min_value=self.min_value)
+ str)(min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
def to_internal_value(self, value):
if isinstance(value, datetime.timedelta):
return value
- parsed = parse_duration(six.text_type(value))
+ parsed = parse_duration(str(value))
if parsed is not None:
return parsed
self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]')
@@ -1420,21 +1391,21 @@ def __init__(self, choices, **kwargs):
self.allow_blank = kwargs.pop('allow_blank', False)
- super(ChoiceField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def to_internal_value(self, data):
if data == '' and self.allow_blank:
return ''
try:
- return self.choice_strings_to_values[six.text_type(data)]
+ return self.choice_strings_to_values[str(data)]
except KeyError:
self.fail('invalid_choice', input=data)
def to_representation(self, value):
if value in ('', None):
return value
- return self.choice_strings_to_values.get(six.text_type(value), value)
+ return self.choice_strings_to_values.get(str(value), value)
def iter_options(self):
"""
@@ -1457,7 +1428,7 @@ def _set_choices(self, choices):
# Allows us to deal with eg. integer choices while supporting either
# integer or string input, but still get the correct datatype out.
self.choice_strings_to_values = {
- six.text_type(key): key for key in self.choices
+ str(key): key for key in self.choices
}
choices = property(_get_choices, _set_choices)
@@ -1473,7 +1444,7 @@ class MultipleChoiceField(ChoiceField):
def __init__(self, *args, **kwargs):
self.allow_empty = kwargs.pop('allow_empty', True)
- super(MultipleChoiceField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def get_value(self, dictionary):
if self.field_name not in dictionary:
@@ -1486,7 +1457,7 @@ def get_value(self, dictionary):
return dictionary.get(self.field_name, empty)
def to_internal_value(self, data):
- if isinstance(data, six.text_type) or not hasattr(data, '__iter__'):
+ if isinstance(data, str) or not hasattr(data, '__iter__'):
self.fail('not_a_list', input_type=type(data).__name__)
if not self.allow_empty and len(data) == 0:
self.fail('empty')
@@ -1498,7 +1469,7 @@ def to_internal_value(self, data):
def to_representation(self, value):
return {
- self.choice_strings_to_values.get(six.text_type(item), item) for item in value
+ self.choice_strings_to_values.get(str(item), item) for item in value
}
@@ -1516,7 +1487,7 @@ def __init__(self, path, match=None, recursive=False, allow_files=True,
allow_folders=allow_folders, required=required
)
kwargs['choices'] = field.choices
- super(FilePathField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
# File types...
@@ -1535,7 +1506,7 @@ def __init__(self, *args, **kwargs):
self.allow_empty_file = kwargs.pop('allow_empty_file', False)
if 'use_url' in kwargs:
self.use_url = kwargs.pop('use_url')
- super(FileField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def to_internal_value(self, data):
try:
@@ -1581,13 +1552,13 @@ class ImageField(FileField):
def __init__(self, *args, **kwargs):
self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField)
- super(ImageField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def to_internal_value(self, data):
# Image validation is a bit grungy, so we'll just outright
# defer to Django's implementation so we don't need to
# consider it, or treat PIL as a test dependency.
- file_object = super(ImageField, self).to_internal_value(data)
+ file_object = super().to_internal_value(data)
django_field = self._DjangoImageField()
django_field.error_messages = self.error_messages
return django_field.clean(file_object)
@@ -1597,7 +1568,7 @@ def to_internal_value(self, data):
class _UnvalidatedField(Field):
def __init__(self, *args, **kwargs):
- super(_UnvalidatedField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.allow_blank = True
self.allow_null = True
@@ -1630,7 +1601,7 @@ def __init__(self, *args, **kwargs):
"Remove `source=` from the field declaration."
)
- super(ListField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
if self.max_length is not None:
message = self.error_messages['max_length'].format(max_length=self.max_length)
@@ -1660,7 +1631,7 @@ def to_internal_value(self, data):
"""
if html.is_html_input(data):
data = html.parse_html_list(data, default=[])
- if isinstance(data, (six.text_type, Mapping)) or not hasattr(data, '__iter__'):
+ if isinstance(data, (str, Mapping)) or not hasattr(data, '__iter__'):
self.fail('not_a_list', input_type=type(data).__name__)
if not self.allow_empty and len(data) == 0:
self.fail('empty')
@@ -1703,7 +1674,7 @@ def __init__(self, *args, **kwargs):
"Remove `source=` from the field declaration."
)
- super(DictField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
def get_value(self, dictionary):
@@ -1725,7 +1696,7 @@ def to_internal_value(self, data):
def to_representation(self, value):
return {
- six.text_type(key): self.child.to_representation(val) if val is not None else None
+ str(key): self.child.to_representation(val) if val is not None else None
for key, val in value.items()
}
@@ -1734,7 +1705,7 @@ def run_child_validation(self, data):
errors = OrderedDict()
for key, value in data.items():
- key = six.text_type(key)
+ key = str(key)
try:
result[key] = self.child.run_validation(value)
@@ -1750,7 +1721,7 @@ class HStoreField(DictField):
child = CharField(allow_blank=True, allow_null=True)
def __init__(self, *args, **kwargs):
- super(HStoreField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
assert isinstance(self.child, CharField), (
"The `child` argument must be an instance of `CharField`, "
"as the hstore extension stores values as strings."
@@ -1764,15 +1735,15 @@ class JSONField(Field):
def __init__(self, *args, **kwargs):
self.binary = kwargs.pop('binary', False)
- super(JSONField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def get_value(self, dictionary):
if html.is_html_input(dictionary) and self.field_name in dictionary:
# When HTML form input is used, mark up the input
# as being a JSON string, rather than a JSON primitive.
- class JSONString(six.text_type):
+ class JSONString(str):
def __new__(self, value):
- ret = six.text_type.__new__(self, value)
+ ret = str.__new__(self, value)
ret.is_json_string = True
return ret
return JSONString(dictionary[self.field_name])
@@ -1795,7 +1766,7 @@ def to_representation(self, value):
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, six.text_type):
+ if isinstance(value, str):
value = bytes(value.encode('utf-8'))
return value
@@ -1817,7 +1788,7 @@ class ExampleSerializer(Serializer):
def __init__(self, **kwargs):
kwargs['read_only'] = True
- super(ReadOnlyField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def to_representation(self, value):
return value
@@ -1834,7 +1805,7 @@ class HiddenField(Field):
def __init__(self, **kwargs):
assert 'default' in kwargs, 'default is a required argument.'
kwargs['write_only'] = True
- super(HiddenField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def get_value(self, dictionary):
# We always use the default value for `HiddenField`.
@@ -1864,7 +1835,7 @@ def __init__(self, method_name=None, **kwargs):
self.method_name = method_name
kwargs['source'] = '*'
kwargs['read_only'] = True
- super(SerializerMethodField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def bind(self, field_name, parent):
# In order to enforce a consistent style, we error if a redundant
@@ -1882,7 +1853,7 @@ def bind(self, field_name, parent):
if self.method_name is None:
self.method_name = default_method_name
- super(SerializerMethodField, self).bind(field_name, parent)
+ super().bind(field_name, parent)
def to_representation(self, value):
method = getattr(self.parent, self.method_name)
@@ -1905,11 +1876,10 @@ def __init__(self, model_field, **kwargs):
# The `max_length` option is supported by Django's base `Field` class,
# so we'd better support it here.
max_length = kwargs.pop('max_length', None)
- super(ModelField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
if max_length is not None:
message = lazy(
- self.error_messages['max_length'].format,
- six.text_type)(max_length=self.max_length)
+ self.error_messages['max_length'].format, str)(max_length=self.max_length)
self.validators.append(
MaxLengthValidator(self.max_length, message=message))
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
index bb1b86586cf..b77069ddc92 100644
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -2,8 +2,6 @@
Provides generic filtering backends that can be used to filter the results
returned by list views.
"""
-from __future__ import unicode_literals
-
import operator
import warnings
from functools import reduce
@@ -13,7 +11,6 @@
from django.db.models.constants import LOOKUP_SEP
from django.db.models.sql.constants import ORDER_PATTERN
from django.template import loader
-from django.utils import six
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
@@ -24,7 +21,7 @@
from rest_framework.settings import api_settings
-class BaseFilterBackend(object):
+class BaseFilterBackend:
"""
A base class from which all filter backend classes should inherit.
"""
@@ -109,7 +106,7 @@ def filter_queryset(self, request, queryset, view):
return queryset
orm_lookups = [
- self.construct_search(six.text_type(search_field))
+ self.construct_search(str(search_field))
for search_field in search_fields
]
@@ -188,7 +185,7 @@ def get_ordering(self, request, queryset, view):
def get_default_ordering(self, view):
ordering = getattr(view, 'ordering', None)
- if isinstance(ordering, six.string_types):
+ if isinstance(ordering, str):
return (ordering,)
return ordering
@@ -237,7 +234,7 @@ def get_valid_fields(self, queryset, view, context={}):
]
else:
valid_fields = [
- (item, item) if isinstance(item, six.string_types) else item
+ (item, item) if isinstance(item, str) else item
for item in valid_fields
]
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 8d0bf284a91..c39b02ab7f6 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -1,8 +1,6 @@
"""
Generic views that provide commonly needed behaviour.
"""
-from __future__ import unicode_literals
-
from django.core.exceptions import ValidationError
from django.db.models.query import QuerySet
from django.http import Http404
diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py
index 9f93244693b..42442f91cb3 100644
--- a/rest_framework/metadata.py
+++ b/rest_framework/metadata.py
@@ -6,8 +6,6 @@
Future implementations might use JSON schema or other definitions in order
to return this information in a more standardized way.
"""
-from __future__ import unicode_literals
-
from collections import OrderedDict
from django.core.exceptions import PermissionDenied
@@ -19,7 +17,7 @@
from rest_framework.utils.field_mapping import ClassLookupDict
-class BaseMetadata(object):
+class BaseMetadata:
def determine_metadata(self, request, view):
"""
Return a dictionary of metadata about the view.
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index de10d69308d..7fa8947cb9c 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -4,14 +4,12 @@
We don't bind behaviour to http method handlers yet,
which allows mixin classes to be composed in interesting ways.
"""
-from __future__ import unicode_literals
-
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
-class CreateModelMixin(object):
+class CreateModelMixin:
"""
Create a model instance.
"""
@@ -32,7 +30,7 @@ def get_success_headers(self, data):
return {}
-class ListModelMixin(object):
+class ListModelMixin:
"""
List a queryset.
"""
@@ -48,7 +46,7 @@ def list(self, request, *args, **kwargs):
return Response(serializer.data)
-class RetrieveModelMixin(object):
+class RetrieveModelMixin:
"""
Retrieve a model instance.
"""
@@ -58,7 +56,7 @@ def retrieve(self, request, *args, **kwargs):
return Response(serializer.data)
-class UpdateModelMixin(object):
+class UpdateModelMixin:
"""
Update a model instance.
"""
@@ -84,7 +82,7 @@ def partial_update(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
-class DestroyModelMixin(object):
+class DestroyModelMixin:
"""
Destroy a model instance.
"""
diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py
index ca1b59f12e2..76113a827fb 100644
--- a/rest_framework/negotiation.py
+++ b/rest_framework/negotiation.py
@@ -2,8 +2,6 @@
Content negotiation deals with selecting an appropriate renderer given the
incoming request. Typically this will be based on the request's Accept header.
"""
-from __future__ import unicode_literals
-
from django.http import Http404
from rest_framework import HTTP_HEADER_ENCODING, exceptions
@@ -13,7 +11,7 @@
)
-class BaseContentNegotiation(object):
+class BaseContentNegotiation:
def select_parser(self, request, parsers):
raise NotImplementedError('.select_parser() must be implemented')
@@ -66,7 +64,7 @@ def select_renderer(self, request, renderers, format_suffix=None):
# Accepted media type is 'application/json'
full_media_type = ';'.join(
(renderer.media_type,) +
- tuple('{0}={1}'.format(
+ tuple('{}={}'.format(
key, value.decode(HTTP_HEADER_ENCODING))
for key, value in media_type_wrapper.params.items()))
return renderer, full_media_type
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index b11d7cdf3aa..fcc78da43f8 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -1,19 +1,15 @@
-# coding: utf-8
"""
Pagination serializers determine the structure of the output that should
be used for paginated responses.
"""
-from __future__ import unicode_literals
-
from base64 import b64decode, b64encode
from collections import OrderedDict, namedtuple
+from urllib import parse
from django.core.paginator import InvalidPage
from django.core.paginator import Paginator as DjangoPaginator
from django.template import loader
-from django.utils import six
from django.utils.encoding import force_text
-from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import coreapi, coreschema
@@ -133,7 +129,7 @@ def invert(x):
PAGE_BREAK = PageLink(url=None, number=None, is_active=False, is_break=True)
-class BasePagination(object):
+class BasePagination:
display_page_controls = False
def paginate_queryset(self, queryset, request, view=None): # pragma: no cover
@@ -204,7 +200,7 @@ def paginate_queryset(self, queryset, request, view=None):
self.page = paginator.page(page_number)
except InvalidPage as exc:
msg = self.invalid_page_message.format(
- page_number=page_number, message=six.text_type(exc)
+ page_number=page_number, message=str(exc)
)
raise NotFound(msg)
@@ -716,13 +712,13 @@ def get_ordering(self, request, queryset, view):
'nearly-unique field on the model, such as "-created" or "pk".'
)
- assert isinstance(ordering, (six.string_types, list, tuple)), (
+ assert isinstance(ordering, (str, list, tuple)), (
'Invalid ordering. Expected string or tuple, but got {type}'.format(
type=type(ordering).__name__
)
)
- if isinstance(ordering, six.string_types):
+ if isinstance(ordering, str):
return (ordering,)
return tuple(ordering)
@@ -737,7 +733,7 @@ def decode_cursor(self, request):
try:
querystring = b64decode(encoded.encode('ascii')).decode('ascii')
- tokens = urlparse.parse_qs(querystring, keep_blank_values=True)
+ tokens = parse.parse_qs(querystring, keep_blank_values=True)
offset = tokens.get('o', ['0'])[0]
offset = _positive_int(offset, cutoff=self.offset_cutoff)
@@ -763,7 +759,7 @@ def encode_cursor(self, cursor):
if cursor.position is not None:
tokens['p'] = cursor.position
- querystring = urlparse.urlencode(tokens, doseq=True)
+ querystring = parse.urlencode(tokens, doseq=True)
encoded = b64encode(querystring.encode('ascii')).decode('ascii')
return replace_query_param(self.base_url, self.cursor_query_param, encoded)
@@ -773,7 +769,7 @@ def _get_position_from_instance(self, instance, ordering):
attr = instance[field_name]
else:
attr = getattr(instance, field_name)
- return six.text_type(attr)
+ return str(attr)
def get_paginated_response(self, data):
return Response(OrderedDict([
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index 35d0d1aa70e..5b5e3f15812 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -4,9 +4,8 @@
They give us a generic way of being able to handle various media types
on the request, such as form content or json encoded data.
"""
-from __future__ import unicode_literals
-
import codecs
+from urllib import parse
from django.conf import settings
from django.core.files.uploadhandler import StopFutureHandlers
@@ -15,9 +14,7 @@
from django.http.multipartparser import \
MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError, parse_header
-from django.utils import six
from django.utils.encoding import force_text
-from django.utils.six.moves.urllib import parse as urlparse
from rest_framework import renderers
from rest_framework.exceptions import ParseError
@@ -25,13 +22,13 @@
from rest_framework.utils import json
-class DataAndFiles(object):
+class DataAndFiles:
def __init__(self, data, files):
self.data = data
self.files = files
-class BaseParser(object):
+class BaseParser:
"""
All parsers should extend `BaseParser`, specifying a `media_type`
attribute, and overriding the `.parse()` method.
@@ -67,7 +64,7 @@ def parse(self, stream, media_type=None, parser_context=None):
parse_constant = json.strict_constant if self.strict else None
return json.load(decoded_stream, parse_constant=parse_constant)
except ValueError as exc:
- raise ParseError('JSON parse error - %s' % six.text_type(exc))
+ raise ParseError('JSON parse error - %s' % str(exc))
class FormParser(BaseParser):
@@ -113,7 +110,7 @@ def parse(self, stream, media_type=None, parser_context=None):
data, files = parser.parse()
return DataAndFiles(data, files)
except MultiPartParserError as exc:
- raise ParseError('Multipart form parse error - %s' % six.text_type(exc))
+ raise ParseError('Multipart form parse error - %s' % str(exc))
class FileUploadParser(BaseParser):
@@ -221,7 +218,7 @@ def get_encoded_filename(self, filename_parm):
encoded_filename = force_text(filename_parm['filename*'])
try:
charset, lang, filename = encoded_filename.split('\'', 2)
- filename = urlparse.unquote(filename)
+ filename = parse.unquote(filename)
except (ValueError, LookupError):
filename = force_text(filename_parm['filename'])
return filename
diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py
index 5d75f54badd..3a8c5806465 100644
--- a/rest_framework/permissions.py
+++ b/rest_framework/permissions.py
@@ -1,10 +1,7 @@
"""
Provides a set of pluggable permission policies.
"""
-from __future__ import unicode_literals
-
from django.http import Http404
-from django.utils import six
from rest_framework import exceptions
@@ -101,8 +98,7 @@ class BasePermissionMetaclass(OperationHolderMixin, type):
pass
-@six.add_metaclass(BasePermissionMetaclass)
-class BasePermission(object):
+class BasePermission(metaclass=BasePermissionMetaclass):
"""
A base class from which all permission classes should inherit.
"""
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 31c1e756180..76c4d700899 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -1,18 +1,12 @@
-# coding: utf-8
-from __future__ import unicode_literals
-
import sys
from collections import OrderedDict
+from urllib import parse
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.db.models import Manager
from django.db.models.query import QuerySet
from django.urls import NoReverseMatch, Resolver404, get_script_prefix, resolve
-from django.utils import six
-from django.utils.encoding import (
- python_2_unicode_compatible, smart_text, uri_to_iri
-)
-from django.utils.six.moves.urllib import parse as urlparse
+from django.utils.encoding import smart_text, uri_to_iri
from django.utils.translation import ugettext_lazy as _
from rest_framework.fields import (
@@ -46,14 +40,14 @@ class ObjectTypeError(TypeError):
"""
-class Hyperlink(six.text_type):
+class Hyperlink(str):
"""
A string like object that additionally has an associated name.
We use this for hyperlinked URLs that may render as a named link
in some contexts, or render as a plain URL in others.
"""
def __new__(self, url, obj):
- ret = six.text_type.__new__(self, url)
+ ret = str.__new__(self, url)
ret.obj = obj
return ret
@@ -65,13 +59,12 @@ def name(self):
# This ensures that we only called `__str__` lazily,
# as in some cases calling __str__ on a model instances *might*
# involve a database lookup.
- return six.text_type(self.obj)
+ return str(self.obj)
is_hyperlink = True
-@python_2_unicode_compatible
-class PKOnlyObject(object):
+class PKOnlyObject:
"""
This is a mock object, used for when we only need the pk of the object
instance, but still want to return an object with a .pk attribute,
@@ -121,14 +114,14 @@ def __init__(self, **kwargs):
)
kwargs.pop('many', None)
kwargs.pop('allow_empty', None)
- super(RelatedField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def __new__(cls, *args, **kwargs):
# We override this method in order to automagically create
# `ManyRelatedField` classes instead when `many=True` is set.
if kwargs.pop('many', False):
return cls.many_init(*args, **kwargs)
- return super(RelatedField, cls).__new__(cls, *args, **kwargs)
+ return super().__new__(cls, *args, **kwargs)
@classmethod
def many_init(cls, *args, **kwargs):
@@ -157,7 +150,7 @@ def run_validation(self, data=empty):
# We force empty strings to None values for relational fields.
if data == '':
data = None
- return super(RelatedField, self).run_validation(data)
+ return super().run_validation(data)
def get_queryset(self):
queryset = self.queryset
@@ -189,7 +182,7 @@ def get_attribute(self, instance):
pass
# Standard case, return the object instance.
- return super(RelatedField, self).get_attribute(instance)
+ return super().get_attribute(instance)
def get_choices(self, cutoff=None):
queryset = self.get_queryset()
@@ -225,7 +218,7 @@ def iter_options(self):
)
def display_value(self, instance):
- return six.text_type(instance)
+ return str(instance)
class StringRelatedField(RelatedField):
@@ -236,10 +229,10 @@ class StringRelatedField(RelatedField):
def __init__(self, **kwargs):
kwargs['read_only'] = True
- super(StringRelatedField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def to_representation(self, value):
- return six.text_type(value)
+ return str(value)
class PrimaryKeyRelatedField(RelatedField):
@@ -251,7 +244,7 @@ class PrimaryKeyRelatedField(RelatedField):
def __init__(self, **kwargs):
self.pk_field = kwargs.pop('pk_field', None)
- super(PrimaryKeyRelatedField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def use_pk_only_optimization(self):
return True
@@ -297,7 +290,7 @@ def __init__(self, view_name=None, **kwargs):
# implicit `self` argument to be passed.
self.reverse = reverse
- super(HyperlinkedRelatedField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def use_pk_only_optimization(self):
return self.lookup_field == 'pk'
@@ -317,10 +310,10 @@ def get_object(self, view_name, view_args, view_kwargs):
return queryset.get(**lookup_kwargs)
except ValueError:
exc = ObjectValueError(str(sys.exc_info()[1]))
- six.reraise(type(exc), exc, sys.exc_info()[2])
+ raise exc.with_traceback(sys.exc_info()[2])
except TypeError:
exc = ObjectTypeError(str(sys.exc_info()[1]))
- six.reraise(type(exc), exc, sys.exc_info()[2])
+ raise exc.with_traceback(sys.exc_info()[2])
def get_url(self, obj, view_name, request, format):
"""
@@ -346,7 +339,7 @@ def to_internal_value(self, data):
if http_prefix:
# If needed convert absolute URLs to relative path
- data = urlparse.urlparse(data).path
+ data = parse.urlparse(data).path
prefix = get_script_prefix()
if data.startswith(prefix):
data = '/' + data[len(prefix):]
@@ -432,7 +425,7 @@ def __init__(self, view_name=None, **kwargs):
assert view_name is not None, 'The `view_name` argument is required.'
kwargs['read_only'] = True
kwargs['source'] = '*'
- super(HyperlinkedIdentityField, self).__init__(view_name, **kwargs)
+ super().__init__(view_name, **kwargs)
def use_pk_only_optimization(self):
# We have the complete object instance already. We don't need
@@ -453,7 +446,7 @@ class SlugRelatedField(RelatedField):
def __init__(self, slug_field=None, **kwargs):
assert slug_field is not None, 'The `slug_field` argument is required.'
self.slug_field = slug_field
- super(SlugRelatedField, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def to_internal_value(self, data):
try:
@@ -502,7 +495,7 @@ def __init__(self, child_relation=None, *args, **kwargs):
self.html_cutoff_text or _(api_settings.HTML_SELECT_CUTOFF_TEXT)
)
assert child_relation is not None, '`child_relation` is a required argument.'
- super(ManyRelatedField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.child_relation.bind(field_name='', parent=self)
def get_value(self, dictionary):
@@ -518,7 +511,7 @@ def get_value(self, dictionary):
return dictionary.get(self.field_name, empty)
def to_internal_value(self, data):
- if isinstance(data, six.text_type) or not hasattr(data, '__iter__'):
+ if isinstance(data, str) or not hasattr(data, '__iter__'):
self.fail('not_a_list', input_type=type(data).__name__)
if not self.allow_empty and len(data) == 0:
self.fail('empty')
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index f043e63278f..eb5da008b30 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -6,10 +6,9 @@
REST framework also provides an HTML renderer that renders the browsable API.
"""
-from __future__ import unicode_literals
-
import base64
from collections import OrderedDict
+from urllib import parse
from django import forms
from django.conf import settings
@@ -19,9 +18,7 @@
from django.template import engines, loader
from django.test.client import encode_multipart
from django.urls import NoReverseMatch
-from django.utils import six
from django.utils.html import mark_safe
-from django.utils.six.moves.urllib import parse as urlparse
from rest_framework import VERSION, exceptions, serializers, status
from rest_framework.compat import (
@@ -40,7 +37,7 @@ def zero_as_none(value):
return None if value == 0 else value
-class BaseRenderer(object):
+class BaseRenderer:
"""
All renderers should extend this class, setting the `media_type`
and `format` attributes, and override the `.render()` method.
@@ -111,7 +108,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
# 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, six.text_type):
+ 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.
@@ -349,7 +346,7 @@ def render_field(self, field, parent_style):
# Get a clone of the field with text-only value representation.
field = field.as_form_field()
- if style.get('input_type') == 'datetime-local' and isinstance(field.value, six.text_type):
+ if style.get('input_type') == 'datetime-local' and isinstance(field.value, str):
field.value = field.value.rstrip('Z')
if 'template' in style:
@@ -791,7 +788,7 @@ def get_context(self, data, accepted_media_type, renderer_context):
"""
Render the HTML for the browsable API representation.
"""
- context = super(AdminRenderer, self).get_context(
+ context = super().get_context(
data, accepted_media_type, renderer_context
)
@@ -995,14 +992,14 @@ def get_paths(self, document):
tag = None
for name, link in document.links.items():
- path = urlparse.urlparse(link.url).path
+ path = parse.urlparse(link.url).path
method = link.action.lower()
paths.setdefault(path, {})
paths[path][method] = self.get_operation(link, name, tag=tag)
for tag, section in document.data.items():
for name, link in section.links.items():
- path = urlparse.urlparse(link.url).path
+ path = parse.urlparse(link.url).path
method = link.action.lower()
paths.setdefault(path, {})
paths[path][method] = self.get_operation(link, name, tag=tag)
diff --git a/rest_framework/request.py b/rest_framework/request.py
index a6d92e2bdcb..ec4b749c264 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -8,8 +8,6 @@
- full support of PUT method, including support for file uploads
- form overloading of HTTP method, content type and content
"""
-from __future__ import unicode_literals
-
import io
import sys
from contextlib import contextmanager
@@ -18,7 +16,6 @@
from django.http import HttpRequest, QueryDict
from django.http.multipartparser import parse_header
from django.http.request import RawPostDataException
-from django.utils import six
from django.utils.datastructures import MultiValueDict
from rest_framework import HTTP_HEADER_ENCODING, exceptions
@@ -34,7 +31,7 @@ def is_form_media_type(media_type):
base_media_type == 'multipart/form-data')
-class override_method(object):
+class override_method:
"""
A context manager that temporarily overrides the method on a request,
additionally setting the `view.request` attribute.
@@ -78,10 +75,10 @@ def wrap_attributeerrors():
except AttributeError:
info = sys.exc_info()
exc = WrappedAttributeError(str(info[1]))
- six.reraise(type(exc), exc, info[2])
+ raise exc.with_traceback(info[2])
-class Empty(object):
+class Empty:
"""
Placeholder for unset attributes.
Cannot use `None`, as that may be a valid value.
@@ -126,7 +123,7 @@ def clone_request(request, method):
return ret
-class ForcedAuthentication(object):
+class ForcedAuthentication:
"""
This authentication class is used if the test client or request factory
forcibly authenticated the request.
@@ -140,7 +137,7 @@ def authenticate(self, request):
return (self.force_user, self.force_token)
-class Request(object):
+class Request:
"""
Wrapper allowing to enhance a standard `HttpRequest` instance.
diff --git a/rest_framework/response.py b/rest_framework/response.py
index bf066325572..db79777701a 100644
--- a/rest_framework/response.py
+++ b/rest_framework/response.py
@@ -4,11 +4,9 @@
The appropriate renderer is called during Django's template response rendering.
"""
-from __future__ import unicode_literals
+from http.client import responses
from django.template.response import SimpleTemplateResponse
-from django.utils import six
-from django.utils.six.moves.http_client import responses
from rest_framework.serializers import Serializer
@@ -29,7 +27,7 @@ def __init__(self, data=None, status=None,
Setting 'renderer' and 'media_type' will typically be deferred,
For example being set automatically by the `APIView`.
"""
- super(Response, self).__init__(None, status=status)
+ super().__init__(None, status=status)
if isinstance(data, Serializer):
msg = (
@@ -45,7 +43,7 @@ def __init__(self, data=None, status=None,
self.content_type = content_type
if headers:
- for name, value in six.iteritems(headers):
+ for name, value in headers.items():
self[name] = value
@property
@@ -64,13 +62,13 @@ def rendered_content(self):
content_type = self.content_type
if content_type is None and charset is not None:
- content_type = "{0}; charset={1}".format(media_type, charset)
+ content_type = "{}; charset={}".format(media_type, charset)
elif content_type is None:
content_type = media_type
self['Content-Type'] = content_type
ret = renderer.render(self.data, accepted_media_type, context)
- if isinstance(ret, six.text_type):
+ if isinstance(ret, str):
assert charset, (
'renderer returned unicode, and did not specify '
'a charset value.'
@@ -94,7 +92,7 @@ def __getstate__(self):
"""
Remove attributes from the response that shouldn't be cached.
"""
- state = super(Response, self).__getstate__()
+ state = super().__getstate__()
for key in (
'accepted_renderer', 'renderer_context', 'resolver_match',
'client', 'request', 'json', 'wsgi_request'
diff --git a/rest_framework/reverse.py b/rest_framework/reverse.py
index e9cf737f194..55bf74af182 100644
--- a/rest_framework/reverse.py
+++ b/rest_framework/reverse.py
@@ -1,11 +1,8 @@
"""
Provide urlresolver functions that return fully qualified URLs or view names
"""
-from __future__ import unicode_literals
-
from django.urls import NoReverseMatch
from django.urls import reverse as django_reverse
-from django.utils import six
from django.utils.functional import lazy
from rest_framework.settings import api_settings
@@ -66,4 +63,4 @@ def _reverse(viewname, args=None, kwargs=None, request=None, format=None, **extr
return url
-reverse_lazy = lazy(reverse, six.text_type)
+reverse_lazy = lazy(reverse, str)
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index 1cacea18122..9334706f89c 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -13,8 +13,6 @@
urlpatterns = router.urls
"""
-from __future__ import unicode_literals
-
import itertools
import warnings
from collections import OrderedDict, namedtuple
@@ -22,7 +20,6 @@
from django.conf.urls import url
from django.core.exceptions import ImproperlyConfigured
from django.urls import NoReverseMatch
-from django.utils import six
from django.utils.deprecation import RenameMethodsBase
from rest_framework import (
@@ -39,7 +36,7 @@
DynamicRoute = namedtuple('DynamicRoute', ['url', 'name', 'detail', 'initkwargs'])
-class DynamicDetailRoute(object):
+class DynamicDetailRoute:
def __new__(cls, url, name, initkwargs):
warnings.warn(
"`DynamicDetailRoute` is deprecated and will be removed in 3.10 "
@@ -50,7 +47,7 @@ def __new__(cls, url, name, initkwargs):
return DynamicRoute(url, name, True, initkwargs)
-class DynamicListRoute(object):
+class DynamicListRoute:
def __new__(cls, url, name, initkwargs):
warnings.warn(
"`DynamicListRoute` is deprecated and will be removed in 3.10 in "
@@ -83,7 +80,7 @@ class RenameRouterMethods(RenameMethodsBase):
)
-class BaseRouter(six.with_metaclass(RenameRouterMethods)):
+class BaseRouter(metaclass=RenameRouterMethods):
def __init__(self):
self.registry = []
@@ -173,7 +170,7 @@ class SimpleRouter(BaseRouter):
def __init__(self, trailing_slash=True):
self.trailing_slash = '/' if trailing_slash else ''
- super(SimpleRouter, self).__init__()
+ super().__init__()
def get_default_basename(self, viewset):
"""
@@ -365,7 +362,7 @@ def __init__(self, *args, **kwargs):
self.root_renderers = kwargs.pop('root_renderers')
else:
self.root_renderers = list(api_settings.DEFAULT_RENDERER_CLASSES)
- super(DefaultRouter, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def get_api_root_view(self, api_urls=None):
"""
@@ -383,7 +380,7 @@ def get_urls(self):
Generate the list of URL patterns, including a default root view
for the API, and appending `.json` style format suffixes.
"""
- urls = super(DefaultRouter, self).get_urls()
+ urls = super().get_urls()
if self.include_root_view:
view = self.get_api_root_view(api_urls=urls)
diff --git a/rest_framework/schemas/generators.py b/rest_framework/schemas/generators.py
index db226a6c166..b8da446f729 100644
--- a/rest_framework/schemas/generators.py
+++ b/rest_framework/schemas/generators.py
@@ -11,7 +11,6 @@
from django.contrib.admindocs.views import simplify_regex
from django.core.exceptions import PermissionDenied
from django.http import Http404
-from django.utils import six
from rest_framework import exceptions
from rest_framework.compat import (
@@ -68,7 +67,7 @@ class LinkNode(OrderedDict):
def __init__(self):
self.links = []
self.methods_counter = Counter()
- super(LinkNode, self).__init__()
+ super().__init__()
def get_available_key(self, preferred_key):
if preferred_key not in self:
@@ -140,7 +139,7 @@ def endpoint_ordering(endpoint):
)
-class EndpointEnumerator(object):
+class EndpointEnumerator:
"""
A class to determine the available API endpoints that a project exposes.
"""
@@ -151,7 +150,7 @@ def __init__(self, patterns=None, urlconf=None):
urlconf = settings.ROOT_URLCONF
# Load the given URLconf module
- if isinstance(urlconf, six.string_types):
+ if isinstance(urlconf, str):
urls = import_module(urlconf)
else:
urls = urlconf
@@ -232,7 +231,7 @@ def get_allowed_methods(self, callback):
return [method for method in methods if method not in ('OPTIONS', 'HEAD')]
-class SchemaGenerator(object):
+class SchemaGenerator:
# Map HTTP methods onto actions.
default_mapping = {
'get': 'retrieve',
diff --git a/rest_framework/schemas/inspectors.py b/rest_framework/schemas/inspectors.py
index 85142edce42..91d8405eb7d 100644
--- a/rest_framework/schemas/inspectors.py
+++ b/rest_framework/schemas/inspectors.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
inspectors.py # Per-endpoint view introspection
@@ -7,11 +6,11 @@
import re
import warnings
from collections import OrderedDict
+from urllib import parse
from weakref import WeakKeyDictionary
from django.db import models
from django.utils.encoding import force_text, smart_text
-from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
from rest_framework import exceptions, serializers
@@ -125,7 +124,7 @@ def get_pk_description(model, model_field):
)
-class ViewInspector(object):
+class ViewInspector:
"""
Descriptor class on APIView.
@@ -207,7 +206,7 @@ def __init__(self, manual_fields=None):
* `manual_fields`: list of `coreapi.Field` instances that
will be added to auto-generated fields, overwriting on `Field.name`
"""
- super(AutoSchema, self).__init__()
+ super().__init__()
if manual_fields is None:
manual_fields = []
self._manual_fields = manual_fields
@@ -232,7 +231,7 @@ def get_link(self, path, method, base_url):
path = path[1:]
return coreapi.Link(
- url=urlparse.urljoin(base_url, path),
+ url=parse.urljoin(base_url, path),
action=method.lower(),
encoding=encoding,
fields=fields,
@@ -475,7 +474,7 @@ def __init__(self, fields, description='', encoding=None):
* `fields`: list of `coreapi.Field` instances.
* `description`: String description for view. Optional.
"""
- super(ManualSchema, self).__init__()
+ super().__init__()
assert all(isinstance(f, coreapi.Field) for f in fields), "`fields` must be a list of coreapi.Field instances"
self._fields = fields
self._description = description
@@ -487,7 +486,7 @@ def get_link(self, path, method, base_url):
path = path[1:]
return coreapi.Link(
- url=urlparse.urljoin(base_url, path),
+ url=parse.urljoin(base_url, path),
action=method.lower(),
encoding=self._encoding,
fields=self._fields,
@@ -498,7 +497,7 @@ def get_link(self, path, method, base_url):
class DefaultSchema(ViewInspector):
"""Allows overriding AutoSchema using DEFAULT_SCHEMA_CLASS setting"""
def __get__(self, instance, owner):
- result = super(DefaultSchema, self).__get__(instance, owner)
+ result = super().__get__(instance, owner)
if not isinstance(result, DefaultSchema):
return result
diff --git a/rest_framework/schemas/views.py b/rest_framework/schemas/views.py
index f5e327a9419..fa5cdbdc7a5 100644
--- a/rest_framework/schemas/views.py
+++ b/rest_framework/schemas/views.py
@@ -17,7 +17,7 @@ class SchemaView(APIView):
public = False
def __init__(self, *args, **kwargs):
- super(SchemaView, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
if self.renderer_classes is None:
self.renderer_classes = [
renderers.OpenAPIRenderer,
@@ -38,4 +38,4 @@ def handle_exception(self, exc):
self.renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
neg = self.perform_content_negotiation(self.request, force=True)
self.request.accepted_renderer, self.request.accepted_media_type = neg
- return super(SchemaView, self).handle_exception(exc)
+ return super().handle_exception(exc)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 9830edb3f01..90b31e068ff 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -10,8 +10,6 @@
2. The process of marshalling between python primitives and request and
response content is handled by parsers and renderers.
"""
-from __future__ import unicode_literals
-
import copy
import inspect
import traceback
@@ -23,11 +21,11 @@
from django.db.models import DurationField as ModelDurationField
from django.db.models.fields import Field as DjangoModelField
from django.db.models.fields import FieldDoesNotExist
-from django.utils import six, timezone
+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, unicode_to_repr
+from rest_framework.compat import Mapping, 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
@@ -115,14 +113,14 @@ def __init__(self, instance=None, data=empty, **kwargs):
self.partial = kwargs.pop('partial', False)
self._context = kwargs.pop('context', {})
kwargs.pop('many', None)
- super(BaseSerializer, self).__init__(**kwargs)
+ super().__init__(**kwargs)
def __new__(cls, *args, **kwargs):
# We override this method in order to automagically create
# `ListSerializer` classes instead when `many=True` is set.
if kwargs.pop('many', False):
return cls.many_init(*args, **kwargs)
- return super(BaseSerializer, cls).__new__(cls, *args, **kwargs)
+ return super().__new__(cls, *args, **kwargs)
@classmethod
def many_init(cls, *args, **kwargs):
@@ -315,7 +313,7 @@ def _get_declared_fields(cls, bases, attrs):
def __new__(cls, name, bases, attrs):
attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs)
- return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
+ return super().__new__(cls, name, bases, attrs)
def as_serializer_error(exc):
@@ -344,8 +342,7 @@ def as_serializer_error(exc):
}
-@six.add_metaclass(SerializerMetaclass)
-class Serializer(BaseSerializer):
+class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
default_error_messages = {
'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.')
}
@@ -466,7 +463,7 @@ def run_validators(self, value):
to_validate.update(value)
else:
to_validate = value
- super(Serializer, self).run_validators(to_validate)
+ super().run_validators(to_validate)
def to_internal_value(self, data):
"""
@@ -535,7 +532,7 @@ def validate(self, attrs):
return attrs
def __repr__(self):
- return unicode_to_repr(representation.serializer_repr(self, indent=1))
+ return representation.serializer_repr(self, indent=1)
# The following are used for accessing `BoundField` instances on the
# serializer, for the purposes of presenting a form-like API onto the
@@ -560,12 +557,12 @@ def __getitem__(self, key):
@property
def data(self):
- ret = super(Serializer, self).data
+ ret = super().data
return ReturnDict(ret, serializer=self)
@property
def errors(self):
- ret = super(Serializer, self).errors
+ ret = super().errors
if isinstance(ret, list) and len(ret) == 1 and getattr(ret[0], 'code', None) == 'null':
# Edge case. Provide a more descriptive error than
# "this field may not be null", when no data is passed.
@@ -591,11 +588,11 @@ def __init__(self, *args, **kwargs):
self.allow_empty = kwargs.pop('allow_empty', True)
assert self.child is not None, '`child` is a required argument.'
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
- super(ListSerializer, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
def bind(self, field_name, parent):
- super(ListSerializer, self).bind(field_name, parent)
+ super().bind(field_name, parent)
self.partial = self.parent.partial
def get_initial(self):
@@ -758,19 +755,19 @@ def is_valid(self, raise_exception=False):
return not bool(self._errors)
def __repr__(self):
- return unicode_to_repr(representation.list_repr(self, indent=1))
+ return representation.list_repr(self, indent=1)
# Include a backlink to the serializer class on return objects.
# Allows renderers such as HTMLFormRenderer to get the full field info.
@property
def data(self):
- ret = super(ListSerializer, self).data
+ ret = super().data
return ReturnList(ret, serializer=self)
@property
def errors(self):
- ret = super(ListSerializer, self).errors
+ ret = super().errors
if isinstance(ret, list) and len(ret) == 1 and getattr(ret[0], 'code', None) == 'null':
# Edge case. Provide a more descriptive error than
# "this field may not be null", when no data is passed.
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
index 8db9c81edac..5d92d0cb425 100644
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -18,13 +18,10 @@
REST framework settings, checking for user settings first, then falling
back to the defaults.
"""
-from __future__ import unicode_literals
-
from importlib import import_module
from django.conf import settings
from django.test.signals import setting_changed
-from django.utils import six
from rest_framework import ISO_8601
@@ -166,7 +163,7 @@ def perform_import(val, setting_name):
"""
if val is None:
return None
- elif isinstance(val, six.string_types):
+ elif isinstance(val, str):
return import_from_string(val, setting_name)
elif isinstance(val, (list, tuple)):
return [import_from_string(item, setting_name) for item in val]
@@ -187,7 +184,7 @@ def import_from_string(val, setting_name):
raise ImportError(msg)
-class APISettings(object):
+class APISettings:
"""
A settings object, that allows API settings to be accessed as properties.
For example:
diff --git a/rest_framework/status.py b/rest_framework/status.py
index 4b4561cfcd4..1489b440cf4 100644
--- a/rest_framework/status.py
+++ b/rest_framework/status.py
@@ -5,7 +5,6 @@
And RFC 6585 - https://tools.ietf.org/html/rfc6585
And RFC 4918 - https://tools.ietf.org/html/rfc4918
"""
-from __future__ import unicode_literals
def is_informational(code):
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index f48675d5eb9..56e2994ea18 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -1,12 +1,9 @@
-from __future__ import absolute_import, unicode_literals
-
import re
from collections import OrderedDict
from django import template
from django.template import loader
from django.urls import NoReverseMatch, reverse
-from django.utils import six
from django.utils.encoding import force_text, iri_to_uri
from django.utils.html import escape, format_html, smart_urlquote
from django.utils.safestring import SafeData, mark_safe
@@ -187,7 +184,7 @@ def add_class(value, css_class):
In the case of REST Framework, the filter is used to add Bootstrap-specific
classes to the forms.
"""
- html = six.text_type(value)
+ html = str(value)
match = class_re.search(html)
if match:
m = re.search(r'^%s$|^%s\s|\s%s\s|\s%s$' % (css_class, css_class,
@@ -204,7 +201,7 @@ def add_class(value, css_class):
@register.filter
def format_value(value):
if getattr(value, 'is_hyperlink', False):
- name = six.text_type(value.obj)
+ name = str(value.obj)
return mark_safe('%s' % (value, escape(name)))
if value is None or isinstance(value, bool):
return mark_safe('%s
' % {True: 'true', False: 'false', None: 'null'}[value])
@@ -219,7 +216,7 @@ def format_value(value):
template = loader.get_template('rest_framework/admin/dict_value.html')
context = {'value': value}
return template.render(context)
- elif isinstance(value, six.string_types):
+ elif isinstance(value, str):
if (
(value.startswith('http:') or value.startswith('https:')) and not
re.search(r'\s', value)
@@ -229,7 +226,7 @@ def format_value(value):
return mark_safe('{value}'.format(value=escape(value)))
elif '\n' in value:
return mark_safe('
%s' % escape(value)) - return six.text_type(value) + return str(value) @register.filter diff --git a/rest_framework/test.py b/rest_framework/test.py index edacf0066d8..852d4919e7f 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -1,9 +1,5 @@ -# -- coding: utf-8 -- - # Note that we import as `DjangoRequestFactory` and `DjangoClient` in order # to make it harder for the user to import the wrong thing without realizing. -from __future__ import unicode_literals - import io from importlib import import_module @@ -14,7 +10,6 @@ from django.test.client import Client as DjangoClient from django.test.client import ClientHandler from django.test.client import RequestFactory as DjangoRequestFactory -from django.utils import six from django.utils.encoding import force_bytes from django.utils.http import urlencode @@ -32,7 +27,7 @@ class HeaderDict(requests.packages.urllib3._collections.HTTPHeaderDict): def get_all(self, key, default): return self.getheaders(key) - class MockOriginalResponse(object): + class MockOriginalResponse: def __init__(self, headers): self.msg = HeaderDict(headers) self.closed = False @@ -109,7 +104,7 @@ def close(self): class RequestsClient(requests.Session): def __init__(self, *args, **kwargs): - super(RequestsClient, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) adapter = DjangoTestAdapter() self.mount('http://', adapter) self.mount('https://', adapter) @@ -117,7 +112,7 @@ def __init__(self, *args, **kwargs): def request(self, method, url, *args, **kwargs): if not url.startswith('http'): raise ValueError('Missing "http:" or "https:". Use a fully qualified URL, eg "http://testserver%s"' % url) - return super(RequestsClient, self).request(method, url, *args, **kwargs) + return super().request(method, url, *args, **kwargs) else: def RequestsClient(*args, **kwargs): @@ -129,7 +124,7 @@ class CoreAPIClient(coreapi.Client): def __init__(self, *args, **kwargs): self._session = RequestsClient() kwargs['transports'] = [coreapi.transports.HTTPTransport(session=self.session)] - return super(CoreAPIClient, self).__init__(*args, **kwargs) + return super().__init__(*args, **kwargs) @property def session(self): @@ -149,7 +144,7 @@ def __init__(self, enforce_csrf_checks=False, **defaults): self.renderer_classes = {} for cls in self.renderer_classes_list: self.renderer_classes[cls.format] = cls - super(APIRequestFactory, self).__init__(**defaults) + super().__init__(**defaults) def _encode_data(self, data, format=None, content_type=None): """ @@ -171,7 +166,7 @@ def _encode_data(self, data, format=None, content_type=None): format = format or self.default_format assert format in self.renderer_classes, ( - "Invalid format '{0}'. Available formats are {1}. " + "Invalid format '{}'. Available formats are {}. " "Set TEST_REQUEST_RENDERER_CLASSES to enable " "extra request formats.".format( format, @@ -184,12 +179,12 @@ def _encode_data(self, data, format=None, content_type=None): ret = renderer.render(data) # Determine the content-type header from the renderer - content_type = "{0}; charset={1}".format( + content_type = "{}; charset={}".format( renderer.media_type, renderer.charset ) # Coerce text to bytes if required. - if isinstance(ret, six.text_type): + if isinstance(ret, str): ret = bytes(ret.encode(renderer.charset)) return ret, content_type @@ -202,8 +197,7 @@ def get(self, path, data=None, **extra): # Fix to support old behavior where you have the arguments in the # url. See #1461. query_string = force_bytes(path.split('?')[1]) - if six.PY3: - query_string = query_string.decode('iso-8859-1') + query_string = query_string.decode('iso-8859-1') r['QUERY_STRING'] = query_string r.update(extra) return self.generic('GET', path, **r) @@ -234,11 +228,11 @@ def generic(self, method, path, data='', if content_type is not None: extra['CONTENT_TYPE'] = str(content_type) - return super(APIRequestFactory, self).generic( + return super().generic( method, path, data, content_type, secure, **extra) def request(self, **kwargs): - request = super(APIRequestFactory, self).request(**kwargs) + request = super().request(**kwargs) request._dont_enforce_csrf_checks = not self.enforce_csrf_checks return request @@ -252,18 +246,18 @@ class ForceAuthClientHandler(ClientHandler): def __init__(self, *args, **kwargs): self._force_user = None self._force_token = None - super(ForceAuthClientHandler, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_response(self, request): # This is the simplest place we can hook into to patch the # request object. force_authenticate(request, self._force_user, self._force_token) - return super(ForceAuthClientHandler, self).get_response(request) + return super().get_response(request) class APIClient(APIRequestFactory, DjangoClient): def __init__(self, enforce_csrf_checks=False, **defaults): - super(APIClient, self).__init__(**defaults) + super().__init__(**defaults) self.handler = ForceAuthClientHandler(enforce_csrf_checks) self._credentials = {} @@ -286,17 +280,17 @@ def force_authenticate(self, user=None, token=None): def request(self, **kwargs): # Ensure that any credentials set get added to every request. kwargs.update(self._credentials) - return super(APIClient, self).request(**kwargs) + return super().request(**kwargs) def get(self, path, data=None, follow=False, **extra): - response = super(APIClient, self).get(path, data=data, **extra) + response = super().get(path, data=data, **extra) if follow: response = self._handle_redirects(response, **extra) return response def post(self, path, data=None, format=None, content_type=None, follow=False, **extra): - response = super(APIClient, self).post( + response = super().post( path, data=data, format=format, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) @@ -304,7 +298,7 @@ def post(self, path, data=None, format=None, content_type=None, def put(self, path, data=None, format=None, content_type=None, follow=False, **extra): - response = super(APIClient, self).put( + response = super().put( path, data=data, format=format, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) @@ -312,7 +306,7 @@ def put(self, path, data=None, format=None, content_type=None, def patch(self, path, data=None, format=None, content_type=None, follow=False, **extra): - response = super(APIClient, self).patch( + response = super().patch( path, data=data, format=format, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) @@ -320,7 +314,7 @@ def patch(self, path, data=None, format=None, content_type=None, def delete(self, path, data=None, format=None, content_type=None, follow=False, **extra): - response = super(APIClient, self).delete( + response = super().delete( path, data=data, format=format, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) @@ -328,7 +322,7 @@ def delete(self, path, data=None, format=None, content_type=None, def options(self, path, data=None, format=None, content_type=None, follow=False, **extra): - response = super(APIClient, self).options( + response = super().options( path, data=data, format=format, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) @@ -342,7 +336,7 @@ def logout(self): self.handler._force_token = None if self.session: - super(APIClient, self).logout() + super().logout() class APITransactionTestCase(testcases.TransactionTestCase): @@ -389,11 +383,11 @@ def setUpClass(cls): cls._module.urlpatterns = cls.urlpatterns cls._override.enable() - super(URLPatternsTestCase, cls).setUpClass() + super().setUpClass() @classmethod def tearDownClass(cls): - super(URLPatternsTestCase, cls).tearDownClass() + super().tearDownClass() cls._override.disable() if hasattr(cls, '_module_urlpatterns'): diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 834ced148e7..0ba2ba66b1e 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -1,8 +1,6 @@ """ Provides various throttling policies. """ -from __future__ import unicode_literals - import time from django.core.cache import cache as default_cache @@ -11,7 +9,7 @@ from rest_framework.settings import api_settings -class BaseThrottle(object): +class BaseThrottle: """ Rate throttling of requests. """ @@ -232,7 +230,7 @@ def allow_request(self, request, view): self.num_requests, self.duration = self.parse_rate(self.rate) # We can now proceed as normal. - return super(ScopedRateThrottle, self).allow_request(request, view) + return super().allow_request(request, view) def get_cache_key(self, request, view): """ diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index ab3a74978f9..831d344ddca 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import include, url from rest_framework.compat import ( diff --git a/rest_framework/urls.py b/rest_framework/urls.py index 0e4c2661bd8..482a0a36421 100644 --- a/rest_framework/urls.py +++ b/rest_framework/urls.py @@ -11,8 +11,6 @@ You should make sure your authentication settings include `SessionAuthentication`. """ -from __future__ import unicode_literals - from django.conf.urls import url from django.contrib.auth import views diff --git a/rest_framework/utils/breadcrumbs.py b/rest_framework/utils/breadcrumbs.py index e0374ffd00b..54990e9f6cd 100644 --- a/rest_framework/utils/breadcrumbs.py +++ b/rest_framework/utils/breadcrumbs.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.urls import get_script_prefix, resolve diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index d8f4aeb4ebe..dee2f942e8d 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -1,15 +1,13 @@ """ Helper classes for parsers. """ -from __future__ import absolute_import, unicode_literals - import datetime import decimal import json # noqa import uuid from django.db.models.query import QuerySet -from django.utils import six, timezone +from django.utils import timezone from django.utils.encoding import force_text from django.utils.functional import Promise @@ -39,12 +37,12 @@ def default(self, obj): representation = obj.isoformat() return representation elif isinstance(obj, datetime.timedelta): - return six.text_type(obj.total_seconds()) + return str(obj.total_seconds()) elif isinstance(obj, decimal.Decimal): # Serializers will coerce decimals to strings by default. return float(obj) elif isinstance(obj, uuid.UUID): - return six.text_type(obj) + return str(obj) elif isinstance(obj, QuerySet): return tuple(obj) elif isinstance(obj, bytes): @@ -65,4 +63,4 @@ def default(self, obj): pass elif hasattr(obj, '__iter__'): return tuple(item for item in obj) - return super(JSONEncoder, self).default(obj) + return super().default(obj) diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index 927d08ff25f..1281ee16724 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -16,7 +16,7 @@ ) -class ClassLookupDict(object): +class ClassLookupDict: """ Takes a dictionary with classes as keys. Lookups against this object will traverses the object's inheritance diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index aa805f14e35..4e003f6140a 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -1,8 +1,6 @@ """ Utility functions to return a formatted name and description for a given view. """ -from __future__ import unicode_literals - import re from django.utils.encoding import force_text diff --git a/rest_framework/utils/json.py b/rest_framework/utils/json.py index cb557238015..1c1e69bf169 100644 --- a/rest_framework/utils/json.py +++ b/rest_framework/utils/json.py @@ -5,9 +5,6 @@ spec-compliant encoding/decoding. Support for non-standard features should be handled by users at the renderer and parser layer. """ - -from __future__ import absolute_import - import functools import json # noqa diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py index f4acf4807ee..40bdf261534 100644 --- a/rest_framework/utils/mediatypes.py +++ b/rest_framework/utils/mediatypes.py @@ -3,10 +3,7 @@ See https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 """ -from __future__ import unicode_literals - from django.http.multipartparser import parse_header -from django.utils.encoding import python_2_unicode_compatible from rest_framework import HTTP_HEADER_ENCODING @@ -46,8 +43,7 @@ def order_by_precedence(media_type_lst): return [media_types for media_types in ret if media_types] -@python_2_unicode_compatible -class _MediaType(object): +class _MediaType: def __init__(self, media_type_str): self.orig = '' if (media_type_str is None) else media_type_str self.full_type, self.params = parse_header(self.orig.encode(HTTP_HEADER_ENCODING)) diff --git a/rest_framework/utils/representation.py b/rest_framework/utils/representation.py index deeaf1f63fe..ebead5d759f 100644 --- a/rest_framework/utils/representation.py +++ b/rest_framework/utils/representation.py @@ -2,16 +2,12 @@ Helper functions for creating user-friendly representations of serializer classes and serializer fields. """ -from __future__ import unicode_literals - import re from django.db import models from django.utils.encoding import force_text from django.utils.functional import Promise -from rest_framework.compat import unicode_repr - def manager_repr(value): model = value.model @@ -34,7 +30,7 @@ def smart_repr(value): if isinstance(value, Promise) and value._delegate_text: value = force_text(value) - value = unicode_repr(value) + value = repr(value) # Representations like u'help text' # should simply be presented as 'help text' diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index c24e51d091f..8709352f178 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -1,10 +1,8 @@ -from __future__ import unicode_literals - from collections import OrderedDict from django.utils.encoding import force_text -from rest_framework.compat import MutableMapping, unicode_to_repr +from rest_framework.compat import MutableMapping from rest_framework.utils import json @@ -17,7 +15,7 @@ class ReturnDict(OrderedDict): def __init__(self, *args, **kwargs): self.serializer = kwargs.pop('serializer') - super(ReturnDict, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def copy(self): return ReturnDict(self, serializer=self.serializer) @@ -40,7 +38,7 @@ class ReturnList(list): def __init__(self, *args, **kwargs): self.serializer = kwargs.pop('serializer') - super(ReturnList, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def __repr__(self): return list.__repr__(self) @@ -51,7 +49,7 @@ def __reduce__(self): return (list, (list(self),)) -class BoundField(object): +class BoundField: """ A field object that also includes `.value` and `.error` properties. Returned when iterating over a serializer instance, @@ -73,9 +71,9 @@ def _proxy_class(self): return self._field.__class__ def __repr__(self): - return unicode_to_repr('<%s value=%s errors=%s>' % ( + return '<%s value=%s errors=%s>' % ( self.__class__.__name__, self.value, self.errors - )) + ) def as_form_field(self): value = '' if (self.value is None or self.value is False) else self.value @@ -103,9 +101,9 @@ class NestedBoundField(BoundField): """ def __init__(self, field, value, errors, prefix=''): - if value is None or value is '': + if value is None or value == '': value = {} - super(NestedBoundField, self).__init__(field, value, errors, prefix) + super().__init__(field, value, errors, prefix) def __iter__(self): for field in self.fields.values(): diff --git a/rest_framework/utils/urls.py b/rest_framework/utils/urls.py index 3766928d42f..3534e5f49a4 100644 --- a/rest_framework/utils/urls.py +++ b/rest_framework/utils/urls.py @@ -1,5 +1,6 @@ +from urllib import parse + from django.utils.encoding import force_str -from django.utils.six.moves.urllib import parse as urlparse def replace_query_param(url, key, val): @@ -7,11 +8,11 @@ def replace_query_param(url, key, val): Given a URL and a key/val pair, set or replace an item in the query parameters of the URL, and return the new URL. """ - (scheme, netloc, path, query, fragment) = urlparse.urlsplit(force_str(url)) - query_dict = urlparse.parse_qs(query, keep_blank_values=True) + (scheme, netloc, path, query, fragment) = parse.urlsplit(force_str(url)) + query_dict = parse.parse_qs(query, keep_blank_values=True) query_dict[force_str(key)] = [force_str(val)] - query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True) - return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) + query = parse.urlencode(sorted(list(query_dict.items())), doseq=True) + return parse.urlunsplit((scheme, netloc, path, query, fragment)) def remove_query_param(url, key): @@ -19,8 +20,8 @@ def remove_query_param(url, key): Given a URL and a key/val pair, remove an item in the query parameters of the URL, and return the new URL. """ - (scheme, netloc, path, query, fragment) = urlparse.urlsplit(force_str(url)) - query_dict = urlparse.parse_qs(query, keep_blank_values=True) + (scheme, netloc, path, query, fragment) = parse.urlsplit(force_str(url)) + query_dict = parse.parse_qs(query, keep_blank_values=True) query_dict.pop(key, None) - query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True) - return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) + query = parse.urlencode(sorted(list(query_dict.items())), doseq=True) + return parse.urlunsplit((scheme, netloc, path, query, fragment)) diff --git a/rest_framework/validators.py b/rest_framework/validators.py index 2ea3e5ac155..a5222fbc666 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -6,12 +6,9 @@ object creation, and makes it possible to switch between using the implicit `ModelSerializer` class and an equivalent explicit `Serializer` class. """ -from __future__ import unicode_literals - from django.db import DataError from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import unicode_to_repr from rest_framework.exceptions import ValidationError from rest_framework.utils.representation import smart_repr @@ -33,7 +30,7 @@ def qs_filter(queryset, **kwargs): return queryset.none() -class UniqueValidator(object): +class UniqueValidator: """ Validator that corresponds to `unique=True` on a model field. @@ -82,13 +79,13 @@ def __call__(self, value): raise ValidationError(self.message, code='unique') def __repr__(self): - return unicode_to_repr('<%s(queryset=%s)>' % ( + return '<%s(queryset=%s)>' % ( self.__class__.__name__, smart_repr(self.queryset) - )) + ) -class UniqueTogetherValidator(object): +class UniqueTogetherValidator: """ Validator that corresponds to `unique_together = (...)` on a model class. @@ -170,14 +167,14 @@ def __call__(self, attrs): raise ValidationError(message, code='unique') def __repr__(self): - return unicode_to_repr('<%s(queryset=%s, fields=%s)>' % ( + return '<%s(queryset=%s, fields=%s)>' % ( self.__class__.__name__, smart_repr(self.queryset), smart_repr(self.fields) - )) + ) -class BaseUniqueForValidator(object): +class BaseUniqueForValidator: message = None missing_message = _('This field is required.') @@ -236,12 +233,12 @@ def __call__(self, attrs): }, code='unique') def __repr__(self): - return unicode_to_repr('<%s(queryset=%s, field=%s, date_field=%s)>' % ( + return '<%s(queryset=%s, field=%s, date_field=%s)>' % ( self.__class__.__name__, smart_repr(self.queryset), smart_repr(self.field), smart_repr(self.date_field) - )) + ) class UniqueForDateValidator(BaseUniqueForValidator): diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py index 206ff6c2ec2..0631a75c97e 100644 --- a/rest_framework/versioning.py +++ b/rest_framework/versioning.py @@ -1,6 +1,3 @@ -# coding: utf-8 -from __future__ import unicode_literals - import re from django.utils.translation import ugettext_lazy as _ @@ -13,7 +10,7 @@ from rest_framework.utils.mediatypes import _MediaType -class BaseVersioning(object): +class BaseVersioning: default_version = api_settings.DEFAULT_VERSION allowed_versions = api_settings.ALLOWED_VERSIONS version_param = api_settings.VERSION_PARAM @@ -87,7 +84,7 @@ def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, * kwargs = {} if (kwargs is None) else kwargs kwargs[self.version_param] = request.version - return super(URLPathVersioning, self).reverse( + return super().reverse( viewname, args, kwargs, request, format, **extra ) @@ -133,7 +130,7 @@ def determine_version(self, request, *args, **kwargs): def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): if request.version is not None: viewname = self.get_versioned_viewname(viewname, request) - return super(NamespaceVersioning, self).reverse( + return super().reverse( viewname, args, kwargs, request, format, **extra ) @@ -179,7 +176,7 @@ def determine_version(self, request, *args, **kwargs): return version def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): - url = super(QueryParameterVersioning, self).reverse( + url = super().reverse( viewname, args, kwargs, request, format, **extra ) if request.version is not None: diff --git a/rest_framework/views.py b/rest_framework/views.py index 9d5d959e9d7..6ef7021d4a3 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -1,8 +1,6 @@ """ Provides an APIView class that is the base of all views in REST framework. """ -from __future__ import unicode_literals - from django.conf import settings from django.core.exceptions import PermissionDenied from django.db import connection, models, transaction @@ -137,7 +135,7 @@ def force_evaluation(): ) cls.queryset._fetch_all = force_evaluation - view = super(APIView, cls).as_view(**initkwargs) + view = super().as_view(**initkwargs) view.cls = cls view.initkwargs = initkwargs diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index 7146828d2f8..ad5633854f9 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -16,8 +16,6 @@ router.register(r'users', UserViewSet, 'user') urlpatterns = router.urls """ -from __future__ import unicode_literals - from collections import OrderedDict from functools import update_wrapper from inspect import getmembers @@ -34,7 +32,7 @@ def _is_extra_action(attr): return hasattr(attr, 'mapping') -class ViewSetMixin(object): +class ViewSetMixin: """ This is the magic. @@ -134,7 +132,7 @@ def initialize_request(self, request, *args, **kwargs): """ Set the `.action` attribute on the view, depending on the request method. """ - request = super(ViewSetMixin, self).initialize_request(request, *args, **kwargs) + request = super().initialize_request(request, *args, **kwargs) method = request.method.lower() if method == 'options': # This is a special case as we always provide handling for the diff --git a/runtests.py b/runtests.py index 16b47ce2a4f..a32dde96c5f 100755 --- a/runtests.py +++ b/runtests.py @@ -1,6 +1,4 @@ -#! /usr/bin/env python -from __future__ import print_function - +#! /usr/bin/env python3 import subprocess import sys diff --git a/setup.cfg b/setup.cfg index c95134600d2..b4dee68044b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[bdist_wheel] -universal = 1 - [metadata] license_file = LICENSE.md diff --git a/setup.py b/setup.py index cb850a3aee1..632c7dfd3b3 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 import os import re import shutil @@ -8,6 +7,34 @@ from setuptools import find_packages, setup +CURRENT_PYTHON = sys.version_info[:2] +REQUIRED_PYTHON = (3, 4) + +# This check and everything above must remain compatible with Python 2.7. +if CURRENT_PYTHON < REQUIRED_PYTHON: + sys.stderr.write(""" +========================== +Unsupported Python version +========================== + +This version of Django REST Framework requires Python {}.{}, but you're trying +to install it on Python {}.{}. + +This may be because you are using a version of pip that doesn't +understand the python_requires classifier. Make sure you +have pip >= 9.0 and setuptools >= 24.2, then try again: + + $ python -m pip install --upgrade pip setuptools + $ python -m pip install djangorestframework + +This will install the latest version of Django REST Framework which works on +your version of Python. If you can't upgrade your pip (or Python), request +an older version of Django REST Framework: + + $ python -m pip install "django<3.10" +""".format(*(REQUIRED_PYTHON + CURRENT_PYTHON))) + sys.exit(1) + def read(f): return open(f, 'r', encoding='utf-8').read() @@ -52,7 +79,7 @@ def get_version(package): packages=find_packages(exclude=['tests*']), include_package_data=True, install_requires=[], - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + python_requires=">=3.4", zip_safe=False, classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -66,13 +93,12 @@ def get_version(package): 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3 :: Only', 'Topic :: Internet :: WWW/HTTP', ] ) diff --git a/tests/authentication/migrations/0001_initial.py b/tests/authentication/migrations/0001_initial.py index cfc88724006..548b3576bb7 100644 --- a/tests/authentication/migrations/0001_initial.py +++ b/tests/authentication/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models diff --git a/tests/authentication/models.py b/tests/authentication/models.py index b8d1fd5a6b3..1a721de4d3f 100644 --- a/tests/authentication/models.py +++ b/tests/authentication/models.py @@ -1,6 +1,3 @@ -# coding: utf-8 -from __future__ import unicode_literals - from django.conf import settings from django.db import models diff --git a/tests/authentication/test_authentication.py b/tests/authentication/test_authentication.py index 79377354244..f7e9fcf18a9 100644 --- a/tests/authentication/test_authentication.py +++ b/tests/authentication/test_authentication.py @@ -1,7 +1,3 @@ -# coding: utf-8 - -from __future__ import unicode_literals - import base64 import pytest @@ -10,7 +6,6 @@ from django.contrib.auth.models import User from django.http import HttpResponse from django.test import TestCase, override_settings -from django.utils import six from rest_framework import ( HTTP_HEADER_ENCODING, exceptions, permissions, renderers, status @@ -253,7 +248,7 @@ def test_post_form_session_auth_failing(self): assert response.status_code == status.HTTP_403_FORBIDDEN -class BaseTokenAuthTests(object): +class BaseTokenAuthTests: """Token authentication""" model = None path = None @@ -381,7 +376,7 @@ def test_generate_key_returns_string(self): """Ensure generate_key returns a string""" token = self.model() key = token.generate_key() - assert isinstance(key, six.string_types) + assert isinstance(key, str) def test_token_login_json(self): """Ensure token login view using JSON POST works.""" @@ -534,7 +529,7 @@ def test_basic_authentication_raises_error_if_user_not_found(self): def test_basic_authentication_raises_error_if_user_not_active(self): from rest_framework import authentication - class MockUser(object): + class MockUser: is_active = False old_authenticate = authentication.authenticate authentication.authenticate = lambda **kwargs: MockUser() diff --git a/tests/browsable_api/auth_urls.py b/tests/browsable_api/auth_urls.py index 0e937971720..7530c5e4081 100644 --- a/tests/browsable_api/auth_urls.py +++ b/tests/browsable_api/auth_urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import include, url from .views import MockView diff --git a/tests/browsable_api/no_auth_urls.py b/tests/browsable_api/no_auth_urls.py index 5fc95c7276d..348bfe1c0c1 100644 --- a/tests/browsable_api/no_auth_urls.py +++ b/tests/browsable_api/no_auth_urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from .views import MockView diff --git a/tests/browsable_api/test_browsable_api.py b/tests/browsable_api/test_browsable_api.py index 684d7ae143f..81090e2235f 100644 --- a/tests/browsable_api/test_browsable_api.py +++ b/tests/browsable_api/test_browsable_api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.auth.models import User from django.test import TestCase, override_settings diff --git a/tests/browsable_api/test_browsable_nested_api.py b/tests/browsable_api/test_browsable_nested_api.py index 8f38b3c4e56..3fef74023dd 100644 --- a/tests/browsable_api/test_browsable_nested_api.py +++ b/tests/browsable_api/test_browsable_nested_api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from django.test import TestCase from django.test.utils import override_settings diff --git a/tests/browsable_api/views.py b/tests/browsable_api/views.py index 03758f10b36..e1cf13a1ec8 100644 --- a/tests/browsable_api/views.py +++ b/tests/browsable_api/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import authentication, renderers from rest_framework.response import Response from rest_framework.views import APIView diff --git a/tests/generic_relations/models.py b/tests/generic_relations/models.py index 55bc243cbdb..20df3e4a2df 100644 --- a/tests/generic_relations/models.py +++ b/tests/generic_relations/models.py @@ -1,14 +1,10 @@ -from __future__ import unicode_literals - from django.contrib.contenttypes.fields import ( GenericForeignKey, GenericRelation ) from django.contrib.contenttypes.models import ContentType from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Tag(models.Model): """ Tags have a descriptive slug, and are attached to an arbitrary object. @@ -22,7 +18,6 @@ def __str__(self): return self.tag -@python_2_unicode_compatible class Bookmark(models.Model): """ A URL bookmark that may have multiple tags attached. @@ -34,7 +29,6 @@ def __str__(self): return 'Bookmark: %s' % self.url -@python_2_unicode_compatible class Note(models.Model): """ A textual note that may have multiple tags attached. diff --git a/tests/generic_relations/test_generic_relations.py b/tests/generic_relations/test_generic_relations.py index c8de332e1d9..33f8ea1d012 100644 --- a/tests/generic_relations/test_generic_relations.py +++ b/tests/generic_relations/test_generic_relations.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.test import TestCase from rest_framework import serializers diff --git a/tests/models.py b/tests/models.py index 17bf23cda44..f389a51a92a 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import uuid from django.db import models diff --git a/tests/test_api_client.py b/tests/test_api_client.py index e4354ec6037..74a3579e2f2 100644 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import os import tempfile import unittest diff --git a/tests/test_atomic_requests.py b/tests/test_atomic_requests.py index bddd480a5a7..de04d2c069c 100644 --- a/tests/test_atomic_requests.py +++ b/tests/test_atomic_requests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import unittest from django.conf.urls import url @@ -38,7 +36,7 @@ def post(self, request, *args, **kwargs): class NonAtomicAPIExceptionView(APIView): @transaction.non_atomic_requests def dispatch(self, *args, **kwargs): - return super(NonAtomicAPIExceptionView, self).dispatch(*args, **kwargs) + return super().dispatch(*args, **kwargs) def get(self, request, *args, **kwargs): BasicModel.objects.all() diff --git a/tests/test_authtoken.py b/tests/test_authtoken.py index c8957f97853..036e317efd4 100644 --- a/tests/test_authtoken.py +++ b/tests/test_authtoken.py @@ -1,9 +1,10 @@ +from io import StringIO + import pytest from django.contrib.admin import site from django.contrib.auth.models import User from django.core.management import CommandError, call_command from django.test import TestCase -from django.utils.six import StringIO from rest_framework.authtoken.admin import TokenAdmin from rest_framework.authtoken.management.commands.drf_create_token import \ diff --git a/tests/test_bound_fields.py b/tests/test_bound_fields.py index e588ae62399..dc5ab542ff3 100644 --- a/tests/test_bound_fields.py +++ b/tests/test_bound_fields.py @@ -28,7 +28,7 @@ class ExampleSerializer(serializers.Serializer): assert serializer['text'].value == 'abc' assert serializer['text'].errors is None assert serializer['text'].name == 'text' - assert serializer['amount'].value is 123 + assert serializer['amount'].value == 123 assert serializer['amount'].errors is None assert serializer['amount'].name == 'amount' @@ -43,7 +43,7 @@ class ExampleSerializer(serializers.Serializer): assert serializer['text'].value == 'x' * 1000 assert serializer['text'].errors == ['Ensure this field has no more than 100 characters.'] assert serializer['text'].name == 'text' - assert serializer['amount'].value is 123 + assert serializer['amount'].value == 123 assert serializer['amount'].errors is None assert serializer['amount'].name == 'amount' diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 13dd41ff3aa..3f24e7ef031 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import pytest from django.test import TestCase diff --git a/tests/test_description.py b/tests/test_description.py index 702e56332fa..ae00fe4a97e 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -1,9 +1,4 @@ -# -- coding: utf-8 -- - -from __future__ import unicode_literals - from django.test import TestCase -from django.utils.encoding import python_2_unicode_compatible from rest_framework.compat import apply_markdown from rest_framework.utils.formatting import dedent @@ -157,8 +152,8 @@ class that can be converted to a string. """ # use a mock object instead of gettext_lazy to ensure that we can't end # up with a test case string in our l10n catalog - @python_2_unicode_compatible - class MockLazyStr(object): + + class MockLazyStr: def __init__(self, string): self.s = string diff --git a/tests/test_encoders.py b/tests/test_encoders.py index 12eca8105d6..c66954b8075 100644 --- a/tests/test_encoders.py +++ b/tests/test_encoders.py @@ -10,7 +10,7 @@ from rest_framework.utils.encoders import JSONEncoder -class MockList(object): +class MockList: def tolist(self): return [1, 2, 3] diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index ce0ed8514f1..13b1b47571c 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import RequestFactory, TestCase -from django.utils import six, translation +from django.utils import translation from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import ( @@ -46,12 +43,12 @@ def test_get_full_details_with_throttling(self): exception = Throttled(wait=2) assert exception.get_full_details() == { - 'message': 'Request was throttled. Expected available in {} seconds.'.format(2 if six.PY3 else 2.), + 'message': 'Request was throttled. Expected available in {} seconds.'.format(2), 'code': 'throttled'} exception = Throttled(wait=2, detail='Slow down!') assert exception.get_full_details() == { - 'message': 'Slow down! Expected available in {} seconds.'.format(2 if six.PY3 else 2.), + 'message': 'Slow down! Expected available in {} seconds.'.format(2), 'code': 'throttled'} @@ -92,7 +89,7 @@ class TranslationTests(TestCase): def test_message(self): # this test largely acts as a sanity test to ensure the translation files are present. self.assertEqual(_('A server error occurred.'), 'Une erreur du serveur est survenue.') - self.assertEqual(six.text_type(APIException()), 'Une erreur du serveur est survenue.') + self.assertEqual(str(APIException()), 'Une erreur du serveur est survenue.') def test_server_error(): diff --git a/tests/test_fields.py b/tests/test_fields.py index 42adedfed9b..e0833564b40 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -10,7 +10,6 @@ from django.core.exceptions import ValidationError as DjangoValidationError from django.http import QueryDict from django.test import TestCase, override_settings -from django.utils import six from django.utils.timezone import activate, deactivate, override, utc import rest_framework @@ -167,7 +166,7 @@ def test_default(self): """ field = serializers.IntegerField(default=123) output = field.run_validation() - assert output is 123 + assert output == 123 class TestSource: @@ -193,7 +192,7 @@ def test_callable_source(self): class ExampleSerializer(serializers.Serializer): example_field = serializers.CharField(source='example_callable') - class ExampleInstance(object): + class ExampleInstance: def example_callable(self): return 'example callable value' @@ -204,7 +203,7 @@ def test_callable_source_raises(self): class ExampleSerializer(serializers.Serializer): example_field = serializers.CharField(source='example_callable', read_only=True) - class ExampleInstance(object): + class ExampleInstance: def example_callable(self): raise AttributeError('method call failed') @@ -754,7 +753,7 @@ def test_iterable_validators(self): def raise_exception(value): raise exceptions.ValidationError('Raised error') - for validators in ([raise_exception], (raise_exception,), set([raise_exception])): + for validators in ([raise_exception], (raise_exception,), {raise_exception}): field = serializers.CharField(validators=validators) with pytest.raises(serializers.ValidationError) as exc_info: field.run_validation(value) @@ -822,7 +821,7 @@ def test_allow_unicode_true(self): validation_error = False try: - field.run_validation(u'slug-99-\u0420') + field.run_validation('slug-99-\u0420') except serializers.ValidationError: validation_error = True @@ -1148,7 +1147,7 @@ def test_to_representation(self): def test_localize_forces_coerce_to_string(self): field = serializers.DecimalField(max_digits=2, decimal_places=1, coerce_to_string=False, localize=True) - assert isinstance(field.to_representation(Decimal('1.1')), six.string_types) + assert isinstance(field.to_representation(Decimal('1.1')), str) class TestQuantizedValueForDecimal(TestCase): @@ -1219,7 +1218,7 @@ class TestDateField(FieldValues): outputs = { datetime.date(2001, 1, 1): '2001-01-01', '2001-01-01': '2001-01-01', - six.text_type('2016-01-10'): '2016-01-10', + str('2016-01-10'): '2016-01-10', None: None, '': None, } @@ -1286,7 +1285,7 @@ class TestDateTimeField(FieldValues): datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00Z', datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): '2001-01-01T13:00:00Z', '2001-01-01T00:00:00': '2001-01-01T00:00:00', - six.text_type('2016-01-10T00:00:00'): '2016-01-10T00:00:00', + str('2016-01-10T00:00:00'): '2016-01-10T00:00:00', None: None, '': None, } @@ -1628,7 +1627,7 @@ def test_edit_choices(self): ] ) field.choices = [1] - assert field.run_validation(1) is 1 + assert field.run_validation(1) == 1 with pytest.raises(serializers.ValidationError) as exc_info: field.run_validation(2) assert exc_info.value.detail == ['"2" is not a valid choice.'] diff --git a/tests/test_filters.py b/tests/test_filters.py index a53fa192a1e..a52f40103f3 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -1,6 +1,5 @@ -from __future__ import unicode_literals - import datetime +from importlib import reload as reload_module import pytest from django.core.exceptions import ImproperlyConfigured @@ -8,7 +7,6 @@ from django.db.models.functions import Concat, Upper from django.test import TestCase from django.test.utils import override_settings -from django.utils.six.moves import reload_module from rest_framework import filters, generics, serializers from rest_framework.compat import coreschema @@ -163,7 +161,7 @@ class CustomSearchFilter(filters.SearchFilter): def get_search_fields(self, view, request): if request.query_params.get('title_only'): return ('$title',) - return super(CustomSearchFilter, self).get_search_fields(view, request) + return super().get_search_fields(view, request) class SearchListView(generics.ListAPIView): queryset = SearchFilterModel.objects.all() diff --git a/tests/test_generateschema.py b/tests/test_generateschema.py index 915c6ea0593..a6a1f2bedb4 100644 --- a/tests/test_generateschema.py +++ b/tests/test_generateschema.py @@ -1,11 +1,10 @@ -from __future__ import unicode_literals +import io import pytest from django.conf.urls import url from django.core.management import call_command from django.test import TestCase from django.test.utils import override_settings -from django.utils import six from rest_framework.compat import coreapi from rest_framework.utils import formatting, json @@ -28,9 +27,8 @@ class GenerateSchemaTests(TestCase): """Tests for management command generateschema.""" def setUp(self): - self.out = six.StringIO() + self.out = io.StringIO() - @pytest.mark.skipif(six.PY2, reason='PyYAML unicode output is malformed on PY2.') def test_renders_default_schema_with_custom_title_url_and_description(self): expected_out = """info: description: Sample description diff --git a/tests/test_generics.py b/tests/test_generics.py index c0ff1c5c4e0..f41ebe6da79 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -1,11 +1,8 @@ -from __future__ import unicode_literals - import pytest from django.db import models from django.http import Http404 from django.shortcuts import get_object_or_404 from django.test import TestCase -from django.utils import six from rest_framework import generics, renderers, serializers, status from rest_framework.response import Response @@ -245,7 +242,7 @@ def test_delete_instance_view(self): with self.assertNumQueries(2): response = self.view(request, pk=1).render() assert response.status_code == status.HTTP_204_NO_CONTENT - assert response.content == six.b('') + assert response.content == b'' ids = [obj.id for obj in self.objects.all()] assert ids == [2, 3] @@ -291,7 +288,7 @@ def test_put_to_filtered_out_instance(self): """ data = {'text': 'foo'} filtered_out_pk = BasicModel.objects.filter(text='filtered out')[0].pk - request = factory.put('/{0}'.format(filtered_out_pk), data, format='json') + request = factory.put('/{}'.format(filtered_out_pk), data, format='json') response = self.view(request, pk=filtered_out_pk).render() assert response.status_code == status.HTTP_404_NOT_FOUND @@ -446,12 +443,12 @@ def test_m2m_in_browsable_api(self): assert response.status_code == status.HTTP_200_OK -class InclusiveFilterBackend(object): +class InclusiveFilterBackend: def filter_queryset(self, request, queryset, view): return queryset.filter(text='foo') -class ExclusiveFilterBackend(object): +class ExclusiveFilterBackend: def filter_queryset(self, request, queryset, view): return queryset.filter(text='other') @@ -653,7 +650,7 @@ def destroy(self, request, *args, **kwargs): class GetObjectOr404Tests(TestCase): def setUp(self): - super(GetObjectOr404Tests, self).setUp() + super().setUp() self.uuid_object = UUIDForeignKeyTarget.objects.create(name='bar') def test_get_object_or_404_with_valid_uuid(self): diff --git a/tests/test_htmlrenderer.py b/tests/test_htmlrenderer.py index decd25a3fe5..e31a9ced522 100644 --- a/tests/test_htmlrenderer.py +++ b/tests/test_htmlrenderer.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django.template.loader import pytest from django.conf.urls import url @@ -7,7 +5,6 @@ from django.http import Http404 from django.template import TemplateDoesNotExist, engines from django.test import TestCase, override_settings -from django.utils import six from rest_framework import status from rest_framework.decorators import api_view, renderer_classes @@ -47,7 +44,7 @@ def not_found(request): @override_settings(ROOT_URLCONF='tests.test_htmlrenderer') class TemplateHTMLRendererTests(TestCase): def setUp(self): - class MockResponse(object): + class MockResponse: template_name = None self.mock_response = MockResponse() self._monkey_patch_get_template() @@ -85,13 +82,13 @@ def test_simple_html_view(self): def test_not_found_html_view(self): response = self.client.get('/not_found') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertEqual(response.content, six.b("404 Not Found")) + self.assertEqual(response.content, b"404 Not Found") self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') def test_permission_denied_html_view(self): response = self.client.get('/permission_denied') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.content, six.b("403 Forbidden")) + self.assertEqual(response.content, b"403 Forbidden") self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') # 2 tests below are based on order of if statements in corresponding method @@ -105,14 +102,14 @@ def test_get_template_names_returns_own_template_name(self): def test_get_template_names_returns_view_template_name(self): renderer = TemplateHTMLRenderer() - class MockResponse(object): + class MockResponse: template_name = None - class MockView(object): + class MockView: def get_template_names(self): return ['template from get_template_names method'] - class MockView2(object): + class MockView2: template_name = 'template from template_name attribute' template_name = renderer.get_template_names(self.mock_response, @@ -156,12 +153,11 @@ def test_not_found_html_view_with_template(self): response = self.client.get('/not_found') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertTrue(response.content in ( - six.b("404: Not found"), six.b("404 Not Found"))) + b"404: Not found", b"404 Not Found")) self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') def test_permission_denied_html_view_with_template(self): response = self.client.get('/permission_denied') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertTrue(response.content in ( - six.b("403: Permission denied"), six.b("403 Forbidden"))) + self.assertTrue(response.content in (b"403: Permission denied", b"403 Forbidden")) self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') diff --git a/tests/test_metadata.py b/tests/test_metadata.py index fe4ea4b428e..e1a1fd3528f 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import pytest from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 9df7d8e3e69..28a5e558a1b 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -22,7 +22,7 @@ def post(self, request): ] -class RequestUserMiddleware(object): +class RequestUserMiddleware: def __init__(self, get_response): self.get_response = get_response @@ -34,7 +34,7 @@ def __call__(self, request): return response -class RequestPOSTMiddleware(object): +class RequestPOSTMiddleware: def __init__(self, get_response): self.get_response = get_response diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 898c859a4ff..413d7885d35 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -5,8 +5,6 @@ These tests deal with ensuring that we correctly map the model fields onto an appropriate set of serializer fields for each case. """ -from __future__ import unicode_literals - import datetime import decimal import sys @@ -20,10 +18,9 @@ ) from django.db import models from django.test import TestCase -from django.utils import six from rest_framework import serializers -from rest_framework.compat import postgres_fields, unicode_repr +from rest_framework.compat import postgres_fields from .models import NestedForeignKeySource @@ -193,7 +190,7 @@ class Meta: file_path_field = FilePathField(path='/tmp/') """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_field_options(self): class TestSerializer(serializers.ModelSerializer): @@ -212,14 +209,7 @@ class Meta: descriptive_field = IntegerField(help_text='Some help text', label='A label') choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green'))) """) - if six.PY2: - # This particular case is too awkward to resolve fully across - # both py2 and py3. - expected = expected.replace( - "('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')", - "(u'red', u'Red'), (u'blue', u'Blue'), (u'green', u'Green')" - ) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) # merge this into test_regular_fields / RegularFieldsModel when # Django 2.1 is the minimum supported version @@ -238,7 +228,7 @@ class Meta: field = BooleanField(allow_null=True, required=False) """) - self.assertEqual(unicode_repr(NullableBooleanSerializer()), expected) + self.assertEqual(repr(NullableBooleanSerializer()), expected) def test_method_field(self): """ @@ -382,7 +372,7 @@ class Meta: id = IntegerField(label='ID', read_only=True) duration_field = DurationField() """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_duration_field_with_validators(self): class ValidatedDurationFieldModel(models.Model): @@ -407,7 +397,7 @@ class Meta: id = IntegerField(label='ID', read_only=True) duration_field = DurationField(max_value=datetime.timedelta(days=3), min_value=datetime.timedelta(days=1)) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) class TestGenericIPAddressFieldValidation(TestCase): @@ -424,7 +414,7 @@ class Meta: self.assertFalse(s.is_valid()) self.assertEqual(1, len(s.errors['address']), 'Unexpected number of validation errors: ' - '{0}'.format(s.errors)) + '{}'.format(s.errors)) @pytest.mark.skipif('not postgres_fields') @@ -442,7 +432,7 @@ class Meta: TestSerializer(): hstore_field = HStoreField() """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_array_field(self): class ArrayFieldModel(models.Model): @@ -457,7 +447,7 @@ class Meta: TestSerializer(): array_field = ListField(child=CharField(label='Array field', validators=[