From 9664b82f586c01aa69a703b80befcb2753331b8c Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 18 Oct 2026 15:44:12 +0000 Subject: [PATCH] Add PytestRemovedIn11Warning for unreleased deprecations The next release is planned as pytest 10.0 (#15200), which turns PytestRemovedIn10Warning into errors. Three deprecations on main were never released, so they would go straight from nothing to an error. Add PytestRemovedIn11Warning and use it for: - module/package/session-scoped fixtures defined as instance methods (class-scoped ones, deprecated in 9.1, stay on pytest 10) - non-string values for string-typed ini options - the _pytest.python.CallSpec2 alias Also correct two "deprecated since" versions in deprecations.rst: config.inicfg was deprecated in 9.1 (not 9.0), and the CallSpec2 alias is unreleased (not 9.1). Co-Authored-By: Claude Opus 5.5 (1M context) --- changelog/14742.deprecation.rst | 2 +- changelog/14774.deprecation.rst | 2 +- changelog/14808.deprecation.rst | 2 +- changelog/15200.deprecation.rst | 7 +++++++ doc/en/deprecations.rst | 8 ++++---- doc/en/reference/reference.rst | 3 +++ src/_pytest/deprecated.py | 14 +++++++++++--- src/_pytest/fixtures.py | 16 ++++++++++++++-- src/_pytest/warning_types.py | 6 ++++++ src/pytest/__init__.py | 2 ++ testing/deprecated_test.py | 23 +++++++++++++++++------ testing/plugins_integration/pytest.ini | 2 +- testing/test_config.py | 4 ++-- 13 files changed, 70 insertions(+), 21 deletions(-) create mode 100644 changelog/15200.deprecation.rst diff --git a/changelog/14742.deprecation.rst b/changelog/14742.deprecation.rst index 6a992c5dc4c..191552ac1ab 100644 --- a/changelog/14742.deprecation.rst +++ b/changelog/14742.deprecation.rst @@ -1,2 +1,2 @@ The private ``_pytest.python.CallSpec2`` name has been renamed to ``CallSpec``. -The old ``CallSpec2`` alias remains available for now and will be removed in pytest 10. +The old ``CallSpec2`` alias remains available for now and will be removed in pytest 11. diff --git a/changelog/14774.deprecation.rst b/changelog/14774.deprecation.rst index fa9c6ca4cc3..151b037788e 100644 --- a/changelog/14774.deprecation.rst +++ b/changelog/14774.deprecation.rst @@ -1,6 +1,6 @@ Similarly to the deprecation of class-scoped fixtures defined as instance methods in pytest 9.1, *all* higher-scoped fixtures (``module``, ``package``, and ``session``) defined -as instance methods are now deprecated. +as instance methods are now deprecated and will be removed in pytest 11. They suffer from the same issue the original deprecation only fixed for class-scoped fixtures, see :issue:`10819` and :issue:`14011`. diff --git a/changelog/14808.deprecation.rst b/changelog/14808.deprecation.rst index ad53b0084a9..cdff7b95a8d 100644 --- a/changelog/14808.deprecation.rst +++ b/changelog/14808.deprecation.rst @@ -1 +1 @@ -Passing a non-string value to a ``string``-typed ini option now emits a ``pytest.PytestRemovedIn10Warning``. This affects ``[tool.pytest.ini_options]`` in ``pyproject.toml``, where TOML arrays are kept as lists by ``make_scalar`` instead of being converted to strings. In pytest 10 this will raise a ``TypeError``, matching the behavior of the corresponding TOML config path. +Passing a non-string value to a ``string``-typed ini option now emits a ``pytest.PytestRemovedIn11Warning``. This affects ``[tool.pytest.ini_options]`` in ``pyproject.toml``, where TOML arrays are kept as lists by ``make_scalar`` instead of being converted to strings. In pytest 11 this will raise a ``TypeError``, matching the behavior of the corresponding TOML config path. diff --git a/changelog/15200.deprecation.rst b/changelog/15200.deprecation.rst new file mode 100644 index 00000000000..ad3f4f697be --- /dev/null +++ b/changelog/15200.deprecation.rst @@ -0,0 +1,7 @@ +Added :class:`pytest.PytestRemovedIn11Warning`. + +Deprecations that have not been part of a release yet now use it, so they are removed in pytest 11 instead of turning into errors in pytest 10 without ever having been released as warnings: + +- module-, package- and session-scoped fixtures defined as instance methods (class-scoped ones stay scheduled for pytest 10); +- non-string values for ``string``-typed ini options; +- the ``_pytest.python.CallSpec2`` alias. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 59f11c4ec09..e755283c592 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -20,12 +20,12 @@ Below is a complete list of all pytest features which are considered deprecated. ``_pytest.python.CallSpec2`` renamed to ``CallSpec`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. deprecated:: 9.1 +.. deprecated:: 9.2 The private ``_pytest.python.CallSpec2`` name has been renamed to ``CallSpec``. Plugins that import ``CallSpec2`` should update to ``CallSpec``. The old -``CallSpec2`` alias remains available for now and will be removed in pytest 10. +``CallSpec2`` alias remains available for now and will be removed in pytest 11. .. _fixture-nodeid-deprecated: @@ -137,7 +137,7 @@ be retrieved while they remain active, though this is discouraged. ``config.inicfg`` ~~~~~~~~~~~~~~~~~ -.. deprecated:: 9.0 +.. deprecated:: 9.1 The private ``config.inicfg`` attribute is deprecated. Use :meth:`config.getini() ` to access configuration values instead. @@ -280,7 +280,7 @@ to all test methods. Similarly, using a scope higher than ``class`` (``module``, ``package`` or ``session``) without using ``@classmethod`` (or ``@staticmethod``) is deprecated and will be -removed in pytest 10.0. +removed in pytest 11.0. .. _monkeypatch-fixup-namespace-packages: diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 4e0f40f4f93..f8ffd2b7688 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1317,6 +1317,9 @@ Custom warnings generated in some situations such as improper usage or deprecate .. autoclass:: pytest.PytestRemovedIn10Warning :show-inheritance: +.. autoclass:: pytest.PytestRemovedIn11Warning + :show-inheritance: + .. autoclass:: pytest.PytestUnknownMarkWarning :show-inheritance: diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index a94aeb735e5..48334bd4506 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -17,6 +17,7 @@ from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import PytestRemovedIn10Warning +from _pytest.warning_types import PytestRemovedIn11Warning from _pytest.warning_types import UnformattedWarning @@ -46,6 +47,13 @@ "See https://docs.pytest.org/en/stable/deprecations.html#class-scoped-fixture-as-instance-method", ) +# Same as CLASS_FIXTURE_INSTANCE_METHOD, but for module/package/session scope, +# which was deprecated later and so is removed later. +HIGHER_SCOPED_FIXTURE_INSTANCE_METHOD = UnformattedWarning( + PytestRemovedIn11Warning, + CLASS_FIXTURE_INSTANCE_METHOD.template, +) + # This deprecation is never really meant to be removed. PRIVATE = UnformattedWarning( PytestDeprecationWarning, @@ -106,7 +114,7 @@ "See https://docs.pytest.org/en/stable/deprecations.html#the-pastebin-option", ) -INI_STRING_TYPE_NON_STR_VALUE = PytestRemovedIn10Warning( +INI_STRING_TYPE_NON_STR_VALUE = PytestRemovedIn11Warning( "Passing a value that is not a string to a 'string'-typed ini option is deprecated.\n" "In a future version this will raise a TypeError, matching the behavior of the " "corresponding TOML config path.\n" @@ -157,9 +165,9 @@ ) CALLSPEC2_RENAMED = UnformattedWarning( - PytestRemovedIn10Warning, + PytestRemovedIn11Warning, "_pytest.python.CallSpec2 has been renamed to CallSpec.\n" - "The CallSpec2 alias will be removed in pytest 10.\n" + "The CallSpec2 alias will be removed in pytest 11.\n" "Update imports to use CallSpec instead.\n" "See https://docs.pytest.org/en/stable/deprecations.html#callspec2-renamed", ) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 20e43f89132..3cf21e0cb09 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -61,6 +61,7 @@ from _pytest.deprecated import FIXTURE_GETFIXTUREVALUE_DURING_TEARDOWN from _pytest.deprecated import FIXTURE_NODEID_DEPRECATED from _pytest.deprecated import FIXTUREDEF_HAS_LOCATION_DEPRECATED +from _pytest.deprecated import HIGHER_SCOPED_FIXTURE_INSTANCE_METHOD from _pytest.deprecated import PARSEFACTORIES_NODEID_DEPRECATED from _pytest.deprecated import YIELD_FIXTURE from _pytest.main import Session @@ -1335,7 +1336,13 @@ def resolve_fixture_function( value.""" def __tracebackhide__(e): - return issubclass(e.type, CLASS_FIXTURE_INSTANCE_METHOD.category) + return issubclass( + e.type, + ( + CLASS_FIXTURE_INSTANCE_METHOD.category, + HIGHER_SCOPED_FIXTURE_INSTANCE_METHOD.category, + ), + ) fixturefunc = fixturedef.func # The fixture function needs to be bound to the actual @@ -1350,8 +1357,13 @@ def __tracebackhide__(e): # classmethod: bound_to is the class itself (a type) # instance method: bound_to is an instance (not a type) if not isinstance(bound_to, type): + warning = ( + CLASS_FIXTURE_INSTANCE_METHOD + if fixturedef._scope is Scope.Class + else HIGHER_SCOPED_FIXTURE_INSTANCE_METHOD + ) warnings.warn( - CLASS_FIXTURE_INSTANCE_METHOD.format( + warning.format( scope=fixturedef._scope.name.capitalize(), fixturename=request.fixturename, ), diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index c5b339dec4c..75c375cfc4e 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -56,6 +56,12 @@ class PytestRemovedIn10Warning(PytestDeprecationWarning): __module__ = "pytest" +class PytestRemovedIn11Warning(PytestDeprecationWarning): + """Warning class for features that will be removed in pytest 11.""" + + __module__ = "pytest" + + @final class PytestExperimentalApiWarning(PytestWarning, FutureWarning): """Warning category used to denote experiments in pytest. diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index f6ea24f95a1..07ce1836d3a 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -88,6 +88,7 @@ from _pytest.warning_types import PytestExperimentalApiWarning from _pytest.warning_types import PytestFDWarning from _pytest.warning_types import PytestRemovedIn10Warning +from _pytest.warning_types import PytestRemovedIn11Warning from _pytest.warning_types import PytestReturnNotNoneWarning from _pytest.warning_types import PytestUnhandledThreadExceptionWarning from _pytest.warning_types import PytestUnknownMarkWarning @@ -141,6 +142,7 @@ "PytestFDWarning", "PytestPluginManager", "PytestRemovedIn10Warning", + "PytestRemovedIn11Warning", "PytestReturnNotNoneWarning", "PytestUnhandledThreadExceptionWarning", "PytestUnknownMarkWarning", diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 5de659a481f..61f4366fe1f 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -118,10 +118,15 @@ def test_foo(self, fix): pass """ ) - result = pytester.runpytest("-Werror::pytest.PytestRemovedIn10Warning") + category = ( + "PytestRemovedIn10Warning" + if scope is Scope.Class + else "PytestRemovedIn11Warning" + ) + result = pytester.runpytest(f"-Werror::pytest.{category}") result.assert_outcomes(errors=1) result.stdout.fnmatch_lines( - ["*PytestRemovedIn10Warning: *-scoped fixtures defined as instance methods*"] + [f"*{category}: *-scoped fixtures defined as instance methods*"] ) @@ -146,7 +151,10 @@ def test_foo(self, fix): assert type(self).attr is True """ ) - result = pytester.runpytest("-Werror::pytest.PytestRemovedIn10Warning") + result = pytester.runpytest( + "-Werror::pytest.PytestRemovedIn10Warning", + "-Werror::pytest.PytestRemovedIn11Warning", + ) result.assert_outcomes(passed=1) @@ -167,7 +175,10 @@ def test_foo(self, fix): pass """ ) - result = pytester.runpytest("-Werror::pytest.PytestRemovedIn10Warning") + result = pytester.runpytest( + "-Werror::pytest.PytestRemovedIn10Warning", + "-Werror::pytest.PytestRemovedIn11Warning", + ) result.assert_outcomes(passed=1) @@ -360,12 +371,12 @@ def test_callspec2_renamed() -> None: import _pytest.python as python_mod from _pytest.python import CallSpec - with pytest.warns(pytest.PytestRemovedIn10Warning, match="CallSpec2"): + with pytest.warns(pytest.PytestRemovedIn11Warning, match="CallSpec2"): from _pytest.python import CallSpec2 assert CallSpec2 is CallSpec - with pytest.warns(pytest.PytestRemovedIn10Warning, match="CallSpec2"): + with pytest.warns(pytest.PytestRemovedIn11Warning, match="CallSpec2"): assert python_mod.CallSpec2 is CallSpec diff --git a/testing/plugins_integration/pytest.ini b/testing/plugins_integration/pytest.ini index bc4d19ce837..4aabe91285e 100644 --- a/testing/plugins_integration/pytest.ini +++ b/testing/plugins_integration/pytest.ini @@ -6,4 +6,4 @@ filterwarnings = error::pytest.PytestWarning ignore:usefixtures.* without arguments has no effect:pytest.PytestWarning ignore:.*.fspath is deprecated and will be replaced by .*.path.*:pytest.PytestDeprecationWarning - ignore:_pytest.python.CallSpec2 has been renamed to CallSpec.:pytest.PytestRemovedIn10Warning + ignore:_pytest.python.CallSpec2 has been renamed to CallSpec.:pytest.PytestRemovedIn11Warning diff --git a/testing/test_config.py b/testing/test_config.py index 282e66409f7..80b5ecad20a 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1160,7 +1160,7 @@ def pytest_addoption(parser): def test_addini_string_non_str_deprecated(self, pytester: Pytester) -> None: """Passing a non-string value to a 'string'-typed ini option emits a deprecation warning. The value is still returned as-is for now, but will - raise TypeError in pytest 10.""" + raise TypeError in pytest 11.""" pytester.makeconftest( """ def pytest_addoption(parser): @@ -1175,7 +1175,7 @@ def pytest_addoption(parser): ) config = pytester.parseconfig() with pytest.warns( - pytest.PytestRemovedIn10Warning, + pytest.PytestRemovedIn11Warning, match="Passing a value that is not a string to a 'string'-typed ini option", ): result = config.getini("myname")