Skip to content

Commit 9b1ee0e

Browse files
243 test dep group doesnt seem to install automatically (#267)
* Add functionality to register dependency groups with uv * Add test for use_pytest that it registers the group * Add test that pytest is installed in the dev env * Don't add [tool.uv.default-groups] if it's just going to be the default * Fix bug with adding dev dep multiple times * Fix test
1 parent 941aaa6 commit 9b1ee0e

File tree

4 files changed

+230
-1
lines changed

4 files changed

+230
-1
lines changed

doc/lessons.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,16 @@
5454
1. Use present tense ("Removing..." not "Removed...")
5555
2. Show messages as soon as possible, don't wait until after the operation
5656
3. Use a flag like `first_removal` to ensure messages are shown exactly once
57+
58+
## Configuration Management
59+
60+
- When modifying configuration files like `pyproject.toml`, always consider the impact on existing configurations:
61+
1. Check if a configuration exists before modifying it
62+
2. Don't overwrite existing configurations unless explicitly requested
63+
3. Handle empty or invalid configurations gracefully
64+
4. Use appropriate functions from `pyproject.core` to manage configuration values
65+
- Keep configuration behavior consistent and avoid special cases:
66+
1. Document configuration behavior in docstrings
67+
2. Add test cases to verify behavior
68+
3. Handle edge cases like empty lists or missing configurations
69+
4. Prefer simple, uniform behavior over complex special cases

src/usethis/_integrations/uv/deps.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33

44
from usethis._config import usethis_config
55
from usethis._console import tick_print
6-
from usethis._integrations.pyproject.io_ import read_pyproject_toml
6+
from usethis._integrations.pyproject.core import (
7+
append_config_list,
8+
get_config_value,
9+
)
10+
from usethis._integrations.pyproject.io_ import (
11+
read_pyproject_toml,
12+
)
713
from usethis._integrations.uv.call import call_uv_subprocess
814
from usethis._integrations.uv.errors import UVDepGroupError, UVSubprocessFailedError
915

@@ -51,6 +57,39 @@ def get_deps_from_group(group: str) -> list[Dependency]:
5157
return []
5258

5359

60+
def register_default_group(group: str) -> None:
61+
"""Register a group in the default-groups configuration if it's not already there.
62+
63+
This ensures that dependencies in the group will be installed by default.
64+
"""
65+
if group == "dev":
66+
return
67+
68+
ensure_dev_group_is_defined()
69+
70+
try:
71+
default_groups = get_config_value(["tool", "uv", "default-groups"])
72+
if not isinstance(default_groups, list):
73+
default_groups = []
74+
except KeyError:
75+
default_groups = []
76+
77+
groups_to_add = []
78+
if group not in default_groups:
79+
groups_to_add.append(group)
80+
# Add "dev" if section is empty or if we're adding a new group and "dev" isn't present
81+
if (not default_groups or group != "dev") and "dev" not in default_groups:
82+
groups_to_add.append("dev")
83+
84+
if groups_to_add:
85+
append_config_list(["tool", "uv", "default-groups"], groups_to_add)
86+
87+
88+
def ensure_dev_group_is_defined() -> None:
89+
# Ensure dev group exists in dependency-groups
90+
append_config_list(["dependency-groups", "dev"], [])
91+
92+
5493
def add_deps_to_group(deps: list[Dependency], group: str) -> None:
5594
"""Add a package as a non-build dependency using PEP 735 dependency groups."""
5695
existing_group = get_deps_from_group(group)
@@ -68,6 +107,8 @@ def add_deps_to_group(deps: list[Dependency], group: str) -> None:
68107
f"Adding dependenc{ies} {deps_str} to the '{group}' group in 'pyproject.toml'."
69108
)
70109

110+
register_default_group(group) # Register the group before adding dependencies
111+
71112
for dep in to_add_deps:
72113
try:
73114
if not usethis_config.offline:

