Skip to content

Commit e41c1b5

Browse files
committed
fix: provide clear messaging about config file loading
Fix bug where tox is silently ignoring loading pyproject.toml files that failed be loaded by tomllint and falling back to other config. - if pyproject.toml does not have one of the two expected sections it will be logged, so it would be visible with `-v` - make pyproject.toml failure to load a fatal error with clear information about cause - keeps ignoring missing pyproject.toml files as expected With these, users of tox will not be surprised when they break the config file by mistake, like adding a duplicate section.
1 parent 7d4cb4e commit e41c1b5

File tree

9 files changed

+41
-14
lines changed

9 files changed

+41
-14
lines changed

docs/changelog/3578.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Makes the error message more clear when pyproject.toml file cannot be loaded
2+
or is missing expected keys.

src/tox/config/source/discover.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pathlib import Path
66
from typing import TYPE_CHECKING
77

8+
from tox.config.types import MissingRequiredConfigKeyError
89
from tox.report import HandledError
910

1011
from .legacy_toml import LegacyToml
@@ -59,21 +60,32 @@ def _locate_source() -> Source | None:
5960
for base in chain([folder], folder.parents):
6061
for src_type in SOURCE_TYPES:
6162
candidate: Path = base / src_type.FILENAME
62-
try:
63-
return src_type(candidate)
64-
except ValueError:
65-
pass
63+
if candidate.exists():
64+
try:
65+
return src_type(candidate)
66+
except MissingRequiredConfigKeyError as exc:
67+
msg = f"{src_type.__name__} skipped loading {candidate.resolve()} due to {exc}"
68+
logging.info(msg)
69+
except ValueError as exc:
70+
msg = f"{src_type.__name__} failed loading {candidate.resolve()} due to {exc}"
71+
raise HandledError(msg) from exc
6672
return None
6773

6874

6975
def _load_exact_source(config_file: Path) -> Source:
7076
# if the filename matches to the letter some config file name do not fallback to other source types
77+
if not config_file.exists():
78+
msg = f"config file {config_file} does not exist"
79+
raise HandledError(msg)
7180
exact_match = [s for s in SOURCE_TYPES if config_file.name == s.FILENAME] # pragma: no cover
7281
for src_type in exact_match or SOURCE_TYPES: # pragma: no branch
7382
try:
7483
return src_type(config_file)
75-
except ValueError: # noqa: PERF203
84+
except MissingRequiredConfigKeyError: # noqa: PERF203
7685
pass
86+
except ValueError as exc:
87+
msg = f"{src_type.__name__} failed loading {config_file.resolve()} due to {exc}"
88+
raise HandledError(msg) from exc
7789
msg = f"could not recognize config file {config_file}"
7890
raise HandledError(msg)
7991

@@ -88,7 +100,7 @@ def _create_default_source(root_dir: Path | None) -> Source:
88100
else: # if not set use where we find pyproject.toml in the tree or cwd
89101
empty = root_dir
90102
names = " or ".join({i.FILENAME: None for i in SOURCE_TYPES})
91-
logging.warning("No %s found, assuming empty tox.ini at %s", names, empty)
103+
logging.warning("No loadable %s found, assuming empty tox.ini at %s", names, empty)
92104
return ToxIni(empty / "tox.ini", content="")
93105

94106

src/tox/config/source/legacy_toml.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import sys
44

5+
from tox.config.types import MissingRequiredConfigKeyError
6+
57
if sys.version_info >= (3, 11): # pragma: no cover (py311+)
68
import tomllib
79
else: # pragma: no cover (py311+)
@@ -27,7 +29,8 @@ def __init__(self, path: Path) -> None:
2729
try:
2830
content = toml_content["tool"]["tox"]["legacy_tox_ini"]
2931
except KeyError as exc:
30-
raise ValueError(path) from exc
32+
msg = f"`tool.tox.legacy_tox_ini` missing from {path}"
33+
raise MissingRequiredConfigKeyError(msg) from exc
3134
super().__init__(path, content=content)
3235

3336

src/tox/config/source/setup_cfg.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ class SetupCfg(IniSource):
1818
def __init__(self, path: Path) -> None:
1919
super().__init__(path)
2020
if not self._parser.has_section(self.CORE_SECTION.key):
21-
raise ValueError
21+
msg = f"section {self.CORE_SECTION.key} not found"
22+
raise ValueError(msg)
2223

2324

2425
__all__ = ("SetupCfg",)

src/tox/config/source/toml_pyproject.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from tox.config.loader.section import Section
99
from tox.config.loader.toml import TomlLoader
10+
from tox.config.types import MissingRequiredConfigKeyError
1011
from tox.report import HandledError
1112

1213
from .api import Source
@@ -81,7 +82,7 @@ def __init__(self, path: Path) -> None:
8182
our_content = our_content[key]
8283
self._our_content = our_content
8384
except KeyError as exc:
84-
raise ValueError(path) from exc
85+
raise MissingRequiredConfigKeyError(path) from exc
8586
super().__init__(path)
8687

8788
def get_core_section(self) -> Section:

src/tox/config/types.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ class CircularChainError(ValueError):
1010
"""circular chain in config"""
1111

1212

13+
class MissingRequiredConfigKeyError(ValueError):
14+
"""missing required config key
15+
16+
Used by the two toml loaders in order to identify if config keys are present.
17+
"""
18+
19+
1320
class Command: # noqa: PLW1641
1421
"""A command to execute."""
1522

tests/config/source/test_discover.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111
def out_no_src(path: Path) -> str:
1212
return (
13-
f"ROOT: No tox.ini or setup.cfg or pyproject.toml or tox.toml found, assuming empty tox.ini at {path}\n"
14-
f"default environments:\npy -> [no description]\n"
13+
f"ROOT: No loadable tox.ini or setup.cfg or pyproject.toml or tox.toml found, assuming empty tox.ini at {path}"
14+
f"\ndefault environments:\npy -> [no description]\n"
1515
)
1616

1717

@@ -47,4 +47,4 @@ def test_bad_src_content(tox_project: ToxProjectCreator, tmp_path: Path) -> None
4747

4848
outcome = project.run("l", "-c", str(tmp_path / "setup.cfg"))
4949
outcome.assert_failed()
50-
assert outcome.out == f"ROOT: HandledError| could not recognize config file {tmp_path / 'setup.cfg'}\n"
50+
assert outcome.out == f"ROOT: HandledError| config file {tmp_path / 'setup.cfg'} does not exist\n"

tests/config/source/test_setup_cfg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ def test_bad_conf_setup_cfg(tox_project: ToxProjectCreator) -> None:
1919
filename = str(project.path / "setup.cfg")
2020
outcome = project.run("l", "-c", filename)
2121
outcome.assert_failed()
22-
assert outcome.out == f"ROOT: HandledError| could not recognize config file {filename}\n"
22+
assert outcome.out == f"ROOT: HandledError| SetupCfg failed loading {filename} due to section tox:tox not found\n"

tests/session/cmd/test_legacy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ def test_legacy_list_env_with_no_tox_file(tox_project: ToxProjectCreator) -> Non
6666
outcome = project.run("le", "-l")
6767
outcome.assert_success()
6868
out = (
69-
f"ROOT: No tox.ini or setup.cfg or pyproject.toml or tox.toml found, assuming empty tox.ini at {project.path}\n"
69+
"ROOT: No loadable tox.ini or setup.cfg or pyproject.toml or tox.toml found, assuming empty tox.ini at "
70+
f"{project.path}\n"
7071
)
7172
assert not outcome.err
7273
assert outcome.out == out

0 commit comments

Comments
 (0)