Skip to content

Commit d5a0057

Browse files
authored
Merge pull request #13845 from bluetech/config-tweaks
config: some config file/ini cleanups and tweaks
2 parents 68016f0 + ab44bec commit d5a0057

File tree

6 files changed

+46
-40
lines changed

6 files changed

+46
-40
lines changed

src/_pytest/config/__init__.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,7 +1647,7 @@ def _getini(self, name: str):
16471647
raise ValueError(f"unknown configuration value: {name!r}") from e
16481648

16491649
# Collect all possible values (canonical name + aliases) from inicfg.
1650-
# Each candidate is (IniValue, is_canonical).
1650+
# Each candidate is (ConfigValue, is_canonical).
16511651
candidates = []
16521652
if canonical_name in self.inicfg:
16531653
candidates.append((self.inicfg[canonical_name], True))
@@ -1661,8 +1661,8 @@ def _getini(self, name: str):
16611661
# Pick the best candidate based on precedence:
16621662
# 1. CLI override takes precedence over file, then
16631663
# 2. Canonical name takes precedence over alias.
1664-
ini_value = max(candidates, key=lambda x: (x[0].origin == "override", x[1]))[0]
1665-
value = ini_value.value
1664+
selected = max(candidates, key=lambda x: (x[0].origin == "override", x[1]))[0]
1665+
value = selected.value
16661666

16671667
# Coerce the values based on types.
16681668
#
@@ -1710,8 +1710,6 @@ def _getini(self, name: str):
17101710
f"Expected a float string for option {name} of type float, but got: {value!r}"
17111711
) from None
17121712
return float(value)
1713-
elif type is None:
1714-
return value
17151713
else:
17161714
return self._getini_unknown_type(name, type, value)
17171715