tests/usethis/_core/test_tool.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
_HOOK_ORDER,
2020
get_hook_names,
2121
)
22+
from usethis._integrations.pyproject.core import get_config_value
2223
from usethis._integrations.uv.call import call_uv_subprocess
2324
from usethis._integrations.uv.deps import (
2425
Dependency,
@@ -338,6 +339,27 @@ def test_use_deptry_removes_config(self, tmp_path: Path):
338339
# Assert
339340
assert "[tool.deptry]" not in pyproject.read_text()
340341

342+
@pytest.mark.usefixtures("_vary_network_conn")
343+
def test_roundtrip(self, uv_init_dir: Path):
344+
# Arrange
345+
contents = (uv_init_dir / "pyproject.toml").read_text()
346+
347+
# Act
348+
with change_cwd(uv_init_dir):
349+
use_deptry()
350+
use_deptry(remove=True)
351+
352+
# Assert
353+
assert (
354+
(uv_init_dir / "pyproject.toml").read_text()
355+
== contents
356+
+ """\
357+
358+
[dependency-groups]
359+
dev = []
360+
"""
361+
)
362+
341363
class TestPreCommitIntegration:
342364
@pytest.mark.usefixtures("_vary_network_conn")
343365
def test_pre_commit_first(
@@ -1013,6 +1035,25 @@ def test_coverage_integration(
10131035
"☐ Run 'pytest --cov' to run your tests with coverage.\n"
10141036
)
10151037

1038+
@pytest.mark.usefixtures("_vary_network_conn")
1039+
def test_pytest_installed(self, tmp_path: Path):
1040+
with change_cwd(tmp_path):
1041+
# Act
1042+
use_pytest()
1043+
1044+
# Assert
1045+
# This will raise if pytest is not installed
1046+
call_uv_subprocess(["pip", "show", "pytest"])
1047+
1048+
def test_registers_test_group(self, tmp_path: Path):
1049+
with change_cwd(tmp_path):
1050+
# Act
1051+
use_pytest()
1052+
1053+
# Assert
1054+
default_groups = get_config_value(["tool", "uv", "default-groups"])
1055+
assert "test" in default_groups
1056+
10161057
class TestRemove:
10171058
class TestRuffIntegration:
10181059
def test_deselected(self, uv_init_dir: Path):

tests/usethis/_integrations/uv/test_deps.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
import pytest
44

55
from usethis._config import usethis_config
6+
from usethis._integrations.pyproject.core import (
7+
get_config_value,
8+
remove_config_value,
9+
)
610
from usethis._integrations.uv.deps import (
711
Dependency,
812
add_deps_to_group,
913
get_dep_groups,
1014
get_deps_from_group,
1115
is_dep_in_any_group,
1216
is_dep_satisfied_in,
17+
register_default_group,
1318
remove_deps_from_group,
1419
)
1520
from usethis._test import change_cwd
@@ -242,6 +247,26 @@ def test_combine_extras_alphabetical(self, uv_init_dir: Path):
242247
content = (uv_init_dir / "pyproject.toml").read_text()
243248
assert "coverage[extra,toml]" in content
244249

250+
@pytest.mark.usefixtures("_vary_network_conn")
251+
def test_registers_default_group(self, uv_init_dir: Path):
252+
with change_cwd(uv_init_dir):
253+
# Act
254+
add_deps_to_group([Dependency(name="pytest")], "test")
255+
256+
# Assert
257+
default_groups = get_config_value(["tool", "uv", "default-groups"])
258+
assert "test" in default_groups
259+
260+
@pytest.mark.usefixtures("_vary_network_conn")
261+
def test_dev_group_not_registered(self, uv_init_dir: Path):
262+
with change_cwd(uv_init_dir):
263+
# Act
264+
add_deps_to_group([Dependency(name="black")], "dev")
265+
266+
# Assert
267+
# Tool section shouldn't even exist in pyproject.toml
268+
assert "tool" not in (uv_init_dir / "pyproject.toml").read_text()
269+
245270

246271
class TestRemoveDepsFromGroup:
247272
@pytest.mark.usefixtures("_vary_network_conn")
@@ -344,6 +369,27 @@ def test_extras(self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]):
344369
== "✔ Removing dependency 'pytest' from the 'test' group in 'pyproject.toml'.\n"
345370
)
346371

