Skip to content

Commit 282e75d

Browse files
committed
Add support for configuration in pyproject.toml
Fix pytest-dev#1556
1 parent 991649d commit 282e75d

File tree

5 files changed

+112
-13
lines changed

5 files changed

+112
-13
lines changed

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"pluggy>=0.12,<1.0",
1414
'importlib-metadata>=0.12;python_version<"3.8"',
1515
"wcwidth",
16+
"toml",
1617
]
1718

1819

src/_pytest/config/__init__.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -1126,16 +1126,22 @@ def _getini(self, name: str) -> Any:
11261126
if type is None:
11271127
return ""
11281128
return []
1129+
# coerce the values based on types
1130+
# note: some coercions are only required if we are reading from .ini files, because
1131+
# the file format doesn't contain type information; toml files however support
1132+
# data types and complex types such as lists directly, so many conversions are not
1133+
# necessary
11291134
if type == "pathlist":
11301135
dp = py.path.local(self.inifile).dirpath()
1131-
values = []
1132-
for relpath in shlex.split(value):
1133-
values.append(dp.join(relpath, abs=True))
1134-
return values
1136+
input_values = shlex.split(value) if isinstance(value, str) else value
1137+
return [dp.join(x, abs=True) for x in input_values]
11351138
elif type == "args":
1136-
return shlex.split(value)
1139+
return shlex.split(value) if isinstance(value, str) else value
11371140
elif type == "linelist":
1138-
return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
1141+
if isinstance(value, str):
1142+
return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
1143+
else:
1144+
return value
11391145
elif type == "bool":
11401146
return bool(_strtobool(value.strip()))
11411147
else:

src/_pytest/config/findpaths.py

