diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c947a7b8c..2f1aad08f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recom To run the tests, clone the repository, and then: # Setup the virtual environment - virtualenv env + python3 -m venv env source env/bin/activate pip install django pip install -r requirements.txt @@ -115,7 +115,7 @@ It's also useful to remember that if you have an outstanding pull request then p GitHub's documentation for working on pull requests is [available here][pull-requests]. -Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible with both Python 2 and Python 3, and that they run properly on all supported versions of Django. +Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible on all supported versions of Python and Django. Once you've made a pull request take a look at the Travis build status in the GitHub interface and make sure the tests are running as you'd expect. diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 5b8a9844f..52650299f 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -354,7 +354,7 @@ The following third party packages are also available. ## Django OAuth Toolkit -The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**. +The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support and works with Python 3.4+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**. #### Installation & configuration diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index ab042ac03..9b2fc82ed 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -159,7 +159,7 @@ If you want the date field to be entirely hidden from the user, then use `Hidden --- -**Note**: The `UniqueForValidation` classes impose an implicit constraint that the fields they are applied to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input. +**Note**: The `UniqueForValidator` classes impose an implicit constraint that the fields they are applied to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input. --- diff --git a/docs/community/contributing.md b/docs/community/contributing.md index 9cc6ccee0..cb67100d2 100644 --- a/docs/community/contributing.md +++ b/docs/community/contributing.md @@ -65,7 +65,7 @@ Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recom To run the tests, clone the repository, and then: # Setup the virtual environment - virtualenv env + python3 -m venv env source env/bin/activate pip install django pip install -r requirements.txt @@ -121,7 +121,7 @@ It's also useful to remember that if you have an outstanding pull request then p GitHub's documentation for working on pull requests is [available here][pull-requests]. -Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible with both Python 2 and Python 3, and that they run properly on all supported versions of Django. +Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible on all supported versions of Python and Django. Once you've made a pull request take a look at the Travis build status in the GitHub interface and make sure the tests are running as you'd expect. diff --git a/docs/index.md b/docs/index.md index 9f5d3fa15..7adc52dfb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -84,7 +84,7 @@ continued development by **[signing up for a paid plan][funding]**. REST framework requires the following: -* 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/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 224ebf25b..22fe49e39 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -14,18 +14,18 @@ The tutorial is fairly in-depth, so you should probably get a cookie and a cup o ## Setting up a new environment -Before we do anything else we'll create a new virtual environment, using [virtualenv]. This will make sure our package configuration is kept nicely isolated from any other projects we're working on. +Before we do anything else we'll create a new virtual environment, using [venv]. This will make sure our package configuration is kept nicely isolated from any other projects we're working on. - virtualenv env + python3 -m venv env source env/bin/activate -Now that we're inside a virtualenv environment, we can install our package requirements. +Now that we're inside a virtual environment, we can install our package requirements. pip install django pip install djangorestframework pip install pygments # We'll be using this for the code highlighting -**Note:** To exit the virtualenv environment at any time, just type `deactivate`. For more information see the [virtualenv documentation][virtualenv]. +**Note:** To exit the virtual environment at any time, just type `deactivate`. For more information see the [venv documentation][venv]. ## Getting started @@ -372,7 +372,7 @@ We'll see how we can start to improve things in [part 2 of the tutorial][tut-2]. [quickstart]: quickstart.md [repo]: https://github.com/encode/rest-framework-tutorial [sandbox]: https://restframework.herokuapp.com/ -[virtualenv]: http://www.virtualenv.org/en/latest/index.html +[venv]: https://docs.python.org/3/library/venv.html [tut-2]: 2-requests-and-responses.md [httpie]: https://github.com/jakubroztocil/httpie#installation [curl]: https://curl.haxx.se/ diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index cbec2501b..8b02b888e 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -10,11 +10,11 @@ Create a new Django project named `tutorial`, then start a new app called `quick mkdir tutorial cd tutorial - # Create a virtualenv to isolate our package dependencies locally - virtualenv env + # Create a virtual environment to isolate our package dependencies locally + python3 -m venv env source env/bin/activate # On Windows use `env\Scripts\activate` - # Install Django and Django REST framework into the virtualenv + # Install Django and Django REST framework into the virtual environment pip install django pip install djangorestframework diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 0612563e4..1e30728d3 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -6,7 +6,7 @@ import binascii from django.contrib.auth import authenticate, get_user_model from django.middleware.csrf import CsrfViewMiddleware -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import HTTP_HEADER_ENCODING, exceptions diff --git a/rest_framework/authtoken/apps.py b/rest_framework/authtoken/apps.py index ad01cb404..f90fe961e 100644 --- a/rest_framework/authtoken/apps.py +++ b/rest_framework/authtoken/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class AuthTokenConfig(AppConfig): diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 0ed02c415..bff42d3de 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -3,7 +3,7 @@ import os from django.conf import settings from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class Token(models.Model): diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py index e5f46dd66..bb552f3e5 100644 --- a/rest_framework/authtoken/serializers.py +++ b/rest_framework/authtoken/serializers.py @@ -1,5 +1,5 @@ from django.contrib.auth import authenticate -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers diff --git a/rest_framework/compat.py b/rest_framework/compat.py index aad44e342..3068665a8 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -3,7 +3,6 @@ The `compat` module provides support for backwards compatibility with older versions of Django/Python, and compatibility wrappers around optional packages. """ import sys -from collections.abc import Mapping, MutableMapping # noqa from django.conf import settings from django.core import validators diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 8fbdfcd08..a91138026 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -8,8 +8,8 @@ import math from django.http import JsonResponse from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import ungettext +from django.utils.translation import gettext_lazy as _ +from django.utils.translation import ngettext from rest_framework import status from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList @@ -230,9 +230,9 @@ class Throttled(APIException): wait = math.ceil(wait) detail = ' '.join(( detail, - force_text(ungettext(self.extra_detail_singular.format(wait=wait), - self.extra_detail_plural.format(wait=wait), - wait)))) + force_text(ngettext(self.extra_detail_singular.format(wait=wait), + self.extra_detail_plural.format(wait=wait), + wait)))) self.wait = wait super().__init__(detail, code) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index ad9611e05..5e3132074 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -6,6 +6,7 @@ import inspect import re import uuid from collections import OrderedDict +from collections.abc import Mapping from django.conf import settings from django.core.exceptions import ObjectDoesNotExist @@ -25,12 +26,12 @@ from django.utils.formats import localize_input, sanitize_separators from django.utils.functional import lazy from django.utils.ipv6 import clean_ipv6_address from django.utils.timezone import utc -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from pytz.exceptions import InvalidTimeError from rest_framework import ISO_8601 from rest_framework.compat import ( - Mapping, MaxLengthValidator, MaxValueValidator, MinLengthValidator, + MaxLengthValidator, MaxValueValidator, MinLengthValidator, MinValueValidator, ProhibitNullCharactersValidator ) from rest_framework.exceptions import ErrorDetail, ValidationError @@ -1753,7 +1754,7 @@ class JSONField(Field): try: if self.binary or getattr(data, 'is_json_string', False): if isinstance(data, bytes): - data = data.decode('utf-8') + data = data.decode() return json.loads(data) else: json.dumps(data) @@ -1764,10 +1765,7 @@ class JSONField(Field): def to_representation(self, value): if self.binary: value = json.dumps(value) - # On python 2.x the return type for json.dumps() is underspecified. - # On python 3.x json.dumps() returns unicode strings. - if isinstance(value, str): - value = bytes(value.encode('utf-8')) + value = value.encode() return value diff --git a/rest_framework/filters.py b/rest_framework/filters.py index b77069ddc..d5fe36964 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -12,7 +12,7 @@ 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.encoding import force_text -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import RemovedInDRF310Warning from rest_framework.compat import ( diff --git a/rest_framework/management/commands/generateschema.py b/rest_framework/management/commands/generateschema.py index 591073ba0..40909bd04 100644 --- a/rest_framework/management/commands/generateschema.py +++ b/rest_framework/management/commands/generateschema.py @@ -29,7 +29,7 @@ class Command(BaseCommand): renderer = self.get_renderer(options['format']) output = renderer.render(schema, renderer_context={}) - self.stdout.write(output.decode('utf-8')) + self.stdout.write(output.decode()) def get_renderer(self, format): renderer_cls = { diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index fcc78da43..0b2877a45 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -10,7 +10,7 @@ from django.core.paginator import InvalidPage from django.core.paginator import Paginator as DjangoPaginator from django.template import loader from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework.compat import coreapi, coreschema from rest_framework.exceptions import NotFound diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 5b5e3f158..978576a71 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -80,8 +80,7 @@ class FormParser(BaseParser): """ parser_context = parser_context or {} encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) - data = QueryDict(stream.read(), encoding=encoding) - return data + return QueryDict(stream.read(), encoding=encoding) class MultiPartParser(BaseParser): @@ -202,7 +201,7 @@ class FileUploadParser(BaseParser): try: meta = parser_context['request'].META - disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) + disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode()) filename_parm = disposition[1] if 'filename*' in filename_parm: return self.get_encoded_filename(filename_parm) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 76c4d7008..3c2132c5b 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -7,7 +7,7 @@ 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.encoding import smart_text, uri_to_iri -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework.fields import ( Field, empty, get_attribute, is_simple_callable, iter_options diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index eb5da008b..143d1b7e7 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -104,18 +104,11 @@ class JSONRenderer(BaseRenderer): allow_nan=not self.strict, separators=separators ) - # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True, - # but if ensure_ascii=False, the return type is underspecified, - # and may (or may not) be unicode. - # On python 3.x json.dumps() returns unicode strings. - if isinstance(ret, str): - # We always fully escape \u2028 and \u2029 to ensure we output JSON - # that is a strict javascript subset. If bytes were returned - # by json.dumps() then we don't have these characters in any case. - # See: http://timelessrepo.com/json-isnt-a-javascript-subset - ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029') - return bytes(ret.encode('utf-8')) - return ret + # We always fully escape \u2028 and \u2029 to ensure we output JSON + # that is a strict javascript subset. + # See: http://timelessrepo.com/json-isnt-a-javascript-subset + ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029') + return ret.encode() class TemplateHTMLRenderer(BaseRenderer): @@ -574,7 +567,7 @@ class BrowsableAPIRenderer(BaseRenderer): data.pop(name, None) content = renderer.render(data, accepted, context) # Renders returns bytes, but CharField expects a str. - content = content.decode('utf-8') + content = content.decode() else: content = None @@ -681,7 +674,7 @@ class BrowsableAPIRenderer(BaseRenderer): csrf_header_name = csrf_header_name[5:] csrf_header_name = csrf_header_name.replace('_', '-') - context = { + return { 'content': self.get_content(renderer, data, accepted_media_type, renderer_context), 'code_style': pygments_css(self.code_style), 'view': view, @@ -717,7 +710,6 @@ class BrowsableAPIRenderer(BaseRenderer): 'csrf_cookie_name': csrf_cookie_name, 'csrf_header_name': csrf_header_name } - return context def render(self, data, accepted_media_type=None, renderer_context=None): """ @@ -1032,7 +1024,7 @@ class OpenAPIRenderer(_BaseOpenAPIRenderer): def render(self, data, media_type=None, renderer_context=None): structure = self.get_structure(data) - return yaml.dump(structure, default_flow_style=False).encode('utf-8') + return yaml.dump(structure, default_flow_style=False).encode() class JSONOpenAPIRenderer(_BaseOpenAPIRenderer): @@ -1045,4 +1037,4 @@ class JSONOpenAPIRenderer(_BaseOpenAPIRenderer): def render(self, data, media_type=None, renderer_context=None): structure = self.get_structure(data) - return json.dumps(structure, indent=4).encode('utf-8') + return json.dumps(structure, indent=4).encode() diff --git a/rest_framework/schemas/generators.py b/rest_framework/schemas/generators.py index b8da446f7..66afcca94 100644 --- a/rest_framework/schemas/generators.py +++ b/rest_framework/schemas/generators.py @@ -184,9 +184,7 @@ class EndpointEnumerator: ) api_endpoints.extend(nested_endpoints) - api_endpoints = sorted(api_endpoints, key=endpoint_ordering) - - return api_endpoints + return sorted(api_endpoints, key=endpoint_ordering) def get_path_from_regex(self, path_regex): """ @@ -195,8 +193,7 @@ class EndpointEnumerator: path = simplify_regex(path_regex) # Strip Django 2.0 convertors as they are incompatible with uritemplate format - path = re.sub(_PATH_PARAMETER_COMPONENT_RE, r'{\g}', path) - return path + return re.sub(_PATH_PARAMETER_COMPONENT_RE, r'{\g}', path) def should_include_endpoint(self, path, callback): """ diff --git a/rest_framework/schemas/inspectors.py b/rest_framework/schemas/inspectors.py index 91d8405eb..2858c8c5b 100644 --- a/rest_framework/schemas/inspectors.py +++ b/rest_framework/schemas/inspectors.py @@ -11,7 +11,7 @@ from weakref import WeakKeyDictionary from django.db import models from django.utils.encoding import force_text, smart_text -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import exceptions, serializers from rest_framework.compat import coreapi, coreschema, uritemplate @@ -435,8 +435,7 @@ class AutoSchema(ViewInspector): by_name = OrderedDict((f.name, f) for f in fields) for f in update_with: by_name[f.name] = f - fields = list(by_name.values()) - return fields + return list(by_name.values()) def get_encoding(self, path, method): """ diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 90b31e068..742fa6577 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -14,6 +14,7 @@ import copy import inspect import traceback from collections import OrderedDict +from collections.abc import Mapping from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ValidationError as DjangoValidationError @@ -23,9 +24,9 @@ from django.db.models.fields import Field as DjangoModelField from django.db.models.fields import FieldDoesNotExist from django.utils import timezone from django.utils.functional import cached_property -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ -from rest_framework.compat import Mapping, postgres_fields +from rest_framework.compat import postgres_fields from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.fields import get_error_detail, set_value from rest_framework.settings import api_settings diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 5d92d0cb4..1d5dc036f 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -18,10 +18,9 @@ This module provides the `api_setting` object, that is used to access REST framework settings, checking for user settings first, then falling back to the defaults. """ -from importlib import import_module - from django.conf import settings from django.test.signals import setting_changed +from django.utils.module_loading import import_string from rest_framework import ISO_8601 @@ -175,11 +174,8 @@ def import_from_string(val, setting_name): Attempt to import a class from a string representation. """ try: - # Nod to tastypie's use of importlib. - module_path, class_name = val.rsplit('.', 1) - module = import_module(module_path) - return getattr(module, class_name) - except (ImportError, AttributeError) as e: + return import_string(val) + except ImportError as e: msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e) raise ImportError(msg) diff --git a/rest_framework/templates/rest_framework/docs/auth/session.html b/rest_framework/templates/rest_framework/docs/auth/session.html index d09d3f2aa..59430d95e 100644 --- a/rest_framework/templates/rest_framework/docs/auth/session.html +++ b/rest_framework/templates/rest_framework/docs/auth/session.html @@ -12,7 +12,7 @@