diff --git a/changelog/14808.deprecation.rst b/changelog/14808.deprecation.rst new file mode 100644 index 00000000000..18c1417d06d --- /dev/null +++ b/changelog/14808.deprecation.rst @@ -0,0 +1,4 @@ +In ini mode (``.ini`` files and ``[tool.pytest.ini_options]`` in ``pyproject.toml``), +reading a non-string value for a :confval:`string`-typed ini option (including +options registered without an explicit type, which default to ``"string"``) is now +deprecated and will raise a ``TypeError`` in pytest 10. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 7a7573de050..8a9923abca4 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -79,6 +79,7 @@ from _pytest.pathlib import safe_exists from _pytest.stash import Stash from _pytest.warning_types import PytestConfigWarning +from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import warn_explicit_for @@ -1949,6 +1950,15 @@ def _getini_ini( elif type == "bool": return _strtobool(str(value).strip()) elif type == "string": + if not isinstance(value, str): + warnings.warn( + PytestDeprecationWarning( + f"{self.inipath}: config option '{name}' expects a string value, " + f"got {builtins.type(value).__name__}: {value!r}. This will raise " + "a TypeError in pytest 10." + ), + stacklevel=2, + ) return value elif type == "int": if not isinstance(value, str): diff --git a/testing/test_config.py b/testing/test_config.py index 4a290f98ec5..04b37f0bf3c 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -949,6 +949,33 @@ def pytest_addoption(parser): with pytest.raises(ValueError): config.getini("other") + def test_addini_string_type_toml_list_deprecated(self, pytester: Pytester) -> None: + # https://github.com/pytest-dev/pytest/issues/14808 + pytester.makeconftest( + """ + def pytest_addoption(parser): + parser.addini("mystr", "a string option", type="string") + parser.addini("myuntyped", "an untyped option") + """ + ) + pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + mystr = ["a", "b"] + myuntyped = ["c", "d"] + """ + ) + config = pytester.parseconfig() + with pytest.warns( + pytest.PytestDeprecationWarning, match="expects a string value" + ): + assert config.getini("mystr") == ["a", "b"] + # Options registered without an explicit type default to "string". + with pytest.warns( + pytest.PytestDeprecationWarning, match="expects a string value" + ): + assert config.getini("myuntyped") == ["c", "d"] + @pytest.mark.parametrize("config_type", ["ini", "pyproject"]) def test_addini_paths(self, pytester: Pytester, config_type: str) -> None: pytester.makeconftest(