372+
@pytest.mark.usefixtures("_vary_network_conn")
373+
def test_group_not_in_dependency_groups(
374+
self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]
375+
):
376+
with change_cwd(uv_init_dir):
377+
# Arrange
378+
with usethis_config.set(quiet=True):
379+
add_deps_to_group([Dependency(name="pytest")], "test")
380+
381+
# Remove the group from dependency-groups but keep it in default-groups
382+
remove_config_value(["dependency-groups", "test"])
383+
384+
# Act
385+
remove_deps_from_group([Dependency(name="pytest")], "test")
386+
387+
# Assert
388+
assert not get_deps_from_group("test")
389+
out, err = capfd.readouterr()
390+
assert not err
391+
assert not out
392+
347393

348394
class TestIsDepInAnyGroup:
349395
def test_no_group(self, uv_init_dir: Path):
@@ -430,3 +476,91 @@ def test_multiple(self):
430476

431477
# Assert
432478
assert result
479+
480+
481+
class TestRegisterDefaultGroup:
482+
def test_section_not_exists_adds_dev(self, tmp_path: Path):
483+
# Arrange
484+
(tmp_path / "pyproject.toml").write_text("")
485+
486+
with change_cwd(tmp_path):
487+
# Act
488+
register_default_group("test")
489+
490+
# Assert
491+
default_groups = get_config_value(["tool", "uv", "default-groups"])
492+
assert set(default_groups) == {"test", "dev"}
493+
494+
def test_empty_section_adds_dev(self, tmp_path: Path):
495+
# Arrange
496+
(tmp_path / "pyproject.toml").write_text("""\
497+
[tool.uv]
498+
""")
499+
500+
with change_cwd(tmp_path):
501+
# Act
502+
register_default_group("test")
503+
504+
# Assert
505+
default_groups = get_config_value(["tool", "uv", "default-groups"])
506+
assert set(default_groups) == {"test", "dev"}
507+
508+
def test_empty_default_groups_adds_dev(self, tmp_path: Path):
509+
# Arrange
510+
(tmp_path / "pyproject.toml").write_text("""\
511+
[tool.uv]
512+
default-groups = []
513+
""")
514+
515+
with change_cwd(tmp_path):
516+
# Act
517+
register_default_group("test")
518+
519+
# Assert
520+
default_groups = get_config_value(["tool", "uv", "default-groups"])
521+
assert set(default_groups) == {"test", "dev"}
522+
523+
def test_existing_section_no_dev_added_if_no_other_groups(self, tmp_path: Path):
524+
# Arrange
525+
(tmp_path / "pyproject.toml").write_text("""\
526+
[tool.uv]
527+
default-groups = ["test"]
528+
""")
529+
530+
with change_cwd(tmp_path):
531+
# Act
532+
register_default_group("test")
533+
534+
# Assert
535+
default_groups = get_config_value(["tool", "uv", "default-groups"])
536+
assert set(default_groups) == {"test"}
537+
538+
def test_existing_section_no_dev_added_if_dev_exists(self, tmp_path: Path):
539+
# Arrange
540+
(tmp_path / "pyproject.toml").write_text("""\
541+
[tool.uv]
542+
default-groups = ["test", "dev"]
543+
""")
544+
545+
with change_cwd(tmp_path):
546+
# Act
547+
register_default_group("docs")
548+
549+
# Assert
550+
default_groups = get_config_value(["tool", "uv", "default-groups"])
551+
assert set(default_groups) == {"test", "dev", "docs"}
552+
553+
def test_existing_section_adds_dev_with_new_group(self, tmp_path: Path):
554+
# Arrange
555+
(tmp_path / "pyproject.toml").write_text("""\
556+
[tool.uv]
557+
default-groups = ["test"]
558+
""")
559+
560+
with change_cwd(tmp_path):
561+
# Act
562+
register_default_group("docs")
563+
564+
# Assert
565+
default_groups = get_config_value(["tool", "uv", "default-groups"])
566+
assert set(default_groups) == {"test", "docs", "dev"}

0 commit comments

Comments
 (0)