Skip to content

Commit acafd00

Browse files
authored
Consider pyproject.toml files for config if no other config files were found (#11962)
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 46e6fb1 commit acafd00

File tree

4 files changed

+46
-5
lines changed

4 files changed

+46
-5
lines changed

changelog/11962.improvement.rst

+1
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

+9-4
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,20 @@ 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

186+
The configuration file also determines the value of the ``rootpath``.
187+
183188
The :class:`Config <pytest.Config>` object (accessible via hooks or through the :fixture:`pytestconfig` fixture)
184189
will subsequently carry these attributes:
185190

186-
- :attr:`config.rootpath <pytest.Config.rootpath>`: the determined root directory, guaranteed to exist.
191+
- :attr:`config.rootpath <pytest.Config.rootpath>`: the determined root directory, guaranteed to exist. It is used as
192+
a reference directory for constructing test addresses ("nodeids") and can be used also by plugins for storing
193+
per-testrun information.
187194

188195
- :attr:`config.inipath <pytest.Config.inipath>`: the determined ``configfile``, may be ``None``
189196
(it is named ``inipath`` for historical reasons).
@@ -193,9 +200,7 @@ will subsequently carry these attributes:
193200
versions of the older ``config.rootdir`` and ``config.inifile``, which have type
194201
``py.path.local``, and still exist for backward compatibility.
195202

196-
The ``rootdir`` is used as a reference directory for constructing test
197-
addresses ("nodeids") and can be used also by plugins for storing
198-
per-testrun information.
203+
199204

200205
Example:
201206

src/_pytest/config/findpaths.py

+5
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

+31-1
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+
"""A pytest.ini always take precedence over a pyproject.toml file."""
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)