+28
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,33 @@ def _get_ini_config_from_setup_cfg(path: py.path.local) -> Optional[Dict[str, An
7979
return None
8080

8181

82+
def _get_ini_config_from_pyproject_toml(
83+
path: py.path.local,
84+
) -> Optional[Dict[str, Any]]:
85+
"""Parses and validates a 'setup.cfg' file for pytest configuration.
86+
87+
'setup.cfg' files are only considered for pytest configuration if they contain a "[tool:pytest]"
88+
section.
89+
90+
If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that
91+
plain "[pytest]" sections in setup.cfg files is no longer supported (#3086).
92+
"""
93+
import toml
94+
95+
config = toml.load(path)
96+
97+
result = config.get("tool", {}).get("pytest", {}).get("ini", None)
98+
if result is not None:
99+
# convert all scalar values to strings for compatibility with other ini formats
100+
# conversion to actual useful values is made by Config._getini
101+
def make_scalar(v):
102+
return v if isinstance(v, (list, tuple)) else str(v)
103+
104+
return {k: make_scalar(v) for k, v in result.items()}
105+
else:
106+
return None
107+
108+
82109
def getcfg(args):
83110
"""
84111
Search the list of arguments for a valid ini-file for pytest,
@@ -88,6 +115,7 @@ def getcfg(args):
88115
("pytest.ini", _get_ini_config_from_pytest_ini),
89116
("tox.ini", _get_ini_config_from_tox_ini),
90117
("setup.cfg", _get_ini_config_from_setup_cfg),
118+
("pyproject.toml", _get_ini_config_from_pyproject_toml),
91119
]
92120
args = [x for x in args if not str(x).startswith("-")]
93121
if not args:

src/_pytest/pytester.py

+7
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,13 @@ def getinicfg(self, source):
686686
p = self.makeini(source)
687687
return py.iniconfig.IniConfig(p)["pytest"]
688688

689+
def makepyprojecttoml(self, source):
690+
"""Write a pyproject.toml file with 'source' as contents.
691+
692+
.. versionadded:: 6.0
693+
"""
694+
return self.makefile(".toml", pyproject=source)
695+
689696
def makepyfile(self, *args, **kwargs):
690697
r"""Shortcut for .makefile() with a .py extension.
691698
Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting

testing/test_config.py

+64-7
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ def test_ini_names(self, testdir, name, section):
109109
config = testdir.parseconfig()
110110
assert config.getini("minversion") == "1.0"
111111

112+
def test_pyproject_toml(self, testdir):
113+
testdir.makepyprojecttoml(
114+
"""
115+
[tool.pytest.ini]
116+
minversion = "1.0"
117+
"""
118+
)
119+
config = testdir.parseconfig()
120+
assert config.getini("minversion") == "1.0"
121+
112122
def test_toxini_before_lower_pytestini(self, testdir):
113123
sub = testdir.tmpdir.mkdir("sub")
114124
sub.join("tox.ini").write(
@@ -287,63 +297,110 @@ def pytest_addoption(parser):
287297
assert val == "hello"
288298
pytest.raises(ValueError, config.getini, "other")
289299

290-
def test_addini_pathlist(self, testdir):
300+
def make_conftest_for_pathlist(self, testdir):
291301
testdir.makeconftest(
292302
"""
293303
def pytest_addoption(parser):
294304
parser.addini("paths", "my new ini value", type="pathlist")
295305
parser.addini("abc", "abc value")
296306
"""
297307
)
308+
309+
def test_addini_pathlist_ini_files(self, testdir):
310+
self.make_conftest_for_pathlist(testdir)
298311
p = testdir.makeini(
299312
"""
300313
[pytest]
301314
paths=hello world/sub.py
302315
"""
303316
)
317+
self.check_config_pathlist(testdir, p)
318+
319+
def test_addini_pathlist_pyproject_toml(self, testdir):
320+
self.make_conftest_for_pathlist(testdir)
321+
p = testdir.makepyprojecttoml(
322+
"""
323+
[tool.pytest.ini]
324+
paths=["hello", "world/sub.py"]
325+
"""
326+
)
327+
self.check_config_pathlist(testdir, p)
328+
329+
def check_config_pathlist(self, testdir, config_path):
304330
config = testdir.parseconfig()
305331
values = config.getini("paths")
306332
assert len(values) == 2
307-
assert values[0] == p.dirpath("hello")
308-
assert values[1] == p.dirpath("world/sub.py")
333+
assert values[0] == config_path.dirpath("hello")
334+
assert values[1] == config_path.dirpath("world/sub.py")
309335
pytest.raises(ValueError, config.getini, "other")
310336

311-
def test_addini_args(self, testdir):
337+
def make_conftest_for_args(self, testdir):
312338
testdir.makeconftest(
313339
"""
314340
def pytest_addoption(parser):
315341
parser.addini("args", "new args", type="args")
316342
parser.addini("a2", "", "args", default="1 2 3".split())
317343
"""
318344
)
345+
346+
def test_addini_args_ini_files(self, testdir):
347+
self.make_conftest_for_args(testdir)
319348
testdir.makeini(
320349
"""
321350
[pytest]
322351
args=123 "123 hello" "this"
323-
"""
352+
"""
324353
)
354+
self.check_config_args(testdir)
355+
356+
def test_addini_args_pyproject_toml(self, testdir):
357+
self.make_conftest_for_args(testdir)
358+
testdir.makepyprojecttoml(
359+
"""
360+
[tool.pytest.ini]
361+
args = ["123", "123 hello", "this"]
362+
"""
363+
)
364+
self.check_config_args(testdir)
365+
366+
def check_config_args(self, testdir):
325367
config = testdir.parseconfig()
326368
values = config.getini("args")
327-
assert len(values) == 3
328369
assert values == ["123", "123 hello", "this"]
329370
values = config.getini("a2")
330371
assert values == list("123")
331372

332-
def test_addini_linelist(self, testdir):
373+
def make_conftest_for_linelist(self, testdir):
333374
testdir.makeconftest(
334375
"""
335376
def pytest_addoption(parser):
336377
parser.addini("xy", "", type="linelist")
337378
parser.addini("a2", "", "linelist")
338379
"""
339380
)
381+
382+
def test_addini_linelist_ini_files(self, testdir):
383+
self.make_conftest_for_linelist(testdir)
340384
testdir.makeini(
341385
"""
342386
[pytest]
343387
xy= 123 345
344388
second line
345389
"""
346390
)
391+
self.check_config_linelist(testdir)
392+
393+
def test_addini_linelist_pprojecttoml(self, testdir):
394+
self.make_conftest_for_linelist(testdir)
395+
testdir.makepyprojecttoml(
396+
"""
397+
[tool.pytest.ini]
398+
xy = ["123 345", "second line"]
399+
"""
400+
)
401+
self.check_config_linelist(testdir)
402+
403+
def check_config_linelist(self, testdir):
347404
config = testdir.parseconfig()
348405
values = config.getini("xy")
349406
assert len(values) == 2

0 commit comments

Comments
 (0)