Skip to content

Commit 50f74bb

Browse files
authored
Merge pull request #13961 from bluetech/backport-restore-inicfg
[9.0.x] config: restore `config.inicfg`
2 parents 704b14c + 2e333ec commit 50f74bb

File tree

3 files changed

+88
-13
lines changed

3 files changed

+88
-13
lines changed

changelog/13946.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The private ``config.inicfg`` attribute was changed in a breaking manner in pytest 9.0.0.
2+
Due to its usage in the ecosystem, it is now restored to working order using a compatibility shim.
3+
It will be deprecated in pytest 9.1 and removed in pytest 10.

src/_pytest/config/__init__.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from collections.abc import Iterable
1212
from collections.abc import Iterator
1313
from collections.abc import Mapping
14+
from collections.abc import MutableMapping
1415
from collections.abc import Sequence
1516
import contextlib
1617
import copy
@@ -47,6 +48,7 @@
4748
from .compat import PathAwareHookProxy
4849
from .exceptions import PrintHelp as PrintHelp
4950
from .exceptions import UsageError as UsageError
51+
from .findpaths import ConfigValue
5052
from .findpaths import determine_setup
5153
from _pytest import __version__
5254
import _pytest._code
@@ -980,6 +982,30 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
980982
yield from _iter_rewritable_modules(new_package_files)
981983

982984

985+
class _DeprecatedInicfgProxy(MutableMapping[str, Any]):
986+
"""Compatibility proxy for the deprecated Config.inicfg."""
987+
988+
__slots__ = ("_config",)
989+
990+
def __init__(self, config: Config) -> None:
991+
self._config = config
992+
993+
def __getitem__(self, key: str) -> Any:
994+
return self._config._inicfg[key].value
995+
996+
def __setitem__(self, key: str, value: Any) -> None:
997+
self._config._inicfg[key] = ConfigValue(value, origin="override", mode="toml")
998+
999+
def __delitem__(self, key: str) -> None:
1000+
del self._config._inicfg[key]
1001+
1002+
def __iter__(self) -> Iterator[str]:
1003+
return iter(self._config._inicfg)
1004+
1005+
def __len__(self) -> int:
1006+
return len(self._config._inicfg)
1007+
1008+
9831009
@final
9841010
class Config:
9851011
"""Access to configuration values, pluginmanager and plugin hooks.
@@ -1100,6 +1126,10 @@ def __init__(
11001126
self.args_source = Config.ArgsSource.ARGS
11011127
self.args: list[str] = []
11021128

1129+
@property
1130+
def inicfg(self) -> _DeprecatedInicfgProxy:
1131+
return _DeprecatedInicfgProxy(self)
1132+
11031133
@property
11041134
def rootpath(self) -> pathlib.Path:
11051135
"""The path to the :ref:`rootdir <rootdir>`.
@@ -1376,7 +1406,7 @@ def pytest_collection(self) -> Generator[None, object, object]:
13761406
def _checkversion(self) -> None:
13771407
import pytest
13781408

1379-
minver_ini_value = self.inicfg.get("minversion", None)
1409+
minver_ini_value = self._inicfg.get("minversion", None)
13801410
minver = minver_ini_value.value if minver_ini_value is not None else None
13811411
if minver:
13821412
# Imported lazily to improve start-up time.
@@ -1440,7 +1470,7 @@ def _warn_or_fail_if_strict(self, message: str) -> None:
14401470

14411471
def _get_unknown_ini_keys(self) -> set[str]:
14421472
known_keys = self._parser._inidict.keys() | self._parser._ini_aliases.keys()
1443-
return self.inicfg.keys() - known_keys
1473+
return self._inicfg.keys() - known_keys
14441474

14451475
def parse(self, args: list[str], addopts: bool = True) -> None:
14461476
# Parse given cmdline arguments into this config object.
@@ -1471,7 +1501,7 @@ def parse(self, args: list[str], addopts: bool = True) -> None:
14711501
self._rootpath = rootpath
14721502
self._inipath = inipath
14731503
self._ignored_config_files = ignored_config_files
1474-
self.inicfg = inicfg
1504+
self._inicfg = inicfg
14751505
self._parser.extra_info["rootdir"] = str(self.rootpath)
14761506
self._parser.extra_info["inifile"] = str(self.inipath)
14771507

@@ -1648,14 +1678,14 @@ def _getini(self, name: str):
16481678
except KeyError as e:
16491679
raise ValueError(f"unknown configuration value: {name!r}") from e
16501680

