Introduce RemovedInDRF…Warning classes to simplify deprecations. (#6480)

Closes #6290.
This commit is contained in:
Carlton Gibson 2019-03-03 09:20:45 +01:00 committed by GitHub
parent a216d02ce0
commit 94593b3a50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 34 additions and 20 deletions

View File

@ -18,9 +18,9 @@ REST framework releases follow a formal deprecation policy, which is in line wit
The timeline for deprecation of a feature present in version 1.0 would work as follows: The timeline for deprecation of a feature present in version 1.0 would work as follows:
* Version 1.1 would remain **fully backwards compatible** with 1.0, but would raise `PendingDeprecationWarning` warnings if you use the feature that are due to be deprecated. These warnings are **silent by default**, but can be explicitly enabled when you're ready to start migrating any required changes. For example if you start running your tests using `python -Wd manage.py test`, you'll be warned of any API changes you need to make. * Version 1.1 would remain **fully backwards compatible** with 1.0, but would raise `RemovedInDRF13Warning` warnings, subclassing `PendingDeprecationWarning`, if you use the feature that are due to be deprecated. These warnings are **silent by default**, but can be explicitly enabled when you're ready to start migrating any required changes. For example if you start running your tests using `python -Wd manage.py test`, you'll be warned of any API changes you need to make.
* Version 1.2 would escalate these warnings to `DeprecationWarning`, which is loud by default. * Version 1.2 would escalate these warnings to subclass `DeprecationWarning`, which is loud by default.
* Version 1.3 would remove the deprecated bits of API entirely. * Version 1.3 would remove the deprecated bits of API entirely.

View File

@ -23,3 +23,11 @@ HTTP_HEADER_ENCODING = 'iso-8859-1'
ISO_8601 = 'iso-8601' ISO_8601 = 'iso-8601'
default_app_config = 'rest_framework.apps.RestFrameworkConfig' default_app_config = 'rest_framework.apps.RestFrameworkConfig'
class RemovedInDRF310Warning(DeprecationWarning):
pass
class RemovedInDRF311Warning(PendingDeprecationWarning):
pass

View File

@ -14,6 +14,7 @@ import warnings
from django.forms.utils import pretty_name from django.forms.utils import pretty_name
from django.utils import six from django.utils import six
from rest_framework import RemovedInDRF310Warning
from rest_framework.views import APIView from rest_framework.views import APIView
@ -225,7 +226,7 @@ def detail_route(methods=None, **kwargs):
warnings.warn( warnings.warn(
"`detail_route` is deprecated and will be removed in 3.10 in favor of " "`detail_route` is deprecated and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=True)` instead.", "`action`, which accepts a `detail` bool. Use `@action(detail=True)` instead.",
DeprecationWarning, stacklevel=2 RemovedInDRF310Warning, stacklevel=2
) )
def decorator(func): def decorator(func):
@ -243,7 +244,7 @@ def list_route(methods=None, **kwargs):
warnings.warn( warnings.warn(
"`list_route` is deprecated and will be removed in 3.10 in favor of " "`list_route` is deprecated and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=False)` instead.", "`action`, which accepts a `detail` bool. Use `@action(detail=False)` instead.",
DeprecationWarning, stacklevel=2 RemovedInDRF310Warning, stacklevel=2
) )
def decorator(func): def decorator(func):

View File

@ -17,6 +17,7 @@ from django.utils import six
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import RemovedInDRF310Warning
from rest_framework.compat import ( from rest_framework.compat import (
coreapi, coreschema, distinct, is_guardian_installed coreapi, coreschema, distinct, is_guardian_installed
) )
@ -299,7 +300,7 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend):
warnings.warn( warnings.warn(
"`DjangoObjectPermissionsFilter` has been deprecated and moved to " "`DjangoObjectPermissionsFilter` has been deprecated and moved to "
"the 3rd-party django-rest-framework-guardian package.", "the 3rd-party django-rest-framework-guardian package.",
DeprecationWarning, stacklevel=2 RemovedInDRF310Warning, stacklevel=2
) )
assert is_guardian_installed(), 'Using DjangoObjectPermissionsFilter, but django-guardian is not installed' assert is_guardian_installed(), 'Using DjangoObjectPermissionsFilter, but django-guardian is not installed'

