Skip to content

Dropped Python 2 compatibility. #6615

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/api-guide/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand All @@ -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):
Expand Down
7 changes: 2 additions & 5 deletions rest_framework/authentication.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -33,7 +30,7 @@ def _reject(self, request, reason):
return reason


class BaseAuthentication(object):
class BaseAuthentication:
"""
All authentication classes should extend BaseAuthentication.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
3 changes: 0 additions & 3 deletions rest_framework/authtoken/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models

Expand Down
4 changes: 1 addition & 3 deletions rest_framework/authtoken/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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()
Expand Down
58 changes: 6 additions & 52 deletions rest_framework/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -36,11 +26,6 @@
except ImportError:
ProhibitNullCharactersValidator = None

try:
from unittest import mock
except ImportError:
mock = None


def get_original_route(urlpattern):
"""
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down
5 changes: 1 addition & 4 deletions rest_framework/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__}
)
Expand Down
26 changes: 11 additions & 15 deletions rest_framework/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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:
Expand All @@ -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))
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand Down
Loading