Skip to content

Commit

Permalink
Dropped Python 2 compatibility. (encode#6615)
Browse files Browse the repository at this point in the history
Thanks to Jon Dufresne (@jdufresne) for review.

Co-authored-by: Asif Saif Uddin <[email protected]>
Co-authored-by: Rizwan Mansuri <[email protected]>
  • Loading branch information
3 people authored and Pierre Chiquet committed Mar 24, 2020
1 parent f99137e commit 2ce4059
Show file tree
Hide file tree
Showing 111 changed files with 473 additions and 795 deletions.
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

0 comments on commit 2ce4059

Please sign in to comment.