Skip to content

Commit ce02cc7

Browse files
Add more detailed error message for pyproject.toml 'dependency-groups` validation (#553)
* Check runner.invoke has run successfully in tests * Add more detailed error message for pyproject.toml 'dependency-groups` validation * Add git to test_maximal_config test * Add context to assert error in test_maximal_config test * Use uv_init_repo_dir for maximal config test (since pre-commit needs git) * Deal more carefully with Python version pinning * Add git to test_several_tools_add_and_remove test * Add missing check to test_help_flag * Test more specific error message for [dependency-groups] section validation message
1 parent faa9882 commit ce02cc7

7 files changed

Lines changed: 69 additions & 24 deletions

File tree

src/usethis/_integrations/uv/deps.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import pydantic
34
from packaging.requirements import Requirement
45
from pydantic import BaseModel, TypeAdapter
56

@@ -36,9 +37,17 @@ def get_dep_groups() -> dict[str, list[Dependency]]:
3637
# will be deprecated.
3738
return {}
3839

39-
req_strs_by_group = TypeAdapter(dict[str, list[str]]).validate_python(
40-
dep_groups_section
41-
)
40+
try:
41+
req_strs_by_group = TypeAdapter(dict[str, list[str]]).validate_python(
42+
dep_groups_section
43+
)
44+
except pydantic.ValidationError as err:
45+
msg = (
46+
"Failed to parse the 'dependency-groups' section in 'pyproject.toml':\n"
47+
f"{err}\n\n"
48+
"Please check the section and try again."
49+
)
50+
raise UVDepGroupError(msg) from None
4251
reqs_by_group = {
4352
group: [Requirement(req_str) for req_str in req_strs]
4453
for group, req_strs in req_strs_by_group.items()

tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ def _uv_init_dir(tmp_path_factory: pytest.TempPathFactory) -> Path:
1919
"init",
2020
"--lib",
2121
"--python",
22+
# Deliberately kept at at a version other than the latest version to
23+
# check range checks e.g. for Bitbucket pipelines matrixes.
2224
"3.12",
2325
"--vcs",
2426
"none",

tests/test_help.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ def test_help_flag():
1010
runner = CliRunner()
1111

1212
# Act
13-
runner.invoke(app, ["--help"])
13+
result = runner.invoke(app, ["--help"])
14+
assert result.exit_code == 0, result.stdout

tests/usethis/_integrations/uv/test_deps.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,20 @@ def test_no_pyproject(self, tmp_path: Path):
103103
# Assert
104104
assert result == {}
105105

106+
def test_invalid_dtype(self, tmp_path: Path):
107+
# Arrange
108+
(tmp_path / "pyproject.toml").write_text("""\
109+
[dependency-groups]
110+
test="not a list"
111+
""")
112+
# Act, Assert
113+
with (
114+
change_cwd(tmp_path),
115+
PyprojectTOMLManager(),
116+
pytest.raises(UVDepGroupError),
117+
):
118+
get_dep_groups()
119+
106120

107121
class TestAddDepsToGroup:
108122
@pytest.mark.usefixtures("_vary_network_conn")

tests/usethis/_interface/maximal_bitbucket_pipelines.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ pipelines:
1919
script:
2020
- *install-uv
2121
- uv run pre-commit run --all-files
22+
- step:
23+
name: Test on 3.12
24+
caches:
25+
- uv
26+
script:
27+
- *install-uv
28+
- uv run --python 3.12 pytest -x --junitxml=test-reports/report.xml
2229
- step:
2330
name: Test on 3.13
2431
caches:

tests/usethis/_interface/test_interface_ci.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,30 +41,34 @@ def test_remove(self, tmp_path: Path):
4141
assert not (tmp_path / "bitbucket-pipelines.yml").exists()
4242

4343
@pytest.mark.usefixtures("_vary_network_conn")
44-
def test_maximal_config(self, tmp_path: Path):
44+
def test_maximal_config(self, uv_init_repo_dir: Path):
45+
# N.B. uv_init_repo_dir is used since we need git if we want to add pre-commit
4546
runner = CliRunner()
46-
with change_cwd(tmp_path):
47-
# Pin the Python version
48-
(tmp_path / ".python-version").write_text(
49-
"3.13" # Bump to latest version of Python
50-
)
51-
47+
with change_cwd(uv_init_repo_dir):
5248
# Arrange
5349
for tool_command in ALL_TOOL_COMMANDS:
5450
if not usethis_config.offline:
55-
runner.invoke(main_app, ["tool", tool_command])
51+
result = runner.invoke(main_app, ["tool", tool_command])
5652
else:
57-
runner.invoke(main_app, ["tool", tool_command, "--offline"])
53+
result = runner.invoke(
54+
main_app, ["tool", tool_command, "--offline"]
55+
)
56+
assert not result.exit_code, f"{tool_command=}: {result.stdout}"
5857

5958
# Act
60-
runner.invoke(app) # The CI menu only has 1 command (bitbucket
59+
result = runner.invoke(app) # The CI menu only has 1 command (bitbucket
6160
# pipelines) so we skip the subcommand here
61+
assert not result.exit_code, result.stdout
6262

6363
# Assert
6464
expected_yml = (
65+
# N.B. when updating this file, check it against the validator:
66+
# https://bitbucket.org/product/pipelines/validator
6567
Path(__file__).parent / "maximal_bitbucket_pipelines.yml"
6668
).read_text()
67-
assert (tmp_path / "bitbucket-pipelines.yml").read_text() == expected_yml
69+
assert (
70+
uv_init_repo_dir / "bitbucket-pipelines.yml"
71+
).read_text() == expected_yml
6872

6973
def test_invalid_pyproject_toml(self, tmp_path: Path):
7074
# Arrange

tests/usethis/_interface/test_tool.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -486,17 +486,25 @@ def test_several_tools_add_and_remove(tmp_path: Path):
486486
(tmp_path / "src" / "benchmark").mkdir(exist_ok=True)
487487
(tmp_path / "src" / "benchmark" / "__init__.py").touch(exist_ok=True)
488488

489-
# Act
490489
runner = CliRunner()
491490
with change_cwd(tmp_path):
492-
runner.invoke(app, ["pytest"])
493-
runner.invoke(app, ["coverage.py"])
494-
runner.invoke(app, ["ruff"])
495-
runner.invoke(app, ["deptry"])
496-
runner.invoke(app, ["pre-commit"])
497-
runner.invoke(app, ["ruff", "--remove"])
498-
runner.invoke(app, ["pyproject-fmt"])
499-
runner.invoke(app, ["pytest", "--remove"])
491+
# Act, Assert
492+
result = runner.invoke(app, ["pytest"])
493+
assert not result.exit_code, result.stdout
494+
result = runner.invoke(app, ["coverage"])
495+
assert not result.exit_code, result.stdout
496+
result = runner.invoke(app, ["ruff"])
497+
assert not result.exit_code, result.stdout
498+
result = runner.invoke(app, ["deptry"])
499+
assert not result.exit_code, result.stdout
500+
result = runner.invoke(app, ["pre-commit"])
501+
assert not result.exit_code, result.stdout
502+
result = runner.invoke(app, ["ruff", "--remove"])
503+
assert not result.exit_code, result.stdout
504+
result = runner.invoke(app, ["pyproject-fmt"])
505+
assert not result.exit_code, result.stdout
506+
result = runner.invoke(app, ["pytest", "--remove"])
507+
assert not result.exit_code, result.stdout
500508

501509

502510
def test_tool_matches_command():

0 commit comments

Comments
 (0)