From 8ac78daf18c2abd6a973a1af05bd0d98c9ad7f9a Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 15 Sep 2015 11:04:51 +0200 Subject: [PATCH 1/8] Add `deprecated` function and apply. --- rest_framework/compat.py | 19 +++++++++++++++++++ rest_framework/pagination.py | 15 +++++---------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 460795f65..253e331fe 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -6,12 +6,31 @@ versions of Django/Python, and compatibility wrappers around optional packages. # flake8: noqa from __future__ import unicode_literals +import warnings + import django from django.conf import settings from django.db import connection, transaction from django.utils import six from django.views.generic import View +from rest_framework import VERSION + +def deprecated(since, message): + current_version = [int(i) for i in VERSION.split('.')] + + assert current_version[0] == since[0], "Deprecated code must be removed before major version change. Current: {0} vs Deprecated Since: {1}".format(current_version[0], since[0]) + + minor_version_difference = current_version[1] - since[1] + assert minor_version_difference in [1,2], "Deprecated code must be removed within two minor versions" + + warning_type = PendingDeprecationWarning if minor_version_difference == 1 else DeprecationWarning + + warnings.warn(message, warning_type) + + + + try: import importlib # Available in Python 3.1+ except ImportError: diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index bf72ef4fc..911e9adf5 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -5,7 +5,6 @@ be used for paginated responses. """ from __future__ import unicode_literals -import warnings from base64 import b64decode, b64encode from collections import namedtuple @@ -16,7 +15,7 @@ from django.utils import six from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import OrderedDict +from rest_framework.compat import OrderedDict, deprecated from rest_framework.exceptions import NotFound from rest_framework.response import Response from rest_framework.settings import api_settings @@ -216,12 +215,10 @@ class PageNumberPagination(BasePagination): value = getattr(api_settings, settings_key, None) if value is not None: setattr(self, attr_name, value) - warnings.warn( - "The `%s` settings key is deprecated. " + deprecated((3,0,0), "The `%s` settings key is deprecated. " "Use the `%s` attribute on the pagination class instead." % ( settings_key, attr_name - ), - DeprecationWarning, + ) ) for (view_attr, attr_name) in ( @@ -233,12 +230,10 @@ class PageNumberPagination(BasePagination): value = getattr(view, view_attr, None) if value is not None: setattr(self, attr_name, value) - warnings.warn( - "The `%s` view attribute is deprecated. " + deprecated((3,0,0), "The `%s` view attribute is deprecated. " "Use the `%s` attribute on the pagination class instead." % ( view_attr, attr_name - ), - DeprecationWarning, + ) ) def paginate_queryset(self, queryset, request, view=None): From 968832043a74671685753f6530bbd1b0cc391ce5 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 15 Sep 2015 11:13:56 +0200 Subject: [PATCH 2/8] Add note on use of `deprecated` --- docs/topics/release-notes.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 68803a5b1..5eb241a07 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -24,6 +24,12 @@ The timeline for deprecation of a feature present in version 1.0 would work as f * Version 1.3 would remove the deprecated bits of API entirely. +Deprecations are marked using `rest_framework.compat.deprecated`, which accepts a version tuple for the version when code is first deprecated and message to pass to the `warnings` module: + + from rest_framework.compat import deprecated + ... + deprecated((3,1,0), "Using X for Y is deprecated. Prefer Z") + Note that in line with Django's policy, any parts of the framework not mentioned in the documentation should generally be considered private API, and may be subject to change. ## Upgrading From 9a6c0611ac4b449853e4eefc8d58a6378f17ab3c Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 15 Sep 2015 11:29:19 +0200 Subject: [PATCH 3/8] flake8 and isort fixes --- rest_framework/compat.py | 1 + rest_framework/pagination.py | 20 ++++++++++---------- tests/test_atomic_requests.py | 2 +- tests/test_generics.py | 6 +++--- tests/test_multitable_inheritance.py | 2 +- tests/test_permissions.py | 2 +- tests/test_relations_hyperlink.py | 6 +++--- tests/test_relations_pk.py | 4 ++-- tests/test_relations_slug.py | 4 ++-- tests/test_response.py | 2 +- tests/test_utils.py | 2 +- 11 files changed, 26 insertions(+), 25 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 253e331fe..b603b190a 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -16,6 +16,7 @@ from django.views.generic import View from rest_framework import VERSION + def deprecated(since, message): current_version = [int(i) for i in VERSION.split('.')] diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 911e9adf5..4730623f7 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -215,11 +215,11 @@ class PageNumberPagination(BasePagination): value = getattr(api_settings, settings_key, None) if value is not None: setattr(self, attr_name, value) - deprecated((3,0,0), "The `%s` settings key is deprecated. " - "Use the `%s` attribute on the pagination class instead." % ( - settings_key, attr_name - ) - ) + deprecated((3, 0, 0), + "The `%s` settings key is deprecated. " + "Use the `%s` attribute on the pagination class instead." % ( + settings_key, attr_name) + ) for (view_attr, attr_name) in ( ('paginate_by', 'page_size'), @@ -230,11 +230,11 @@ class PageNumberPagination(BasePagination): value = getattr(view, view_attr, None) if value is not None: setattr(self, attr_name, value) - deprecated((3,0,0), "The `%s` view attribute is deprecated. " - "Use the `%s` attribute on the pagination class instead." % ( - view_attr, attr_name - ) - ) + deprecated((3, 0, 0), + "The `%s` view attribute is deprecated. " + "Use the `%s` attribute on the pagination class instead." % ( + view_attr, attr_name) + ) def paginate_queryset(self, queryset, request, view=None): """ diff --git a/tests/test_atomic_requests.py b/tests/test_atomic_requests.py index d0d088f52..89bc0ac66 100644 --- a/tests/test_atomic_requests.py +++ b/tests/test_atomic_requests.py @@ -6,13 +6,13 @@ from django.http import Http404 from django.test import TestCase, TransactionTestCase from django.utils.decorators import method_decorator from django.utils.unittest import skipUnless +from tests.models import BasicModel from rest_framework import status from rest_framework.exceptions import APIException from rest_framework.response import Response from rest_framework.test import APIRequestFactory from rest_framework.views import APIView -from tests.models import BasicModel factory = APIRequestFactory() diff --git a/tests/test_generics.py b/tests/test_generics.py index 000adffa7..4398febc3 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -6,13 +6,13 @@ from django.db import models from django.shortcuts import get_object_or_404 from django.test import TestCase from django.utils import six +from tests.models import ( + BasicModel, ForeignKeySource, ForeignKeyTarget, RESTFrameworkModel +) from rest_framework import generics, renderers, serializers, status from rest_framework.response import Response from rest_framework.test import APIRequestFactory -from tests.models import ( - BasicModel, ForeignKeySource, ForeignKeyTarget, RESTFrameworkModel -) factory = APIRequestFactory() diff --git a/tests/test_multitable_inheritance.py b/tests/test_multitable_inheritance.py index 340d4966a..5867f1966 100644 --- a/tests/test_multitable_inheritance.py +++ b/tests/test_multitable_inheritance.py @@ -2,9 +2,9 @@ from __future__ import unicode_literals from django.db import models from django.test import TestCase +from tests.models import RESTFrameworkModel from rest_framework import serializers -from tests.models import RESTFrameworkModel # Models diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 398020002..ac0ecab90 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -7,6 +7,7 @@ from django.core.urlresolvers import ResolverMatch from django.db import models from django.test import TestCase from django.utils import unittest +from tests.models import BasicModel from rest_framework import ( HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers, @@ -16,7 +17,6 @@ from rest_framework.compat import get_model_name, guardian from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.routers import DefaultRouter from rest_framework.test import APIRequestFactory -from tests.models import BasicModel factory = APIRequestFactory() diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index c0642eda2..b203884d8 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -2,14 +2,14 @@ from __future__ import unicode_literals from django.conf.urls import url from django.test import TestCase - -from rest_framework import serializers -from rest_framework.test import APIRequestFactory from tests.models import ( ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget, NullableForeignKeySource, NullableOneToOneSource, OneToOneTarget ) +from rest_framework import serializers +from rest_framework.test import APIRequestFactory + factory = APIRequestFactory() request = factory.get('/') # Just to ensure we have a request in the serializer context diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 169f7d9c5..fb797e993 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -2,13 +2,13 @@ from __future__ import unicode_literals from django.test import TestCase from django.utils import six - -from rest_framework import serializers from tests.models import ( ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget, NullableForeignKeySource, NullableOneToOneSource, OneToOneTarget ) +from rest_framework import serializers + # ManyToMany class ManyToManyTargetSerializer(serializers.ModelSerializer): diff --git a/tests/test_relations_slug.py b/tests/test_relations_slug.py index 680aee417..cee18031d 100644 --- a/tests/test_relations_slug.py +++ b/tests/test_relations_slug.py @@ -1,10 +1,10 @@ from django.test import TestCase - -from rest_framework import serializers from tests.models import ( ForeignKeySource, ForeignKeyTarget, NullableForeignKeySource ) +from rest_framework import serializers + class ForeignKeyTargetSerializer(serializers.ModelSerializer): sources = serializers.SlugRelatedField( diff --git a/tests/test_response.py b/tests/test_response.py index 1dd5d5ea0..84b0935d7 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from django.conf.urls import include, url from django.test import TestCase from django.utils import six +from tests.models import BasicModel from rest_framework import generics, routers, serializers, status, viewsets from rest_framework.renderers import ( @@ -11,7 +12,6 @@ from rest_framework.renderers import ( from rest_framework.response import Response from rest_framework.settings import api_settings from rest_framework.views import APIView -from tests.models import BasicModel # Serializer used to test BasicModel diff --git a/tests/test_utils.py b/tests/test_utils.py index 062f78e11..acda7adf8 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,12 +4,12 @@ from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured from django.test import TestCase from django.utils import six +from tests.models import BasicModel import rest_framework.utils.model_meta from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework.utils.model_meta import _resolve_model from rest_framework.views import APIView -from tests.models import BasicModel class Root(APIView): From 589e54d73f797235a9c0a235bca5c21ec497c85b Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 6 Oct 2015 21:53:06 +0200 Subject: [PATCH 4/8] Fix import order. --- tests/test_atomic_requests.py | 2 +- tests/test_generics.py | 6 +++--- tests/test_multitable_inheritance.py | 2 +- tests/test_permissions.py | 2 +- tests/test_relations_hyperlink.py | 6 +++--- tests/test_relations_pk.py | 4 ++-- tests/test_response.py | 2 +- tests/test_utils.py | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/test_atomic_requests.py b/tests/test_atomic_requests.py index 3e1d41725..8342ad3af 100644 --- a/tests/test_atomic_requests.py +++ b/tests/test_atomic_requests.py @@ -7,13 +7,13 @@ from django.db import connection, connections, transaction from django.http import Http404 from django.test import TestCase, TransactionTestCase from django.utils.decorators import method_decorator -from tests.models import BasicModel from rest_framework import status from rest_framework.exceptions import APIException from rest_framework.response import Response from rest_framework.test import APIRequestFactory from rest_framework.views import APIView +from tests.models import BasicModel factory = APIRequestFactory() diff --git a/tests/test_generics.py b/tests/test_generics.py index 4398febc3..000adffa7 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -6,13 +6,13 @@ from django.db import models from django.shortcuts import get_object_or_404 from django.test import TestCase from django.utils import six -from tests.models import ( - BasicModel, ForeignKeySource, ForeignKeyTarget, RESTFrameworkModel -) from rest_framework import generics, renderers, serializers, status from rest_framework.response import Response from rest_framework.test import APIRequestFactory +from tests.models import ( + BasicModel, ForeignKeySource, ForeignKeyTarget, RESTFrameworkModel +) factory = APIRequestFactory() diff --git a/tests/test_multitable_inheritance.py b/tests/test_multitable_inheritance.py index 5867f1966..340d4966a 100644 --- a/tests/test_multitable_inheritance.py +++ b/tests/test_multitable_inheritance.py @@ -2,9 +2,9 @@ from __future__ import unicode_literals from django.db import models from django.test import TestCase -from tests.models import RESTFrameworkModel from rest_framework import serializers +from tests.models import RESTFrameworkModel # Models diff --git a/tests/test_permissions.py b/tests/test_permissions.py index f36b8f4da..e04c72ec9 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -7,7 +7,6 @@ from django.contrib.auth.models import Group, Permission, User from django.core.urlresolvers import ResolverMatch from django.db import models from django.test import TestCase -from tests.models import BasicModel from rest_framework import ( HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers, @@ -17,6 +16,7 @@ from rest_framework.compat import guardian from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.routers import DefaultRouter from rest_framework.test import APIRequestFactory +from tests.models import BasicModel factory = APIRequestFactory() diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index b203884d8..c0642eda2 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -2,14 +2,14 @@ from __future__ import unicode_literals from django.conf.urls import url from django.test import TestCase + +from rest_framework import serializers +from rest_framework.test import APIRequestFactory from tests.models import ( ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget, NullableForeignKeySource, NullableOneToOneSource, OneToOneTarget ) -from rest_framework import serializers -from rest_framework.test import APIRequestFactory - factory = APIRequestFactory() request = factory.get('/') # Just to ensure we have a request in the serializer context diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index fb797e993..169f7d9c5 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -2,13 +2,13 @@ from __future__ import unicode_literals from django.test import TestCase from django.utils import six + +from rest_framework import serializers from tests.models import ( ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget, NullableForeignKeySource, NullableOneToOneSource, OneToOneTarget ) -from rest_framework import serializers - # ManyToMany class ManyToManyTargetSerializer(serializers.ModelSerializer): diff --git a/tests/test_response.py b/tests/test_response.py index 595227bd7..df2d7b4ec 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals from django.conf.urls import include, url from django.test import TestCase from django.utils import six -from tests.models import BasicModel from rest_framework import generics, routers, serializers, status, viewsets from rest_framework.parsers import JSONParser @@ -12,6 +11,7 @@ from rest_framework.renderers import ( ) from rest_framework.response import Response from rest_framework.views import APIView +from tests.models import BasicModel # Serializer used to test BasicModel diff --git a/tests/test_utils.py b/tests/test_utils.py index 5d3cdad7e..fdc61a4aa 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,12 +4,12 @@ from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured from django.test import TestCase from django.utils import six -from tests.models import BasicModel import rest_framework.utils.model_meta from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework.utils.model_meta import _resolve_model from rest_framework.views import APIView +from tests.models import BasicModel class Root(APIView): From 3e2a07d5d61983b8a2cd9b9eedae7772d15b36bb Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 6 Oct 2015 21:55:04 +0200 Subject: [PATCH 5/8] Fix import order. --- tests/test_relations_slug.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_relations_slug.py b/tests/test_relations_slug.py index cee18031d..680aee417 100644 --- a/tests/test_relations_slug.py +++ b/tests/test_relations_slug.py @@ -1,10 +1,10 @@ from django.test import TestCase + +from rest_framework import serializers from tests.models import ( ForeignKeySource, ForeignKeyTarget, NullableForeignKeySource ) -from rest_framework import serializers - class ForeignKeyTargetSerializer(serializers.ModelSerializer): sources = serializers.SlugRelatedField( From 10c72132bf5d3bbd379a2dbef872e0c32d4031e8 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 6 Oct 2015 23:36:02 +0200 Subject: [PATCH 6/8] small `deprecated refactor --- rest_framework/compat.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index c1a8bcaed..6de823b8a 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -14,24 +14,34 @@ from django.db import connection, transaction from django.utils import six from django.views.generic import View -from rest_framework import VERSION +import rest_framework def deprecated(since, message): - current_version = [int(i) for i in VERSION.split('.')] + """ + Issue a deprecation warning `message`. `since` is a tuple of major, minor + indicating when the deprecation should start. + """ + current_version = [int(i) for i in rest_framework.VERSION.split('.')] - assert current_version[0] == since[0], "Deprecated code must be removed before major version change. Current: {0} vs Deprecated Since: {1}".format(current_version[0], since[0]) + major_message = ( + "Deprecated code must be removed before major version change. " + "Current: {0} vs Deprecated Since: {1}".format( + current_version[0], since[0]) + ) + minor_message = "Deprecated code must be removed within two minor versions" + + assert current_version[0] == since[0], major_message minor_version_difference = current_version[1] - since[1] - assert minor_version_difference in [1,2], "Deprecated code must be removed within two minor versions" - - warning_type = PendingDeprecationWarning if minor_version_difference == 1 else DeprecationWarning + assert minor_version_difference in [1, 2], minor_message + warning_type = DeprecationWarning + if minor_version_difference == 1: + warning_type = PendingDeprecationWarning warnings.warn(message, warning_type) - - try: import importlib # Available in Python 3.1+ except ImportError: From 852b6a97b8fcf1dc3693bf2c303157877696868f Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 10 Nov 2015 12:35:20 +0100 Subject: [PATCH 7/8] Remove unused import. --- rest_framework/pagination.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 4d2811051..fc7b90967 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -15,7 +15,6 @@ from django.utils import six from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import deprecated from rest_framework.exceptions import NotFound from rest_framework.response import Response from rest_framework.settings import api_settings From c79f4f5b3bddfc80d56d9aa3471f1dec0dd99524 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 10 Nov 2015 12:36:16 +0100 Subject: [PATCH 8/8] Update call to deprecated to use only major and minor (micro removed). --- docs/topics/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 53fed946c..28ada3be8 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -28,7 +28,7 @@ Deprecations are marked using `rest_framework.compat.deprecated`, which accepts from rest_framework.compat import deprecated ... - deprecated((3,1,0), "Using X for Y is deprecated. Prefer Z") + deprecated((3, 1), "Using X for Y is deprecated. Prefer Z") Note that in line with Django's policy, any parts of the framework not mentioned in the documentation should generally be considered private API, and may be subject to change.