1651-
# Collect all possible values (canonical name + aliases) from inicfg.
1681+
# Collect all possible values (canonical name + aliases) from _inicfg.
16521682
# Each candidate is (ConfigValue, is_canonical).
16531683
candidates = []
1654-
if canonical_name in self.inicfg:
1655-
candidates.append((self.inicfg[canonical_name], True))
1684+
if canonical_name in self._inicfg:
1685+
candidates.append((self._inicfg[canonical_name], True))
16561686
for alias, target in self._parser._ini_aliases.items():
1657-
if target == canonical_name and alias in self.inicfg:
1658-
candidates.append((self.inicfg[alias], False))
1687+
if target == canonical_name and alias in self._inicfg:
1688+
candidates.append((self._inicfg[alias], False))
16591689

16601690
if not candidates:
16611691
return default

testing/test_config.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def test_getcfg_and_config(
6060
_, _, cfg, _ = locate_config(Path.cwd(), [sub])
6161
assert cfg["name"] == ConfigValue("value", origin="file", mode="ini")
6262
config = pytester.parseconfigure(str(sub))
63-
assert config.inicfg["name"] == ConfigValue("value", origin="file", mode="ini")
63+
assert config._inicfg["name"] == ConfigValue("value", origin="file", mode="ini")
6464

6565
def test_setupcfg_uses_toolpytest_with_pytest(self, pytester: Pytester) -> None:
6666
p1 = pytester.makepyfile("def test(): pass")
@@ -1434,10 +1434,10 @@ def test_inifilename(self, tmp_path: Path) -> None:
14341434

14351435
# this indicates this is the file used for getting configuration values
14361436
assert config.inipath == inipath
1437-
assert config.inicfg.get("name") == ConfigValue(
1437+
assert config._inicfg.get("name") == ConfigValue(
14381438
"value", origin="file", mode="ini"
14391439
)
1440-
assert config.inicfg.get("should_not_be_set") is None
1440+
assert config._inicfg.get("should_not_be_set") is None
14411441

14421442

14431443
def test_options_on_small_file_do_not_blow_up(pytester: Pytester) -> None:
@@ -2277,7 +2277,7 @@ def test_addopts_before_initini(
22772277
monkeypatch.setenv("PYTEST_ADDOPTS", f"-o cache_dir={cache_dir}")
22782278
config = _config_for_test
22792279
config.parse([], addopts=True)
2280-
assert config.inicfg.get("cache_dir") == ConfigValue(
2280+
assert config._inicfg.get("cache_dir") == ConfigValue(
22812281
cache_dir, origin="override", mode="ini"
22822282
)
22832283

@@ -2318,7 +2318,7 @@ def test_override_ini_does_not_contain_paths(
23182318
"""Check that -o no longer swallows all options after it (#3103)"""
23192319
config = _config_for_test
23202320
config.parse(["-o", "cache_dir=/cache", "/some/test/path"])
2321-
assert config.inicfg.get("cache_dir") == ConfigValue(
2321+
assert config._inicfg.get("cache_dir") == ConfigValue(
23222322
"/cache", origin="override", mode="ini"
23232323
)
23242324

@@ -2999,3 +2999,45 @@ def pytest_addoption(parser):
29992999

30003000
with pytest.raises(TypeError, match=r"expects a string.*got int"):
30013001
config.getini("string_not_string")
3002+
3003+
3004+
class TestInicfgDeprecation:
3005+
"""Tests for the upcoming deprecation of config.inicfg."""
3006+
3007+
def test_inicfg_deprecated(self, pytester: Pytester) -> None:
3008+
"""Test that accessing config.inicfg issues a deprecation warning (not yet)."""
3009+
pytester.makeini(
3010+
"""
3011+
[pytest]
3012+
minversion = 3.0
3013+
"""
3014+
)
3015+
config = pytester.parseconfig()
3016+
3017+
inicfg = config.inicfg
3018+
3019+
assert config.getini("minversion") == "3.0"
3020+
assert inicfg["minversion"] == "3.0"
3021+
assert inicfg.get("minversion") == "3.0"
3022+
del inicfg["minversion"]
3023+
inicfg["minversion"] = "4.0"
3024+
assert list(inicfg.keys()) == ["minversion"]
3025+
assert list(inicfg.items()) == [("minversion", "4.0")]
3026+
assert len(inicfg) == 1
3027+
3028+
def test_issue_13946_setting_bool_no_longer_crashes(
3029+
self, pytester: Pytester
3030+
) -> None:
3031+
"""Regression test for #13946 - setting inicfg doesn't cause a crash."""
3032+
pytester.makepyfile(
3033+
"""
3034+
def pytest_configure(config):
3035+
config.inicfg["xfail_strict"] = True
3036+
3037+
def test():
3038+
pass
3039+
"""
3040+
)
3041+
3042+
result = pytester.runpytest()
3043+
assert result.ret == 0

0 commit comments

Comments
 (0)