Skip to content

Commit

Permalink
Categorize deprecation warnings to PytestRemovedInXWarning
Browse files Browse the repository at this point in the history
Closes #7480.

This allows us to more easily follow our deprecation policy of turning
warnings into errors for the X.0 releases before complete removal in
X.1.

It also makes the deprecation timeline clear to both the users and
pytest developers -- it can be hard to keep track.

Note that the designation is not meant to be a binding contract - if the
time comes for removal of a specific deprecation but we decide it's too
soon, can just bump it to the next major.

Inspired by Django:
https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/submitting-patches/#deprecating-a-feature
  • Loading branch information
bluetech committed Nov 14, 2021
1 parent 3dc17f1 commit 128f29e
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 25 deletions.
5 changes: 5 additions & 0 deletions changelog/7480.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
A deprecation scheduled to be removed in a major version X (e.g. pytest 7, 8, 9, ...) now uses warning category `PytestRemovedInXWarning`,
a subclass of :class:`~pytest.PytestDeprecationWarning`,
instead of :class:`PytestDeprecationWarning` directly.

See :ref:`backwards-compatibility` for more details.
4 changes: 3 additions & 1 deletion doc/en/backwards-compatibility.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ b) transitional: the old and new API don't conflict

We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).

When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn them into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.
A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of `~pytest.PytestDeprecationwarning`).

When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn `PytestRemovedInXWarning` (e.g. `PytestRemovedIn4Warning`) into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.


c) true breakage: should only be considered when normal transition is unreasonably unsustainable and would offset important development/features by years.
Expand Down
39 changes: 22 additions & 17 deletions src/_pytest/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from warnings import warn

from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import PytestRemovedIn7Warning
from _pytest.warning_types import PytestRemovedIn8Warning
from _pytest.warning_types import UnformattedWarning

# set of plugins which have been integrated into the core; we use this list to ignore
Expand All @@ -23,103 +25,106 @@


FILLFUNCARGS = UnformattedWarning(
PytestDeprecationWarning,
PytestRemovedIn7Warning,
"{name} is deprecated, use "
"function._request._fillfixtures() instead if you cannot avoid reaching into internals.",
)

PYTEST_COLLECT_MODULE = UnformattedWarning(
PytestDeprecationWarning,
PytestRemovedIn7Warning,
"pytest.collect.{name} was moved to pytest.{name}\n"
"Please update to the new name.",
)

# This can be* removed pytest 8, but it's harmless and common, so no rush to remove.
# * If you're in the future: "could have been".
YIELD_FIXTURE = PytestDeprecationWarning(
"@pytest.yield_fixture is deprecated.\n"
"Use @pytest.fixture instead; they are the same."
)