src/_pytest/config/argparsing.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ def __init__(
5050
self._groups: list[OptionGroup] = []
5151
self._processopt = processopt
5252
self._usage = usage
53-
self._inidict: dict[str, tuple[str, str | None, Any]] = {}
54-
self._ininames: list[str] = []
53+
self._inidict: dict[str, tuple[str, str, Any]] = {}
5554
# Maps alias -> canonical name.
5655
self._ini_aliases: dict[str, str] = {}
5756
self.extra_info: dict[str, Any] = {}
@@ -238,11 +237,12 @@ def addini(
238237
"int",
239238
"float",
240239
)
240+
if type is None:
241+
type = "string"
241242
if default is NOT_SET:
242243
default = get_ini_default_for_type(type)
243244

244245
self._inidict[name] = (help, type, default)
245-
self._ininames.append(name)
246246

247247
for alias in aliases:
248248
if alias in self._inidict:
@@ -255,16 +255,13 @@ def addini(
255255
def get_ini_default_for_type(
256256
type: Literal[
257257
"string", "paths", "pathlist", "args", "linelist", "bool", "int", "float"
258-
]
259-
| None,
258+
],
260259
) -> Any:
261260
"""
262261
Used by addini to get the default value for a given ini-option type, when
263262
default is not supplied.
264263
"""
265-
if type is None:
266-
return ""
267-
elif type in ("paths", "pathlist", "args", "linelist"):
264+
if type in ("paths", "pathlist", "args", "linelist"):
268265
return []
269266
elif type == "bool":
270267
return False

src/_pytest/config/findpaths.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from collections.abc import Iterable
44
from collections.abc import Sequence
55
from dataclasses import dataclass
6+
from dataclasses import KW_ONLY
67
import os
78
from pathlib import Path
89
import sys
@@ -19,8 +20,8 @@
1920

2021

2122
@dataclass(frozen=True)
22-
class IniValue:
23-
"""Represents an ini configuration value with its origin.
23+
class ConfigValue:
24+
"""Represents a configuration value with its origin.
2425
2526
This allows tracking whether a value came from a configuration file
2627
or from a CLI override (--override-ini), which is important for
@@ -31,10 +32,11 @@ class IniValue:
3132
# str/list[str] during parsing to maintain compatibility with the rest of
3233
# the configuration system.
3334
value: str | list[str]
35+
_: KW_ONLY
3436
origin: Literal["file", "override"]
3537

3638

37-
ConfigDict: TypeAlias = dict[str, IniValue]
39+
ConfigDict: TypeAlias = dict[str, ConfigValue]
3840

3941

4042
def _parse_ini_config(path: Path) -> iniconfig.IniConfig:
@@ -61,7 +63,9 @@ def load_config_dict_from_file(
6163
iniconfig = _parse_ini_config(filepath)
6264

6365
if "pytest" in iniconfig:
64-
return {k: IniValue(v, "file") for k, v in iniconfig["pytest"].items()}
66+
return {
67+
k: ConfigValue(v, origin="file") for k, v in iniconfig["pytest"].items()
68+
}
6569
else:
6670
# "pytest.ini" files are always the source of configuration, even if empty.
6771
if filepath.name == "pytest.ini":
@@ -72,7 +76,10 @@ def load_config_dict_from_file(
7276
iniconfig = _parse_ini_config(filepath)
7377

7478
if "tool:pytest" in iniconfig.sections:
75-
return {k: IniValue(v, "file") for k, v in iniconfig["tool:pytest"].items()}
79+
return {
80+
k: ConfigValue(v, origin="file")
81+
for k, v in iniconfig["tool:pytest"].items()
82+
}
7683
elif "pytest" in iniconfig.sections:
7784
# If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that
7885
# plain "[pytest]" sections in setup.cfg files is no longer supported (#3086).
@@ -99,7 +106,9 @@ def load_config_dict_from_file(
99106
def make_scalar(v: object) -> str | list[str]:
100107
return v if isinstance(v, list) else str(v)
101108

102-
return {k: IniValue(make_scalar(v), "file") for k, v in result.items()}
109+
return {
110+
k: ConfigValue(make_scalar(v), origin="file") for k, v in result.items()
111+
}
103112

104113
return None
105114

@@ -215,7 +224,7 @@ def parse_override_ini(override_ini: Sequence[str] | None) -> ConfigDict:
215224
f"-o/--override-ini expects option=value style (got: {ini_config!r})."
216225
) from e
217226
else:
218-
overrides[key] = IniValue(user_ini_value, "override")
227+
overrides[key] = ConfigValue(user_ini_value, origin="override")
219228
return overrides
220229

221230

src/_pytest/helpconfig.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,8 @@ def showhelp(config: Config) -> None:
184184
columns = tw.fullwidth # costly call
185185
indent_len = 24 # based on argparse's max_help_position=24
186186
indent = " " * indent_len
187-
for name in config._parser._ininames:
187+
for name in config._parser._inidict:
188188
help, type, _default = config._parser._inidict[name]
189-
if type is None:
190-
type = "string"
191189
if help is None:
192190
raise TypeError(f"help argument cannot be None for {name}")
193191
spec = f"{name} ({type}):"

testing/test_config.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
from _pytest.config.argparsing import get_ini_default_for_type
2424
from _pytest.config.argparsing import Parser
2525
from _pytest.config.exceptions import UsageError
26+
from _pytest.config.findpaths import ConfigValue
2627
from _pytest.config.findpaths import determine_setup
2728
from _pytest.config.findpaths import get_common_ancestor
28-
from _pytest.config.findpaths import IniValue
2929
from _pytest.config.findpaths import locate_config
3030
from _pytest.monkeypatch import MonkeyPatch
3131
from _pytest.pathlib import absolutepath
@@ -58,9 +58,9 @@ def test_getcfg_and_config(
5858
encoding="utf-8",
5959
)
6060
_, _, cfg, _ = locate_config(Path.cwd(), [sub])
61-
assert cfg["name"] == IniValue("value", "file")
61+
assert cfg["name"] == ConfigValue("value", origin="file")
6262
config = pytester.parseconfigure(str(sub))
63-
assert config.inicfg["name"] == IniValue("value", "file")
63+
assert config.inicfg["name"] == ConfigValue("value", origin="file")
6464

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

13151315
# this indicates this is the file used for getting configuration values
13161316
assert config.inipath == inipath
1317-
assert config.inicfg.get("name") == IniValue("value", "file")
1317+
assert config.inicfg.get("name") == ConfigValue("value", origin="file")
13181318
assert config.inicfg.get("should_not_be_set") is None
13191319

13201320

@@ -1808,7 +1808,7 @@ def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None:
18081808
)
18091809
assert rootpath == tmp_path
18101810
assert parsed_inipath == inipath
1811-
assert ini_config["x"] == IniValue("10", "file")
1811+
assert ini_config["x"] == ConfigValue("10", origin="file")
18121812

18131813
@pytest.mark.parametrize("name", ["setup.cfg", "tox.ini"])
18141814
def test_pytestini_overrides_empty_other(self, tmp_path: Path, name: str) -> None:
@@ -1882,7 +1882,7 @@ def test_with_specific_inifile(
18821882
)
18831883
assert rootpath == tmp_path
18841884
assert inipath == p
1885-
assert ini_config["x"] == IniValue("10", "file")
1885+
assert ini_config["x"] == ConfigValue("10", origin="file")
18861886

18871887
def test_explicit_config_file_sets_rootdir(
18881888
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
@@ -2152,7 +2152,9 @@ def test_addopts_before_initini(
21522152
monkeypatch.setenv("PYTEST_ADDOPTS", f"-o cache_dir={cache_dir}")
21532153
config = _config_for_test
21542154
config._preparse([], addopts=True)
2155-
assert config.inicfg.get("cache_dir") == IniValue(cache_dir, "override")
2155+
assert config.inicfg.get("cache_dir") == ConfigValue(
2156+
cache_dir, origin="override"
2157+
)
21562158

21572159
def test_addopts_from_env_not_concatenated(
21582160
self, monkeypatch: MonkeyPatch, _config_for_test
@@ -2190,7 +2192,9 @@ def test_override_ini_does_not_contain_paths(
21902192
"""Check that -o no longer swallows all options after it (#3103)"""
21912193
config = _config_for_test
21922194
config._preparse(["-o", "cache_dir=/cache", "/some/test/path"])
2193-
assert config.inicfg.get("cache_dir") == IniValue("/cache", "override")
2195+
assert config.inicfg.get("cache_dir") == ConfigValue(
2196+
"/cache", origin="override"
2197+
)
21942198

21952199
def test_multiple_override_ini_options(self, pytester: Pytester) -> None:
21962200
"""Ensure a file path following a '-o' option does not generate an error (#3103)"""

testing/test_findpaths.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
from textwrap import dedent
77

88
from _pytest.config import UsageError
9+
from _pytest.config.findpaths import ConfigValue
910
from _pytest.config.findpaths import get_common_ancestor
1011
from _pytest.config.findpaths import get_dirs_from_args
11-
from _pytest.config.findpaths import IniValue
1212
from _pytest.config.findpaths import is_fs_root
1313
from _pytest.config.findpaths import load_config_dict_from_file
1414
import pytest
@@ -25,13 +25,13 @@ def test_pytest_ini(self, tmp_path: Path) -> None:
2525
"""[pytest] section in pytest.ini files is read correctly"""
2626
fn = tmp_path / "pytest.ini"
2727
fn.write_text("[pytest]\nx=1", encoding="utf-8")
28-
assert load_config_dict_from_file(fn) == {"x": IniValue("1", "file")}
28+
assert load_config_dict_from_file(fn) == {"x": ConfigValue("1", origin="file")}
2929

3030
def test_custom_ini(self, tmp_path: Path) -> None:
3131
"""[pytest] section in any .ini file is read correctly"""
3232
fn = tmp_path / "custom.ini"
3333
fn.write_text("[pytest]\nx=1", encoding="utf-8")
34-
assert load_config_dict_from_file(fn) == {"x": IniValue("1", "file")}
34+
assert load_config_dict_from_file(fn) == {"x": ConfigValue("1", origin="file")}
3535

3636
def test_custom_ini_without_section(self, tmp_path: Path) -> None:
3737
"""Custom .ini files without [pytest] section are not considered for configuration"""
@@ -49,7 +49,7 @@ def test_valid_cfg_file(self, tmp_path: Path) -> None:
4949
"""Custom .cfg files with [tool:pytest] section are read correctly"""
5050
fn = tmp_path / "custom.cfg"
5151
fn.write_text("[tool:pytest]\nx=1", encoding="utf-8")
52-
assert load_config_dict_from_file(fn) == {"x": IniValue("1", "file")}
52+
assert load_config_dict_from_file(fn) == {"x": ConfigValue("1", origin="file")}
5353

5454
def test_unsupported_pytest_section_in_cfg_file(self, tmp_path: Path) -> None:
5555
""".cfg files with [pytest] section are no longer supported and should fail to alert users"""
@@ -97,11 +97,11 @@ def test_valid_toml_file(self, tmp_path: Path) -> None:
9797
encoding="utf-8",
9898
)
9999
assert load_config_dict_from_file(fn) == {
100-
"x": IniValue("1", "file"),
101-
"y": IniValue("20.0", "file"),
102-
"values": IniValue(["tests", "integration"], "file"),
103-
"name": IniValue("foo", "file"),
104-
"heterogeneous_array": IniValue([1, "str"], "file"), # type: ignore[list-item]
100+
"x": ConfigValue("1", origin="file"),
101+
"y": ConfigValue("20.0", origin="file"),
102+
"values": ConfigValue(["tests", "integration"], origin="file"),
103+
"name": ConfigValue("foo", origin="file"),
104+
"heterogeneous_array": ConfigValue([1, "str"], origin="file"), # type: ignore[list-item]
105105
}
106106

107107

0 commit comments

Comments
 (0)