Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d37b01f

Browse files
committedJun 4, 2020
Add support for configuration in pyproject.toml
Fix pytest-dev#1556
1 parent 2ccda38 commit d37b01f

File tree

5 files changed

+112
-13
lines changed

5 files changed

+112
-13
lines changed
 

‎setup.cfg

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ install_requires =
5050
colorama;sys_platform=="win32"
5151
importlib-metadata>=0.12;python_version<"3.8"
5252
pathlib2>=2.2.0;python_version<"3.6"
53+
toml
5354
python_requires = >=3.5
5455
package_dir =
5556
=src

‎src/_pytest/config/__init__.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -1138,16 +1138,22 @@ def _getini(self, name: str) -> Any:
11381138
if type is None:
11391139
return ""
11401140
return []
1141+
# coerce the values based on types
1142+
# note: some coercions are only required if we are reading from .ini files, because
1143+
# the file format doesn't contain type information; toml files however support
1144+
# data types and complex types such as lists directly, so many conversions are not
1145+
# necessary
11411146
if type == "pathlist":
11421147
dp = py.path.local(self.inifile).dirpath()
1143-
values = []
1144-
for relpath in shlex.split(value):
1145-
values.append(dp.join(relpath, abs=True))
1146-
return values
1148+
input_values = shlex.split(value) if isinstance(value, str) else value
1149+
return [dp.join(x, abs=True) for x in input_values]
11471150
elif type == "args":
1148-
return shlex.split(value)
1151+
return shlex.split(value) if isinstance(value, str) else value
11491152
elif type == "linelist":
1150-
return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
1153+
if isinstance(value, str):
1154+
return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
1155+
else:
1156+
return value
11511157
elif type == "bool":
11521158
return bool(_strtobool(value.strip()))
11531159
else:

‎src/_pytest/config/findpaths.py

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

8383

84+
def _get_ini_config_from_pyproject_toml(
85+
path: py.path.local,
86+
) -> Optional[Dict[str, Any]]:
87+
"""Parses and validates a 'setup.cfg' file for pytest configuration.
88+
89+
'setup.cfg' files are only considered for pytest configuration if they contain a "[tool:pytest]"
90+
section.
91+
92+
If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that
93+
plain "[pytest]" sections in setup.cfg files is no longer supported (#3086).
94+
"""
95+
import toml
96+
97+
config = toml.load(path)
98+
99+
result = config.get("tool", {}).get("pytest", {}).get("ini", None)
100+
if result is not None:
101+
# convert all scalar values to strings for compatibility with other ini formats
102+
# conversion to actual useful values is made by Config._getini
103+
def make_scalar(v):
104+
return v if isinstance(v, (list, tuple)) else str(v)
105+
106+
return {k: make_scalar(v) for k, v in result.items()}
107+
else:
108+
return None
109+
110+
84111
def getcfg(args):
85112
"""
86113
Search the list of arguments for a valid ini-file for pytest,
@@ -90,6 +117,7 @@ def getcfg(args):
90117
("pytest.ini", _get_ini_config_from_pytest_ini),
91118
("tox.ini", _get_ini_config_from_tox_ini),
92119
("setup.cfg", _get_ini_config_from_setup_cfg),
120+
("pyproject.toml", _get_ini_config_from_pyproject_toml),
93121
]
94122
args = [x for x in args if not str(x).startswith("-")]
95123
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 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(
@@ -349,63 +359,110 @@ def pytest_addoption(parser):
349359
assert val == "hello"
350360
pytest.raises(ValueError, config.getini, "other")
351361

352-
def test_addini_pathlist(self, testdir):
362+
def make_conftest_for_pathlist(self, testdir):
353363
testdir.makeconftest(
354364
"""
355365
def pytest_addoption(parser):
356366
parser.addini("paths", "my new ini value", type="pathlist")
357367
parser.addini("abc", "abc value")
358368
"""
359369
)
370+
371+
def test_addini_pathlist_ini_files(self, testdir):
372+
self.make_conftest_for_pathlist(testdir)
360373
p = testdir.makeini(
361374
"""
362375
[pytest]
363376
paths=hello world/sub.py
364377
"""
365378
)
379+
self.check_config_pathlist(testdir, p)
380+
381+
def test_addini_pathlist_pyproject_toml(self, testdir):
382+
self.make_conftest_for_pathlist(testdir)
383+
p = testdir.makepyprojecttoml(
384+
"""
385+
[tool.pytest.ini]
386+
paths=["hello", "world/sub.py"]
387+
"""
388+
)
389+
self.check_config_pathlist(testdir, p)
390+
391+
def check_config_pathlist(self, testdir, config_path):
366392
config = testdir.parseconfig()
367393
values = config.getini("paths")
368394
assert len(values) == 2
369-
assert values[0] == p.dirpath("hello")
370-
assert values[1] == p.dirpath("world/sub.py")
395+
assert values[0] == config_path.dirpath("hello")
396+
assert values[1] == config_path.dirpath("world/sub.py")
371397
pytest.raises(ValueError, config.getini, "other")
372398

373-
def test_addini_args(self, testdir):
399+
def make_conftest_for_args(self, testdir):
374400
testdir.makeconftest(
375401
"""
376402
def pytest_addoption(parser):
377403
parser.addini("args", "new args", type="args")
378404
parser.addini("a2", "", "args", default="1 2 3".split())
379405
"""
380406
)
407+
408+
def test_addini_args_ini_files(self, testdir):
409+
self.make_conftest_for_args(testdir)
381410
testdir.makeini(
382411
"""
383412
[pytest]
384413
args=123 "123 hello" "this"
385-
"""
414+
"""
386415
)
416+
self.check_config_args(testdir)
417+
418+
def test_addini_args_pyproject_toml(self, testdir):
419+
self.make_conftest_for_args(testdir)
420+
testdir.makepyprojecttoml(
421+
"""
422+
[tool.pytest.ini]
423+
args = ["123", "123 hello", "this"]
424+
"""
425+
)
426+
self.check_config_args(testdir)
427+
428+
def check_config_args(self, testdir):
387429
config = testdir.parseconfig()
388430
values = config.getini("args")
389-
assert len(values) == 3
390431
assert values == ["123", "123 hello", "this"]
391432
values = config.getini("a2")
392433
assert values == list("123")
393434

394-
def test_addini_linelist(self, testdir):
435+
def make_conftest_for_linelist(self, testdir):
395436
testdir.makeconftest(
396437
"""
397438
def pytest_addoption(parser):
398439
parser.addini("xy", "", type="linelist")
399440
parser.addini("a2", "", "linelist")
400441
"""
401442
)
443+
444+
def test_addini_linelist_ini_files(self, testdir):
445+
self.make_conftest_for_linelist(testdir)
402446
testdir.makeini(
403447
"""
404448
[pytest]
405449
xy= 123 345
406450
second line
407451
"""
408452
)
453+
self.check_config_linelist(testdir)
454+
455+
def test_addini_linelist_pprojecttoml(self, testdir):
456+
self.make_conftest_for_linelist(testdir)
457+
testdir.makepyprojecttoml(
458+
"""
459+
[tool.pytest.ini]
460+
xy = ["123 345", "second line"]
461+
"""
462+
)
463+
self.check_config_linelist(testdir)
464+
465+
def check_config_linelist(self, testdir):
409466
config = testdir.parseconfig()
410467
values = config.getini("xy")
411468
assert len(values) == 2

0 commit comments

Comments
 (0)
Please sign in to comment.