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=[])) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_json_field(self): class JSONFieldModel(models.Model): @@ -472,7 +462,7 @@ class Meta: TestSerializer(): json_field = JSONField(style={'base_template': 'textarea.html'}) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) # Tests for relational field mappings. @@ -530,7 +520,7 @@ class Meta: many_to_many = PrimaryKeyRelatedField(allow_empty=False, many=True, queryset=ManyToManyTargetModel.objects.all()) through = PrimaryKeyRelatedField(many=True, read_only=True) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_nested_relations(self): class TestSerializer(serializers.ModelSerializer): @@ -555,7 +545,7 @@ class Meta: id = IntegerField(label='ID', read_only=True) name = CharField(max_length=100) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_hyperlinked_relations(self): class TestSerializer(serializers.HyperlinkedModelSerializer): @@ -571,7 +561,7 @@ class Meta: many_to_many = HyperlinkedRelatedField(allow_empty=False, many=True, queryset=ManyToManyTargetModel.objects.all(), view_name='manytomanytargetmodel-detail') through = HyperlinkedRelatedField(many=True, read_only=True, view_name='throughtargetmodel-detail') """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_nested_hyperlinked_relations(self): class TestSerializer(serializers.HyperlinkedModelSerializer): @@ -596,7 +586,7 @@ class Meta: url = HyperlinkedIdentityField(view_name='throughtargetmodel-detail') name = CharField(max_length=100) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_nested_hyperlinked_relations_starred_source(self): class TestSerializer(serializers.HyperlinkedModelSerializer): @@ -627,7 +617,7 @@ class Meta: name = CharField(max_length=100) """) self.maxDiff = None - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_nested_unique_together_relations(self): class TestSerializer(serializers.HyperlinkedModelSerializer): @@ -646,14 +636,7 @@ class Meta: url = HyperlinkedIdentityField(view_name='onetoonetargetmodel-detail') name = CharField(max_length=100) """) - if six.PY2: - # This case is also too awkward to resolve fully across both py2 - # and py3. (See above) - expected = expected.replace( - "('foreign_key', 'one_to_one')", - "(u'foreign_key', u'one_to_one')" - ) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_pk_reverse_foreign_key(self): class TestSerializer(serializers.ModelSerializer): @@ -667,7 +650,7 @@ class Meta: name = CharField(max_length=100) reverse_foreign_key = PrimaryKeyRelatedField(many=True, queryset=RelationalModel.objects.all()) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_pk_reverse_one_to_one(self): class TestSerializer(serializers.ModelSerializer): @@ -681,7 +664,7 @@ class Meta: name = CharField(max_length=100) reverse_one_to_one = PrimaryKeyRelatedField(queryset=RelationalModel.objects.all()) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_pk_reverse_many_to_many(self): class TestSerializer(serializers.ModelSerializer): @@ -695,7 +678,7 @@ class Meta: name = CharField(max_length=100) reverse_many_to_many = PrimaryKeyRelatedField(many=True, queryset=RelationalModel.objects.all()) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_pk_reverse_through(self): class TestSerializer(serializers.ModelSerializer): @@ -709,7 +692,7 @@ class Meta: name = CharField(max_length=100) reverse_through = PrimaryKeyRelatedField(many=True, read_only=True) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) class DisplayValueTargetModel(models.Model): @@ -1078,9 +1061,9 @@ class Meta(TestSerializer.Meta): char_field = CharField(max_length=100) non_model_field = CharField() """) - self.assertEqual(unicode_repr(ChildSerializer()), child_expected) - self.assertEqual(unicode_repr(TestSerializer()), test_expected) - self.assertEqual(unicode_repr(ChildSerializer()), child_expected) + self.assertEqual(repr(ChildSerializer()), child_expected) + self.assertEqual(repr(TestSerializer()), test_expected) + self.assertEqual(repr(ChildSerializer()), child_expected) class OneToOneTargetTestModel(models.Model): @@ -1149,14 +1132,14 @@ class Meta: title = CharField(max_length=64) children = PrimaryKeyRelatedField(many=True, queryset=TestChildModel.objects.all()) """) - self.assertEqual(unicode_repr(TestParentModelSerializer()), parent_expected) + self.assertEqual(repr(TestParentModelSerializer()), parent_expected) child_expected = dedent(""" TestChildModelSerializer(): value = CharField(max_length=64, validators=[]) parent = PrimaryKeyRelatedField(queryset=TestParentModel.objects.all()) """) - self.assertEqual(unicode_repr(TestChildModelSerializer()), child_expected) + self.assertEqual(repr(TestChildModelSerializer()), child_expected) def test_nonID_PK_foreignkey_model_serializer(self): @@ -1248,7 +1231,7 @@ class Meta: number_field = IntegerField(source='integer_field') """) self.maxDiff = None - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) class Issue6110TestModel(models.Model): diff --git a/tests/test_multitable_inheritance.py b/tests/test_multitable_inheritance.py index 2ddd37ebbaa..1e8ab344856 100644 --- a/tests/test_multitable_inheritance.py +++ b/tests/test_multitable_inheritance.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import models from django.test import TestCase diff --git a/tests/test_negotiation.py b/tests/test_negotiation.py index 7ce3f92a9b5..089a86c624a 100644 --- a/tests/test_negotiation.py +++ b/tests/test_negotiation.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import pytest from django.http import Http404 from django.test import TestCase @@ -80,7 +78,7 @@ def test_mediatype_string_representation(self): assert str(mediatype) == 'test/*; foo=bar' def test_raise_error_if_no_suitable_renderers_found(self): - class MockRenderer(object): + class MockRenderer: format = 'xml' renderers = [MockRenderer()] with pytest.raises(Http404): diff --git a/tests/test_one_to_one_with_inheritance.py b/tests/test_one_to_one_with_inheritance.py index 789c7fcb97d..40793d7ca3b 100644 --- a/tests/test_one_to_one_with_inheritance.py +++ b/tests/test_one_to_one_with_inheritance.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import models from django.test import TestCase diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 6d940fe2b0c..3c581ddfbd1 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -1,11 +1,7 @@ -# coding: utf-8 -from __future__ import unicode_literals - import pytest from django.core.paginator import Paginator as DjangoPaginator from django.db import models from django.test import TestCase -from django.utils import six from rest_framework import ( exceptions, filters, generics, pagination, serializers, status @@ -208,7 +204,7 @@ def test_no_page_number(self): ] } assert self.pagination.display_page_controls - assert isinstance(self.pagination.to_html(), six.text_type) + assert isinstance(self.pagination.to_html(), str) def test_second_page(self): request = Request(factory.get('/', {'page': 2})) @@ -314,7 +310,7 @@ def test_no_page_number(self): ] } assert not self.pagination.display_page_controls - assert isinstance(self.pagination.to_html(), six.text_type) + assert isinstance(self.pagination.to_html(), str) def test_invalid_page(self): request = Request(factory.get('/', {'page': 'invalid'})) @@ -369,7 +365,7 @@ def test_no_offset(self): ] } assert self.pagination.display_page_controls - assert isinstance(self.pagination.to_html(), six.text_type) + assert isinstance(self.pagination.to_html(), str) def test_pagination_not_applied_if_limit_or_default_limit_not_set(self): class MockPagination(pagination.LimitOffsetPagination): @@ -503,7 +499,7 @@ def test_invalid_limit(self): content = self.get_paginated_content(queryset) next_limit = self.pagination.default_limit next_offset = self.pagination.default_limit - next_url = 'http://testserver/?limit={0}&offset={1}'.format(next_limit, next_offset) + next_url = 'http://testserver/?limit={}&offset={}'.format(next_limit, next_offset) assert queryset == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] assert content.get('next') == next_url @@ -516,7 +512,7 @@ def test_zero_limit(self): content = self.get_paginated_content(queryset) next_limit = self.pagination.default_limit next_offset = self.pagination.default_limit - next_url = 'http://testserver/?limit={0}&offset={1}'.format(next_limit, next_offset) + next_url = 'http://testserver/?limit={}&offset={}'.format(next_limit, next_offset) assert queryset == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] assert content.get('next') == next_url @@ -532,9 +528,9 @@ def test_max_limit(self): max_limit = self.pagination.max_limit next_offset = offset + max_limit prev_offset = offset - max_limit - base_url = 'http://testserver/?limit={0}'.format(max_limit) - next_url = base_url + '&offset={0}'.format(next_offset) - prev_url = base_url + '&offset={0}'.format(prev_offset) + base_url = 'http://testserver/?limit={}'.format(max_limit) + next_url = base_url + '&offset={}'.format(next_offset) + prev_url = base_url + '&offset={}'.format(prev_offset) assert queryset == list(range(51, 66)) assert content.get('next') == next_url assert content.get('previous') == prev_url @@ -632,7 +628,7 @@ def test_cursor_pagination(self): assert current == [1, 1, 1, 1, 1] assert next == [1, 2, 3, 4, 4] - assert isinstance(self.pagination.to_html(), six.text_type) + assert isinstance(self.pagination.to_html(), str) def test_cursor_pagination_with_page_size(self): (previous, current, next, previous_url, next_url) = self.get_pages('/?page_size=20') @@ -799,11 +795,11 @@ class TestCursorPagination(CursorPaginationTestsMixin): """ def setup(self): - class MockObject(object): + class MockObject: def __init__(self, idx): self.created = idx - class MockQuerySet(object): + class MockQuerySet: def __init__(self, items): self.items = items diff --git a/tests/test_parsers.py b/tests/test_parsers.py index e793948e37d..7cf0c938a97 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import io import math @@ -11,7 +8,6 @@ ) from django.http.request import RawPostDataException from django.test import TestCase -from django.utils.six import StringIO from rest_framework.exceptions import ParseError from rest_framework.parsers import ( @@ -34,7 +30,7 @@ def test_parse(self): """ Make sure the `QueryDict` works OK """ parser = FormParser() - stream = StringIO(self.string) + stream = io.StringIO(self.string) data = parser.parse(stream) assert Form(data).is_valid() is True @@ -42,7 +38,7 @@ def test_parse(self): class TestFileUploadParser(TestCase): def setUp(self): - class MockRequest(object): + class MockRequest: pass self.stream = io.BytesIO( "Test text file".encode('utf-8') diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 2fabdfa05c2..9c93006949b 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -1,8 +1,7 @@ -from __future__ import unicode_literals - import base64 import unittest import warnings +from unittest import mock import django import pytest @@ -15,7 +14,7 @@ HTTP_HEADER_ENCODING, RemovedInDRF310Warning, authentication, generics, permissions, serializers, status, views ) -from rest_framework.compat import PY36, is_guardian_installed, mock +from rest_framework.compat import PY36, is_guardian_installed from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.routers import DefaultRouter from rest_framework.test import APIRequestFactory @@ -331,14 +330,14 @@ def setUp(self): everyone = Group.objects.create(name='everyone') model_name = BasicPermModel._meta.model_name app_label = BasicPermModel._meta.app_label - f = '{0}_{1}'.format + f = '{}_{}'.format perms = { 'view': f('view', model_name), 'change': f('change', model_name), 'delete': f('delete', model_name) } for perm in perms.values(): - perm = '{0}.{1}'.format(app_label, perm) + perm = '{}.{}'.format(app_label, perm) assign_perm(perm, everyone) everyone.user_set.add(*users.values()) diff --git a/tests/test_relations.py b/tests/test_relations.py index 3c4b7d90b95..3281b7ea229 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -26,7 +26,7 @@ def test_string_related_representation(self): assert representation == '' -class MockApiSettings(object): +class MockApiSettings: def __init__(self, cutoff, cutoff_text): self.HTML_SELECT_CUTOFF = cutoff self.HTML_SELECT_CUTOFF_TEXT = cutoff_text diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index 887a6f423a5..5ad0e31ff88 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from django.test import TestCase, override_settings diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 2cffb62e6bf..0da9da890ab 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -1,7 +1,4 @@ -from __future__ import unicode_literals - from django.test import TestCase -from django.utils import six from rest_framework import serializers from tests.models import ( @@ -263,7 +260,7 @@ def test_foreign_key_update_incorrect_type(self): instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data) assert not serializer.is_valid() - assert serializer.errors == {'target': ['Incorrect type. Expected pk value, received %s.' % six.text_type.__name__]} + assert serializer.errors == {'target': ['Incorrect type. Expected pk value, received str.']} def test_reverse_foreign_key_update(self): data = {'id': 2, 'name': 'target-2', 'sources': [1, 3]} @@ -562,7 +559,7 @@ def test_one_to_one_when_primary_key_no_duplicates(self): # When: Trying to create a second object second_source = OneToOnePKSourceSerializer(data=data) self.assertFalse(second_source.is_valid()) - expected = {'target': [u'one to one pk source with this target already exists.']} + expected = {'target': ['one to one pk source with this target already exists.']} self.assertDictEqual(second_source.errors, expected) def test_one_to_one_when_primary_key_does_not_exist(self): diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 60a0c0307d7..54d1cb231e0 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import re from collections import OrderedDict @@ -11,7 +8,6 @@ from django.http.request import HttpRequest from django.template import loader from django.test import TestCase, override_settings -from django.utils import six from django.utils.safestring import SafeText from django.utils.translation import ugettext_lazy as _ @@ -175,7 +171,7 @@ def test_head_method_serializes_no_content(self): resp = self.client.head('/') self.assertEqual(resp.status_code, DUMMYSTATUS) self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8') - self.assertEqual(resp.content, six.b('')) + self.assertEqual(resp.content, b'') def test_default_renderer_serializes_content_on_accept_any(self): """If the Accept header is set to */* the default renderer should serialize the response.""" @@ -348,7 +344,7 @@ def keys(self): self.assertEqual(data, {'key': 'string value', '2': 3}) def test_render_obj_with_getitem(self): - class DictLike(object): + class DictLike: def __init__(self): self._dict = {} @@ -647,7 +643,7 @@ def test_get_description_returns_empty_string_for_401_and_403_statuses(self): assert self.renderer.get_description({}, status_code=403) == '' def test_get_filter_form_returns_none_if_data_is_not_list_instance(self): - class DummyView(object): + class DummyView: get_queryset = None filter_backends = None diff --git a/tests/test_request.py b/tests/test_request.py index 83d295a1285..0f682deb017 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -1,8 +1,6 @@ """ Tests for content parsing, and form-overloaded content parsing. """ -from __future__ import unicode_literals - import os.path import tempfile @@ -15,7 +13,6 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.http.request import RawPostDataException from django.test import TestCase, override_settings -from django.utils import six from rest_framework import status from rest_framework.authentication import SessionAuthentication @@ -82,7 +79,7 @@ def test_request_DATA_with_text_content(self): Ensure request.data returns content for POST request with non-form content. """ - content = six.b('qwerty') + content = b'qwerty' content_type = 'text/plain' request = Request(factory.post('/', content, content_type=content_type)) request.parsers = (PlainTextParser(),) @@ -121,7 +118,7 @@ def test_standard_behaviour_determines_non_form_content_PUT(self): Ensure request.data returns content for PUT request with non-form content. """ - content = six.b('qwerty') + content = b'qwerty' content_type = 'text/plain' request = Request(factory.put('/', content, content_type=content_type)) request.parsers = (PlainTextParser(), ) @@ -235,7 +232,7 @@ def test_calling_user_fails_when_attribute_error_is_raised(self): This proves that when an AttributeError is raised inside of the request.user property, that we can handle this and report the true, underlying error. """ - class AuthRaisesAttributeError(object): + class AuthRaisesAttributeError: def authenticate(self, request): self.MISSPELLED_NAME_THAT_DOESNT_EXIST @@ -249,10 +246,6 @@ def authenticate(self, request): with pytest.raises(WrappedAttributeError, match=expected): request.user - # python 2 hasattr fails for *any* exception, not just AttributeError - if six.PY2: - return - with pytest.raises(WrappedAttributeError, match=expected): hasattr(request, 'user') diff --git a/tests/test_requests_client.py b/tests/test_requests_client.py index 161429f73e0..59b388c5a6a 100644 --- a/tests/test_requests_client.py +++ b/tests/test_requests_client.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import unittest from django.conf.urls import url diff --git a/tests/test_response.py b/tests/test_response.py index e92bf54c169..d3a56d01b83 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,8 +1,5 @@ -from __future__ import unicode_literals - from django.conf.urls import include, url from django.test import TestCase, override_settings -from django.utils import six from rest_framework import generics, routers, serializers, status, viewsets from rest_framework.parsers import JSONParser @@ -150,7 +147,7 @@ def test_head_method_serializes_no_content(self): resp = self.client.head('/') self.assertEqual(resp.status_code, DUMMYSTATUS) self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8') - self.assertEqual(resp.content, six.b('')) + self.assertEqual(resp.content, b'') def test_default_renderer_serializes_content_on_accept_any(self): """If the Accept header is set to */* the default renderer should serialize the response.""" @@ -260,7 +257,7 @@ def test_does_not_append_charset_by_default(self): """ headers = {"HTTP_ACCEPT": RendererA.media_type} resp = self.client.get('/', **headers) - expected = "{0}; charset={1}".format(RendererA.media_type, 'utf-8') + expected = "{}; charset={}".format(RendererA.media_type, 'utf-8') self.assertEqual(expected, resp['Content-Type']) def test_if_there_is_charset_specified_on_renderer_it_gets_appended(self): @@ -270,7 +267,7 @@ def test_if_there_is_charset_specified_on_renderer_it_gets_appended(self): """ headers = {"HTTP_ACCEPT": RendererC.media_type} resp = self.client.get('/', **headers) - expected = "{0}; charset={1}".format(RendererC.media_type, RendererC.charset) + expected = "{}; charset={}".format(RendererC.media_type, RendererC.charset) self.assertEqual(expected, resp['Content-Type']) def test_content_type_set_explicitly_on_response(self): diff --git a/tests/test_reverse.py b/tests/test_reverse.py index 145b1a54f3a..9ab1667c528 100644 --- a/tests/test_reverse.py +++ b/tests/test_reverse.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from django.test import TestCase, override_settings from django.urls import NoReverseMatch @@ -19,7 +17,7 @@ def null_view(request): ] -class MockVersioningScheme(object): +class MockVersioningScheme: def __init__(self, raise_error=False): self.raise_error = raise_error diff --git a/tests/test_routers.py b/tests/test_routers.py index cca2ea7122d..adcec8bd62e 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import warnings from collections import namedtuple diff --git a/tests/test_schemas.py b/tests/test_schemas.py index 3cb9e0cda8f..1aad5d1de01 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -29,7 +29,7 @@ factory = APIRequestFactory() -class MockUser(object): +class MockUser: def is_authenticated(self): return True @@ -112,7 +112,7 @@ def excluded_action(self, request): def get_serializer(self, *args, **kwargs): assert self.request assert self.action - return super(ExampleViewSet, self).get_serializer(*args, **kwargs) + return super().get_serializer(*args, **kwargs) @action(methods=['get', 'post'], detail=False) def documented_custom_action(self, request): @@ -1303,7 +1303,7 @@ def custom_action(self, request, pk): @pytest.mark.skipif(not coreapi, reason='coreapi is not installed') -class TestAutoSchemaAllowsFilters(object): +class TestAutoSchemaAllowsFilters: class MockAPIView(APIView): filter_backends = [filters.OrderingFilter] diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 0f1e81965a9..8f4d9bf63a8 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1,16 +1,13 @@ -# coding: utf-8 -from __future__ import unicode_literals - import inspect import pickle import re -import unittest +from collections import ChainMap import pytest from django.db import models from rest_framework import exceptions, fields, relations, serializers -from rest_framework.compat import Mapping, unicode_repr +from rest_framework.compat import Mapping from rest_framework.fields import Field from .models import ( @@ -18,15 +15,9 @@ ) from .utils import MockObject -try: - from collections import ChainMap -except ImportError: - ChainMap = False - # Test serializer fields imports. # ------------------------------- - class TestFieldImports: def is_field(self, name, value): return ( @@ -130,7 +121,6 @@ def test_validate_none_data(self): assert not serializer.is_valid() assert serializer.errors == {'non_field_errors': ['No data provided']} - @unittest.skipUnless(ChainMap, 'requires python 3.3') def test_serialize_chainmap(self): data = ChainMap({'char': 'abc'}, {'integer': 123}) serializer = self.Serializer(data=data) @@ -160,7 +150,7 @@ def test_custom_to_internal_value(self): to_internal_value() is expected to return a dict, but subclasses may return application specific type. """ - class Point(object): + class Point: def __init__(self, srid, x, y): self.srid = srid self.coords = (x, y) @@ -171,7 +161,7 @@ class NestedPointSerializer(serializers.Serializer): latitude = serializers.FloatField(source='y') def to_internal_value(self, data): - kwargs = super(NestedPointSerializer, self).to_internal_value(data) + kwargs = super().to_internal_value(data) return Point(srid=4326, **kwargs) serializer = NestedPointSerializer(data={'longitude': 6.958307, 'latitude': 50.941357}) @@ -201,7 +191,7 @@ class ExampleSerializer(serializers.Serializer): 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}): class ExampleSerializer(serializers.Serializer): char = serializers.CharField(validators=validators) integer = serializers.IntegerField() @@ -397,7 +387,7 @@ def __init__(self): class TestUnicodeRepr: - def test_unicode_repr(self): + def test_repr(self): class ExampleSerializer(serializers.Serializer): example = serializers.CharField() @@ -406,7 +396,7 @@ def __init__(self): self.example = '한국' def __repr__(self): - return unicode_repr(self.example) + return repr(self.example) instance = ExampleObject() serializer = ExampleSerializer(instance) @@ -609,7 +599,7 @@ class Test2555Regression: def test_serializer_context(self): class NestedSerializer(serializers.Serializer): def __init__(self, *args, **kwargs): - super(NestedSerializer, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # .context should not cache self.context diff --git a/tests/test_serializer_bulk_update.py b/tests/test_serializer_bulk_update.py index d9e5d797823..0465578bb6a 100644 --- a/tests/test_serializer_bulk_update.py +++ b/tests/test_serializer_bulk_update.py @@ -1,10 +1,7 @@ """ Tests to cover bulk create and update using serializers. """ -from __future__ import unicode_literals - from django.test import TestCase -from django.utils import six from rest_framework import serializers @@ -87,8 +84,7 @@ def test_invalid_list_datatype(self): serializer = self.BookSerializer(data=data, many=True) assert serializer.is_valid() is False - text_type_string = six.text_type.__name__ - message = 'Invalid data. Expected a dictionary, but got %s.' % text_type_string + message = 'Invalid data. Expected a dictionary, but got str.' expected_errors = [ {'non_field_errors': [message]}, {'non_field_errors': [message]}, diff --git a/tests/test_settings.py b/tests/test_settings.py index 51e9751b25d..b78125ff958 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.test import TestCase, override_settings from rest_framework.settings import APISettings, api_settings diff --git a/tests/test_status.py b/tests/test_status.py index 1cd6e229e92..07d893bee9c 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.test import TestCase from rest_framework.status import ( diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py index 45bfd4aeb77..128160888a8 100644 --- a/tests/test_templatetags.py +++ b/tests/test_templatetags.py @@ -1,6 +1,3 @@ -# encoding: utf-8 -from __future__ import unicode_literals - import unittest from django.template import Context, Template @@ -225,7 +222,7 @@ def test_as_string_with_none(self): assert result == '' def test_get_pagination_html(self): - class MockPager(object): + class MockPager: def __init__(self): self.called = False @@ -340,7 +337,7 @@ def test_schema_with_empty_links(self): ) section = schema['users'] flat_links = schema_links(section) - assert len(flat_links) is 0 + assert len(flat_links) == 0 def test_single_action(self): schema = coreapi.Document( @@ -358,7 +355,7 @@ def test_single_action(self): ) section = schema['users'] flat_links = schema_links(section) - assert len(flat_links) is 1 + assert len(flat_links) == 1 assert 'list' in flat_links def test_default_actions(self): @@ -396,7 +393,7 @@ def test_default_actions(self): ) section = schema['users'] flat_links = schema_links(section) - assert len(flat_links) is 4 + assert len(flat_links) == 4 assert 'list' in flat_links assert 'create' in flat_links assert 'read' in flat_links @@ -444,7 +441,7 @@ def test_default_actions_and_single_custom_action(self): ) section = schema['users'] flat_links = schema_links(section) - assert len(flat_links) is 5 + assert len(flat_links) == 5 assert 'list' in flat_links assert 'create' in flat_links assert 'read' in flat_links @@ -502,7 +499,7 @@ def test_default_actions_and_single_custom_action_two_methods(self): ) section = schema['users'] flat_links = schema_links(section) - assert len(flat_links) is 6 + assert len(flat_links) == 6 assert 'list' in flat_links assert 'create' in flat_links assert 'read' in flat_links @@ -553,7 +550,7 @@ def test_multiple_nested_routes(self): ) section = schema['animals'] flat_links = schema_links(section) - assert len(flat_links) is 4 + assert len(flat_links) == 4 assert 'cat > create' in flat_links assert 'cat > list' in flat_links assert 'dog > read' in flat_links @@ -622,7 +619,7 @@ def test_multiple_resources_with_multiple_nested_routes(self): ) section = schema['animals'] flat_links = schema_links(section) - assert len(flat_links) is 4 + assert len(flat_links) == 4 assert 'cat > create' in flat_links assert 'cat > list' in flat_links assert 'dog > read' in flat_links @@ -630,6 +627,6 @@ def test_multiple_resources_with_multiple_nested_routes(self): section = schema['farmers'] flat_links = schema_links(section) - assert len(flat_links) is 2 + assert len(flat_links) == 2 assert 'silo > list' in flat_links assert 'silo > soy > list' in flat_links diff --git a/tests/test_testing.py b/tests/test_testing.py index 7868f724c16..8094bfd8d27 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -1,6 +1,3 @@ -# encoding: utf-8 -from __future__ import unicode_literals - from io import BytesIO from django.conf.urls import url @@ -293,13 +290,13 @@ class TestUrlPatternTestCase(URLPatternsTestCase): @classmethod def setUpClass(cls): assert urlpatterns is not cls.urlpatterns - super(TestUrlPatternTestCase, cls).setUpClass() + super().setUpClass() assert urlpatterns is cls.urlpatterns @classmethod def tearDownClass(cls): assert urlpatterns is cls.urlpatterns - super(TestUrlPatternTestCase, cls).tearDownClass() + super().tearDownClass() assert urlpatterns is not cls.urlpatterns def test_urlpatterns(self): diff --git a/tests/test_throttling.py b/tests/test_throttling.py index b220a33a6f3..b20b6a809c2 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -1,7 +1,6 @@ """ Tests for the throttling implementations in the permissions module. """ -from __future__ import unicode_literals import pytest from django.contrib.auth.models import User @@ -296,7 +295,7 @@ def test_unscoped_view_not_throttled(self): assert response.status_code == 200 def test_get_cache_key_returns_correct_key_if_user_is_authenticated(self): - class DummyView(object): + class DummyView: throttle_scope = 'user' request = Request(HttpRequest()) diff --git a/tests/test_urlpatterns.py b/tests/test_urlpatterns.py index 59ba395d293..25cc0032ee2 100644 --- a/tests/test_urlpatterns.py +++ b/tests/test_urlpatterns.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import unittest from collections import namedtuple diff --git a/tests/test_utils.py b/tests/test_utils.py index 28b06b17350..a6f8b9d1607 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf.urls import url from django.test import TestCase, override_settings diff --git a/tests/test_validation.py b/tests/test_validation.py index 4132a7b00f9..6e00b48c2e0 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -1,11 +1,8 @@ -from __future__ import unicode_literals - import re from django.core.validators import MaxValueValidator, RegexValidator from django.db import models from django.test import TestCase -from django.utils import six from rest_framework import generics, serializers, status from rest_framework.test import APIRequestFactory @@ -112,7 +109,7 @@ def test_serializer_errors_has_only_invalid_data_error(self): assert not serializer.is_valid() assert serializer.errors == { 'non_field_errors': [ - 'Invalid data. Expected a dictionary, but got %s.' % six.text_type.__name__ + 'Invalid data. Expected a dictionary, but got str.', ] } @@ -151,14 +148,14 @@ def test_max_value_validation_serializer_fails(self): def test_max_value_validation_success(self): obj = ValidationMaxValueValidatorModel.objects.create(number_value=100) - request = factory.patch('/{0}'.format(obj.pk), {'number_value': 98}, format='json') + request = factory.patch('/{}'.format(obj.pk), {'number_value': 98}, format='json') view = UpdateMaxValueValidationModel().as_view() response = view(request, pk=obj.pk).render() assert response.status_code == status.HTTP_200_OK def test_max_value_validation_fail(self): obj = ValidationMaxValueValidatorModel.objects.create(number_value=100) - request = factory.patch('/{0}'.format(obj.pk), {'number_value': 101}, format='json') + request = factory.patch('/{}'.format(obj.pk), {'number_value': 101}, format='json') view = UpdateMaxValueValidationModel().as_view() response = view(request, pk=obj.pk).render() assert response.content == b'{"number_value":["Ensure this value is less than or equal to 100."]}' diff --git a/tests/test_validators.py b/tests/test_validators.py index 4bbddb64bab..fe31ba23576 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -353,7 +353,7 @@ def test_filter_queryset_do_not_skip_existing_attribute(self): filter_queryset should add value from existing instance attribute if it is not provided in attributes dict """ - class MockQueryset(object): + class MockQueryset: def filter(self, **kwargs): self.called_with = kwargs @@ -558,19 +558,19 @@ class Meta: class ValidatorsTests(TestCase): def test_qs_exists_handles_type_error(self): - class TypeErrorQueryset(object): + class TypeErrorQueryset: def exists(self): raise TypeError assert qs_exists(TypeErrorQueryset()) is False def test_qs_exists_handles_value_error(self): - class ValueErrorQueryset(object): + class ValueErrorQueryset: def exists(self): raise ValueError assert qs_exists(ValueErrorQueryset()) is False def test_qs_exists_handles_data_error(self): - class DataErrorQueryset(object): + class DataErrorQueryset: def exists(self): raise DataError assert qs_exists(DataErrorQueryset()) is False diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 7e650e2752b..d4e269df30d 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -319,9 +319,9 @@ class TestHyperlinkedRelatedField(URLPatternsTestCase, APITestCase): ] def setUp(self): - super(TestHyperlinkedRelatedField, self).setUp() + super().setUp() - class MockQueryset(object): + class MockQueryset: def get(self, pk): return 'object %s' % pk diff --git a/tests/test_views.py b/tests/test_views.py index f0919e84617..2648c9fb38d 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,7 +1,4 @@ -from __future__ import unicode_literals - import copy -import sys from django.test import TestCase @@ -14,10 +11,7 @@ factory = APIRequestFactory() -if sys.version_info[:2] >= (3, 4): - JSON_ERROR = 'JSON parse error - Expecting value:' -else: - JSON_ERROR = 'JSON parse error - No JSON object could be decoded' +JSON_ERROR = 'JSON parse error - Expecting value:' class BasicView(APIView): diff --git a/tests/utils.py b/tests/utils.py index 509e6a10272..06e5b9abe62 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,7 +2,7 @@ from django.urls import NoReverseMatch -class MockObject(object): +class MockObject: def __init__(self, **kwargs): self._kwargs = kwargs for key, val in kwargs.items(): @@ -16,7 +16,7 @@ def __str__(self): return '' % kwargs_str -class MockQueryset(object): +class MockQueryset: def __init__(self, iterable): self.items = iterable @@ -33,7 +33,7 @@ def get(self, **lookup): raise ObjectDoesNotExist() -class BadType(object): +class BadType: """ When used as a lookup with a `MockQueryset`, these objects will raise a `TypeError`, as occurs in Django when making diff --git a/tox.ini b/tox.ini index 5d7a4987e38..fcd32f88afc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - {py27,py34,py35,py36}-django111, + {py34,py35,py36}-django111, {py34,py35,py36,py37}-django20, {py35,py36,py37}-django21 {py35,py36,py37}-django22 @@ -44,7 +44,7 @@ deps = -rrequirements/requirements-optionals.txt [testenv:lint] -basepython = python2.7 +basepython = python3.7 commands = ./runtests.py --lintonly deps = -rrequirements/requirements-codestyle.txt @@ -52,6 +52,7 @@ deps = [testenv:docs] basepython = python2.7 +skip_install = true commands = mkdocs build deps = -rrequirements/requirements-testing.txt