diff --git a/docs/community/contributing.md b/docs/community/contributing.md index c0a9d2dbeb..25bcacafcb 100644 --- a/docs/community/contributing.md +++ b/docs/community/contributing.md @@ -160,6 +160,52 @@ Once you've made a pull request take a look at the build status in the GitHub in Sometimes, in order to ensure your code works on various different versions of Django, Python or third party libraries, you'll need to run slightly different code depending on the environment. Any code that branches in this way should be isolated into the `compat.py` module, and should provide a single common interface that the rest of the codebase can use. +### Deprecating a feature + +REST framework follows a formal [deprecation policy][deprecation-policy]: a feature deprecated during the development of `3.X` keeps working until `3.X+2` removes it. In practice this means that a pull request that deprecates something should not change any behavior, only warn about the change to come. + +The warning classes live in `rest_framework/deprecation.py`, and are named after the release that removes the feature. For example, during the development of 3.0 you'll find: + +* `RemovedInDRF31Warning`, a `DeprecationWarning`, aliased as `RemovedInNextDRFVersionWarning`. Features flagged with it were deprecated one cycle ago, and are removed in the next release. +* `RemovedInDRF32Warning`, a `PendingDeprecationWarning`, aliased as `RemovedAfterNextDRFVersionWarning`. This is the class **new deprecations** should use. + +To deprecate a feature: + +1. Keep the existing behavior working, and raise the pending deprecation warning from the deprecated code path. Refer to the warning class by its concrete name rather than the alias, so that it's easy to grep for everything that has to go when the removal comes around. + + ```python + import warnings + + from rest_framework.deprecation import RemovedInDRF32Warning + + warnings.warn( + "The `foo` argument is deprecated and will be removed in DRF 3.2. " + "Use `bar` instead.", + RemovedInDRF32Warning, + stacklevel=2, + ) + ``` + + The message should say what is deprecated, which release removes it, and what to use instead. Pick a `stacklevel` that points the warning at the user's code rather than at REST framework internals. + +2. If the deprecation can be worked around ahead of time, consider adding a setting that opts into the new behavior early, and mention it in the warning message. + +3. Add a test asserting that the warning is raised, and update the existing tests of the old behavior to expect it: + + ```python + with pytest.warns(RemovedInDRF32Warning, match="Use `bar` instead"): + ... + ``` + +4. Update the documentation for the feature, flagging the deprecation with an admonition: + + ``` + !!! warning + The `foo` argument is deprecated and will be removed in DRF 3.2. Use `bar` instead. + ``` + +Removing a deprecated feature is the mirror image, and happens as part of the release process. When 3.1 is released, everything raising a `RemovedInDRF31Warning` is deleted, along with the class itself and the documentation of the deprecated behavior, `RemovedInDRF32Warning` is escalated to subclass `DeprecationWarning`, a fresh `RemovedInDRF33Warning` is added for the next cycle's deprecations, and the two aliases are moved on to point at them. + ## Documentation The documentation for REST framework is built from the [Markdown][markdown] source files in [the docs directory][docs]. @@ -244,3 +290,4 @@ The documentation theme styles `info`, `warning`, `tip` and `danger` admonition [repo]: https://github.com/encode/django-rest-framework [how-to-fork]: https://help.github.com/articles/fork-a-repo/ [admonition]: https://python-markdown.github.io/extensions/admonition/ +[deprecation-policy]: release-notes.md#deprecation-policy diff --git a/docs/community/project-management.md b/docs/community/project-management.md index 1ac1e00628..8b5c1fc994 100644 --- a/docs/community/project-management.md +++ b/docs/community/project-management.md @@ -58,7 +58,8 @@ Our PyPI releases is automated in GitHub actions on tag pushes. The following te - `pyproject.toml` Python & Django version trove classifiers - `README` Python & Django versions - `docs` Python & Django versions -- Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/encode/django-rest-framework/blob/main/rest_framework/__init__.py). +- Ensure the pull request increments the version to `*.*.*` in [`rest_framework/__init__.py`](https://github.com/encode/django-rest-framework/blob/main/rest_framework/__init__.py). +- Ensure the deprecation warnings in [`rest_framework/deprecation.py`](https://github.com/encode/django-rest-framework/blob/main/rest_framework/deprecation.py) have been rotated for the release, following the [deprecation policy](release-notes.md#deprecation-policy). - Ensure documentation validates - Build and serve docs `mkdocs serve` - Validate links `pylinkvalidate.py -P http://127.0.0.1:8000` diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index a29e63526e..1ba6b4ed04 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -22,8 +22,29 @@ 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. +Two aliases always point at the warning classes for the current release cycle, so you can refer to them without updating your configuration on every release: + +* `RemovedInNextDRFVersionWarning` — features that the next feature release removes. Always a `DeprecationWarning`. +* `RemovedAfterNextDRFVersionWarning` — features that the release after next removes. Always a `PendingDeprecationWarning`. + 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. +### Surfacing deprecations in your test suite + +Deprecations are much easier to deal with one release at a time, so it's worth failing your test suite on anything that is due to be removed in the next release, while keeping the earlier warnings visible. + +If you run your tests with pytest, add the following to your `pyproject.toml`: + +```toml +[tool.pytest] +filterwarnings = [ + "error::rest_framework.deprecation.RemovedInNextDRFVersionWarning", + "always::rest_framework.deprecation.RemovedAfterNextDRFVersionWarning", +] +``` + +When a warning does fire, the message names the release that removes the feature and what to use instead. Once you have dealt with them all, drop the `always` filter down to `error` too, and you'll be ready for the release after next as well. + ## Upgrading To upgrade Django REST framework to the latest version, use pip: diff --git a/rest_framework/deprecation.py b/rest_framework/deprecation.py index ff03c65bf4..ce2be3a2ec 100644 --- a/rest_framework/deprecation.py +++ b/rest_framework/deprecation.py @@ -1,2 +1,27 @@ +""" +Warning classes used to flag features that are scheduled for removal. + +Each `RemovedInDRF3XXWarning` class is named after the release that removes +the feature it flags, so `RemovedInDRF320Warning` marks an API that will stop +working in DRF 3.20. + +New deprecations target the release after next, and therefore start out as a +`PendingDeprecationWarning`, which is silent by default. One release later, +they are escalated to a `DeprecationWarning`, before being removed in the +following release. See the deprecation policy for the full timeline: +https://www.django-rest-framework.org/community/release-notes/#deprecation-policy +""" + + +class RemovedInDRF319Warning(DeprecationWarning): + pass + + class RemovedInDRF320Warning(PendingDeprecationWarning): pass + + +# Aliases that always track the current release cycle, so that projects can +# filter on them without editing their configuration on every DRF release. +RemovedInNextDRFVersionWarning = RemovedInDRF319Warning +RemovedAfterNextDRFVersionWarning = RemovedInDRF320Warning