Skip to content

Commit b599919

Browse files
committed
Consider pyproject.toml files for config if no other config files were found
Today `pyproject.toml` is the standard for declaring a Python project root, so seems reasonable to consider it for the ini configuration (and specially `rootdir`) in case we do not find other suitable candidates. Related to #11311
1 parent 7690a0d commit b599919

File tree

4 files changed

+40
-1
lines changed

4 files changed

+40
-1
lines changed

changelog/11962.improvement.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
In case no other suitable candidates for configuration file are found, a ``pyproject.toml`` (even without a ``[tool.pytest.ini_options]`` table) will be considered as the configuration file and define the ``rootdir``.

doc/en/reference/customize.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ Files will only be matched for configuration if:
177177
* ``tox.ini``: contains a ``[pytest]`` section.
178178
* ``setup.cfg``: contains a ``[tool:pytest]`` section.
179179

180+
Finally, a ``pyproject.toml`` file will be considered the ``configfile`` if no other match was found, in this case
181+
even if it does not contain a ``[tool.pytest.ini_options]`` table (this was added in ``8.1``).
182+
180183
The files are considered in the order above. Options from multiple ``configfiles`` candidates
181184
are never merged - the first match wins.
182185

src/_pytest/config/findpaths.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,20 @@ def locate_config(
101101
args = [x for x in args if not str(x).startswith("-")]
102102
if not args:
103103
args = [invocation_dir]
104+
found_pyproject_toml: Optional[Path] = None
104105
for arg in args:
105106
argpath = absolutepath(arg)
106107
for base in (argpath, *argpath.parents):
107108
for config_name in config_names:
108109
p = base / config_name
109110
if p.is_file():
111+
if p.name == "pyproject.toml" and found_pyproject_toml is None:
112+
found_pyproject_toml = p
110113
ini_config = load_config_dict_from_file(p)
111114
if ini_config is not None:
112115
return base, p, ini_config
116+
if found_pyproject_toml is not None:
117+
return found_pyproject_toml.parent, found_pyproject_toml, {}
113118
return None, None, {}
114119

115120

testing/test_config.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,45 @@ def test_ini_names(self, pytester: Pytester, name, section) -> None:
135135
assert config.getini("minversion") == "3.36"
136136

137137
def test_pyproject_toml(self, pytester: Pytester) -> None:
138-
pytester.makepyprojecttoml(
138+
pyproject_toml = pytester.makepyprojecttoml(
139139
"""
140140
[tool.pytest.ini_options]
141141
minversion = "1.0"
142142
"""
143143
)
144144
config = pytester.parseconfig()
145+
assert config.inipath == pyproject_toml
145146
assert config.getini("minversion") == "1.0"
146147

148+
def test_empty_pyproject_toml(self, pytester: Pytester) -> None:
149+
"""An empty pyproject.toml is considered as config if no other option is found."""
150+
pyproject_toml = pytester.makepyprojecttoml("")
151+
config = pytester.parseconfig()
152+
assert config.inipath == pyproject_toml
153+
154+
def test_empty_pyproject_toml_found_many(self, pytester: Pytester) -> None:
155+
"""
156+
In case we find multiple pyproject.toml files in our search, without a [tool.pytest.ini_options]
157+
table and without finding other candidates, the closest to where we started wins.
158+
"""
159+
pytester.makefile(
160+
".toml",
161+
**{
162+
"pyproject": "",
163+
"foo/pyproject": "",
164+
"foo/bar/pyproject": "",
165+
},
166+
)
167+
config = pytester.parseconfig(pytester.path / "foo/bar")
168+
assert config.inipath == pytester.path / "foo/bar/pyproject.toml"
169+
170+
def test_pytest_ini_trumps_pyproject_toml(self, pytester: Pytester) -> None:
171+
"""An empty pyproject.toml is considered as config if no other option is found."""
172+
pytester.makepyprojecttoml("[tool.pytest.ini_options]")
173+
pytest_ini = pytester.makefile(".ini", pytest="")
174+
config = pytester.parseconfig()
175+
assert config.inipath == pytest_ini
176+
147177
def test_toxini_before_lower_pytestini(self, pytester: Pytester) -> None:
148178
sub = pytester.mkdir("sub")
149179
sub.joinpath("tox.ini").write_text(

0 commit comments

Comments
 (0)