MINUS_K_DASH = PytestDeprecationWarning(
MINUS_K_DASH = PytestRemovedIn7Warning(
"The `-k '-expr'` syntax to -k is deprecated.\nUse `-k 'not expr'` instead."
)

MINUS_K_COLON = PytestDeprecationWarning(
MINUS_K_COLON = PytestRemovedIn7Warning(
"The `-k 'expr:'` syntax to -k is deprecated.\n"
"Please open an issue if you use this and want a replacement."
)

WARNING_CAPTURED_HOOK = PytestDeprecationWarning(
WARNING_CAPTURED_HOOK = PytestRemovedIn7Warning(
"The pytest_warning_captured is deprecated and will be removed in a future release.\n"
"Please use pytest_warning_recorded instead."
)

WARNING_CMDLINE_PREPARSE_HOOK = PytestDeprecationWarning(
WARNING_CMDLINE_PREPARSE_HOOK = PytestRemovedIn8Warning(
"The pytest_cmdline_preparse hook is deprecated and will be removed in a future release. \n"
"Please use pytest_load_initial_conftests hook instead."
)

FSCOLLECTOR_GETHOOKPROXY_ISINITPATH = PytestDeprecationWarning(
FSCOLLECTOR_GETHOOKPROXY_ISINITPATH = PytestRemovedIn8Warning(
"The gethookproxy() and isinitpath() methods of FSCollector and Package are deprecated; "
"use self.session.gethookproxy() and self.session.isinitpath() instead. "
)

STRICT_OPTION = PytestDeprecationWarning(
STRICT_OPTION = PytestRemovedIn8Warning(
"The --strict option is deprecated, use --strict-markers instead."
)

# This deprecation is never really meant to be removed.
PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.")

UNITTEST_SKIP_DURING_COLLECTION = PytestDeprecationWarning(
UNITTEST_SKIP_DURING_COLLECTION = PytestRemovedIn8Warning(
"Raising unittest.SkipTest to skip tests during collection is deprecated. "
"Use pytest.skip() instead."
)

ARGUMENT_PERCENT_DEFAULT = PytestDeprecationWarning(
ARGUMENT_PERCENT_DEFAULT = PytestRemovedIn8Warning(
'pytest now uses argparse. "%default" should be changed to "%(default)s"',
)

ARGUMENT_TYPE_STR_CHOICE = UnformattedWarning(
PytestDeprecationWarning,
PytestRemovedIn8Warning,
"`type` argument to addoption() is the string {typ!r}."
" For choices this is optional and can be omitted, "
" but when supplied should be a type (for example `str` or `int`)."
" (options: {names})",
)

ARGUMENT_TYPE_STR = UnformattedWarning(
PytestDeprecationWarning,
PytestRemovedIn8Warning,
"`type` argument to addoption() is the string {typ!r}, "
" but when supplied should be a type (for example `str` or `int`)."
" (options: {names})",
)


HOOK_LEGACY_PATH_ARG = UnformattedWarning(
PytestDeprecationWarning,
PytestRemovedIn8Warning,
"The ({pylib_path_arg}: py.path.local) argument is deprecated, please use ({pathlib_path_arg}: pathlib.Path)\n"
"see https://docs.pytest.org/en/latest/deprecations.html"
"#py-path-local-arguments-for-hooks-replaced-with-pathlib-path",
)

NODE_CTOR_FSPATH_ARG = UnformattedWarning(
PytestDeprecationWarning,
PytestRemovedIn8Warning,
"The (fspath: py.path.local) argument to {node_type_name} is deprecated. "
"Please use the (path: pathlib.Path) argument instead.\n"
"See https://docs.pytest.org/en/latest/deprecations.html"
"#fspath-argument-for-node-constructors-replaced-with-pathlib-path",
)

WARNS_NONE_ARG = PytestDeprecationWarning(
WARNS_NONE_ARG = PytestRemovedIn8Warning(
"Passing None to catch any warning has been deprecated, pass no arguments instead:\n"
" Replace pytest.warns(None) by simply pytest.warns()."
)

KEYWORD_MSG_ARG = UnformattedWarning(
PytestDeprecationWarning,
PytestRemovedIn8Warning,
"pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead",
)

INSTANCE_COLLECTOR = PytestDeprecationWarning(
INSTANCE_COLLECTOR = PytestRemovedIn8Warning(
"The pytest.Instance collector type is deprecated and is no longer used. "
"See https://docs.pytest.org/en/latest/deprecations.html#the-pytest-instance-collector",
)
Expand Down
15 changes: 14 additions & 1 deletion src/_pytest/warning_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,26 @@ class PytestCollectionWarning(PytestWarning):
__module__ = "pytest"


@final
class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
"""Warning class for features that will be removed in a future version."""

__module__ = "pytest"


@final
class PytestRemovedIn7Warning(PytestDeprecationWarning):
"""Warning class for features that will be removed in pytest 7."""

__module__ = "pytest"


@final
class PytestRemovedIn8Warning(PytestDeprecationWarning):
"""Warning class for features that will be removed in pytest 8."""

__module__ = "pytest"


@final
class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
"""Warning category used to denote experiments in pytest.
Expand Down
4 changes: 4 additions & 0 deletions src/pytest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
from _pytest.warning_types import PytestConfigWarning
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import PytestExperimentalApiWarning
from _pytest.warning_types import PytestRemovedIn7Warning
from _pytest.warning_types import PytestRemovedIn8Warning
from _pytest.warning_types import PytestUnhandledCoroutineWarning
from _pytest.warning_types import PytestUnhandledThreadExceptionWarning
from _pytest.warning_types import PytestUnknownMarkWarning
Expand Down Expand Up @@ -127,6 +129,8 @@
"PytestConfigWarning",
"PytestDeprecationWarning",
"PytestExperimentalApiWarning",
"PytestRemovedIn7Warning",
"PytestRemovedIn8Warning",
"Pytester",
"PytestPluginManager",
"PytestUnhandledCoroutineWarning",
Expand Down
12 changes: 6 additions & 6 deletions testing/deprecated_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def test_foo(): pass
result.stdout.fnmatch_lines(
[
"'unknown' not found in `markers` configuration option",
"*PytestDeprecationWarning: The --strict option is deprecated, use --strict-markers instead.",
"*PytestRemovedIn8Warning: The --strict option is deprecated, use --strict-markers instead.",
]
)

Expand Down Expand Up @@ -154,7 +154,7 @@ def test_raising_unittest_skiptest_during_collection_is_deprecated(
result = pytester.runpytest()
result.stdout.fnmatch_lines(
[
"*PytestDeprecationWarning: Raising unittest.SkipTest*",
"*PytestRemovedIn8Warning: Raising unittest.SkipTest*",
]
)

Expand Down Expand Up @@ -213,7 +213,7 @@ def test_skipping_msg():
result = pytester.runpytest(p)
result.stdout.fnmatch_lines(
[
"*PytestDeprecationWarning: pytest.skip(msg=...) is now deprecated, "
"*PytestRemovedIn8Warning: pytest.skip(msg=...) is now deprecated, "
"use pytest.skip(reason=...) instead",
'*pytest.skip(msg="skippedmsg")*',
]
Expand All @@ -232,7 +232,7 @@ def test_failing_msg():
result = pytester.runpytest(p)
result.stdout.fnmatch_lines(
[
"*PytestDeprecationWarning: pytest.fail(msg=...) is now deprecated, "
"*PytestRemovedIn8Warning: pytest.fail(msg=...) is now deprecated, "
"use pytest.fail(reason=...) instead",
'*pytest.fail(msg="failedmsg")',
]
Expand All @@ -251,7 +251,7 @@ def test_exit_msg():
result = pytester.runpytest(p)
result.stdout.fnmatch_lines(
[
"*PytestDeprecationWarning: pytest.exit(msg=...) is now deprecated, "
"*PytestRemovedIn8Warning: pytest.exit(msg=...) is now deprecated, "
"use pytest.exit(reason=...) instead",
]
)
Expand All @@ -269,7 +269,7 @@ def pytest_cmdline_preparse(config, args):
result = pytester.runpytest()
result.stdout.fnmatch_lines(
[
"*PytestDeprecationWarning: The pytest_cmdline_preparse hook is deprecated*",
"*PytestRemovedIn8Warning: The pytest_cmdline_preparse hook is deprecated*",
"*Please use pytest_load_initial_conftests hook instead.*",
]
)
Expand Down

0 comments on commit 128f29e

Please sign in to comment.