View File

@ -25,7 +25,9 @@ from django.urls import NoReverseMatch
from django.utils import six from django.utils import six
from django.utils.deprecation import RenameMethodsBase from django.utils.deprecation import RenameMethodsBase
from rest_framework import views from rest_framework import (
RemovedInDRF310Warning, RemovedInDRF311Warning, views
)
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.reverse import reverse from rest_framework.reverse import reverse
from rest_framework.schemas import SchemaGenerator from rest_framework.schemas import SchemaGenerator
@ -43,7 +45,7 @@ class DynamicDetailRoute(object):
"`DynamicDetailRoute` is deprecated and will be removed in 3.10 " "`DynamicDetailRoute` is deprecated and will be removed in 3.10 "
"in favor of `DynamicRoute`, which accepts a `detail` boolean. Use " "in favor of `DynamicRoute`, which accepts a `detail` boolean. Use "
"`DynamicRoute(url, name, True, initkwargs)` instead.", "`DynamicRoute(url, name, True, initkwargs)` instead.",
DeprecationWarning, stacklevel=2 RemovedInDRF310Warning, stacklevel=2
) )
return DynamicRoute(url, name, True, initkwargs) return DynamicRoute(url, name, True, initkwargs)
@ -54,7 +56,7 @@ class DynamicListRoute(object):
"`DynamicListRoute` is deprecated and will be removed in 3.10 in " "`DynamicListRoute` is deprecated and will be removed in 3.10 in "
"favor of `DynamicRoute`, which accepts a `detail` boolean. Use " "favor of `DynamicRoute`, which accepts a `detail` boolean. Use "
"`DynamicRoute(url, name, False, initkwargs)` instead.", "`DynamicRoute(url, name, False, initkwargs)` instead.",
DeprecationWarning, stacklevel=2 RemovedInDRF310Warning, stacklevel=2
) )
return DynamicRoute(url, name, False, initkwargs) return DynamicRoute(url, name, False, initkwargs)
@ -77,7 +79,7 @@ def flatten(list_of_lists):
class RenameRouterMethods(RenameMethodsBase): class RenameRouterMethods(RenameMethodsBase):
renamed_methods = ( renamed_methods = (
('get_default_base_name', 'get_default_basename', PendingDeprecationWarning), ('get_default_base_name', 'get_default_basename', RemovedInDRF311Warning),
) )
@ -88,7 +90,7 @@ class BaseRouter(six.with_metaclass(RenameRouterMethods)):
def register(self, prefix, viewset, basename=None, base_name=None): def register(self, prefix, viewset, basename=None, base_name=None):
if base_name is not None: if base_name is not None:
msg = "The `base_name` argument is pending deprecation in favor of `basename`." msg = "The `base_name` argument is pending deprecation in favor of `basename`."
warnings.warn(msg, PendingDeprecationWarning, 2) warnings.warn(msg, RemovedInDRF311Warning, 2)
assert not (basename and base_name), ( assert not (basename and base_name), (
"Do not provide both the `basename` and `base_name` arguments.") "Do not provide both the `basename` and `base_name` arguments.")

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
import pytest import pytest
from django.test import TestCase from django.test import TestCase
from rest_framework import status from rest_framework import RemovedInDRF310Warning, status
from rest_framework.authentication import BasicAuthentication from rest_framework.authentication import BasicAuthentication
from rest_framework.decorators import ( from rest_framework.decorators import (
action, api_view, authentication_classes, detail_route, list_route, action, api_view, authentication_classes, detail_route, list_route,
@ -290,7 +290,7 @@ class ActionDecoratorTestCase(TestCase):
raise NotImplementedError raise NotImplementedError
def test_detail_route_deprecation(self): def test_detail_route_deprecation(self):
with pytest.warns(DeprecationWarning) as record: with pytest.warns(RemovedInDRF310Warning) as record:
@detail_route() @detail_route()
def view(request): def view(request):
raise NotImplementedError raise NotImplementedError
@ -303,7 +303,7 @@ class ActionDecoratorTestCase(TestCase):
) )
def test_list_route_deprecation(self): def test_list_route_deprecation(self):
with pytest.warns(DeprecationWarning) as record: with pytest.warns(RemovedInDRF310Warning) as record:
@list_route() @list_route()
def view(request): def view(request):
raise NotImplementedError raise NotImplementedError
@ -317,7 +317,7 @@ class ActionDecoratorTestCase(TestCase):
def test_route_url_name_from_path(self): def test_route_url_name_from_path(self):
# pre-3.8 behavior was to base the `url_name` off of the `url_path` # pre-3.8 behavior was to base the `url_name` off of the `url_path`
with pytest.warns(DeprecationWarning): with pytest.warns(RemovedInDRF310Warning):
@list_route(url_path='foo_bar') @list_route(url_path='foo_bar')
def view(request): def view(request):
raise NotImplementedError raise NotImplementedError

View File

@ -12,8 +12,8 @@ from django.test import TestCase
from django.urls import ResolverMatch from django.urls import ResolverMatch
from rest_framework import ( from rest_framework import (
HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers, HTTP_HEADER_ENCODING, RemovedInDRF310Warning, authentication, generics,
status, views permissions, serializers, status, views
) )
from rest_framework.compat import PY36, is_guardian_installed, mock from rest_framework.compat import PY36, is_guardian_installed, mock
from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.filters import DjangoObjectPermissionsFilter
@ -427,7 +427,7 @@ class ObjectPermissionsIntegrationTests(TestCase):
message = ("`DjangoObjectPermissionsFilter` has been deprecated and moved " message = ("`DjangoObjectPermissionsFilter` has been deprecated and moved "
"to the 3rd-party django-rest-framework-guardian package.") "to the 3rd-party django-rest-framework-guardian package.")
self.assertEqual(len(w), 1) self.assertEqual(len(w), 1)
self.assertIs(w[-1].category, DeprecationWarning) self.assertIs(w[-1].category, RemovedInDRF310Warning)
self.assertEqual(str(w[-1].message), message) self.assertEqual(str(w[-1].message), message)
def test_can_read_list_permissions(self): def test_can_read_list_permissions(self):

View File

@ -10,7 +10,9 @@ from django.db import models
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.urls import resolve, reverse from django.urls import resolve, reverse
from rest_framework import permissions, serializers, viewsets from rest_framework import (
RemovedInDRF311Warning, permissions, serializers, viewsets
)
from rest_framework.compat import get_regex_pattern from rest_framework.compat import get_regex_pattern
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
@ -508,7 +510,7 @@ class TestBaseNameRename(TestCase):
def test_base_name_argument_deprecation(self): def test_base_name_argument_deprecation(self):
router = SimpleRouter() router = SimpleRouter()
with pytest.warns(PendingDeprecationWarning) as w: with pytest.warns(RemovedInDRF311Warning) as w:
warnings.simplefilter('always') warnings.simplefilter('always')
router.register('mock', MockViewSet, base_name='mock') router.register('mock', MockViewSet, base_name='mock')
@ -535,7 +537,7 @@ class TestBaseNameRename(TestCase):
msg = "`CustomRouter.get_default_base_name` method should be renamed `get_default_basename`." msg = "`CustomRouter.get_default_base_name` method should be renamed `get_default_basename`."
# Class definition should raise a warning # Class definition should raise a warning
with pytest.warns(PendingDeprecationWarning) as w: with pytest.warns(RemovedInDRF311Warning) as w:
warnings.simplefilter('always') warnings.simplefilter('always')
class CustomRouter(SimpleRouter): class CustomRouter(SimpleRouter):