Skip to content

Commit 941aaa6

Browse files
Remove pyproject.toml config when removing deptry (#266)
* Rmeove pyproject.toml config when removing deptry * Fix accidental change to removal message * Don't report null removals
1 parent d8f1070 commit 941aaa6

File tree

7 files changed

+176
-28
lines changed

7 files changed

+176
-28
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,6 @@ cython_debug/
160160
# and can be added to the global gitignore or merged into this file. For a more nuclear
161161
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
162162
#.idea/
163+
164+
# Windsurf config
165+
.windsurfrules

doc/lessons.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Lessons Learned
2+
3+
## Test Driven Development
4+
5+
- ALWAYS write tests before implementing functionality. This helps ensure the code meets requirements and avoids unnecessary complexity.
6+
- When implementing a new feature, first write a failing test that describes the desired behavior.
7+
- Tests should be specific and focused on one piece of functionality.
8+
- After implementing changes, run the full test suite and code quality checks to catch any regressions.
9+
- Test classes should be at the root level of the test file, not nested within other test classes, to ensure proper test discovery and organization.
10+
- When running pytest for a specific test class, use the format `pytest path/to/test.py::TestClassName -v`
11+
- For efficiency, run only the specific tests you need:
12+
- For a single test: `pytest path/to/test.py::TestClass::test_name`
13+
- For a test in a nested class: `pytest path/to/test.py::TestClass::TestNestedClass::test_name`
14+
- For a parameterized test: `pytest path/to/test.py::test_name`
15+
- Structure tests with explicit comments to improve readability:
16+
```python
17+
def test_something(self):
18+
# Arrange - set up test data and conditions
19+
20+
# Act - perform the action being tested
21+
22+
# Assert - verify the results
23+
```
24+
25+
## Code Quality
26+
27+
- Avoid code duplication in error handling paths. When you see similar error handling repeated, consider consolidating the logic:
28+
1. Collect all items to process up front
29+
2. Use a single loop to handle common operations and error cases
30+
3. Consolidate status messages or logging
31+
- Keep docstrings focused on interface behavior, not implementation details
32+
- Never make changes unrelated to the current task or failing test
33+
- Keep a clear separation between interface documentation (docstrings) and implementation notes (comments)
34+
- Run pre-commit checks after making changes to ensure code quality and formatting standards are met
35+
- Never include type information in docstrings when it's already present in type annotations
36+
- ALWAYS run pre-commit checks after any code changes are considered complete:
37+
1. Run `uv run pre-commit run --all-files`
38+
2. If any checks modify files, run the checks again to ensure everything is clean
39+
3. Never consider a change complete until all pre-commit checks pass
40+
- Use `contextlib.suppress` instead of empty try-except blocks:
41+
```python
42+
# Instead of:
43+
try:
44+
do_something()
45+
except SomeError:
46+
pass
47+
48+
# Use:
49+
from contextlib import suppress
50+
with suppress(SomeError):
51+
do_something()
52+
```
53+
- Display user feedback messages:
54+
1. Use present tense ("Removing..." not "Removed...")
55+
2. Show messages as soon as possible, don't wait until after the operation
56+
3. Use a flag like `first_removal` to ensure messages are shown exactly once

src/usethis/_core/tool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ def use_deptry(*, remove: bool = False) -> None:
9090
box_print("Run 'deptry src' to run deptry.")
9191
else:
9292
tool.remove_pre_commit_repo_configs()
93+
tool.remove_pyproject_configs()
9394
remove_bitbucket_step_from_default(get_bitbucket_deptry_step())
94-
9595
remove_deps_from_group(tool.dev_deps, "dev")
9696

9797

src/usethis/_tool.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ def get_pre_commit_repos(self) -> list[LocalRepo | UriRepo]:
5959
return []
6060

6161
def get_pyproject_configs(self) -> list[PyProjectConfig]:
62-
"""Get the pyproject configurations for the tool."""
62+
"""Get the pyproject configurations for the tool.
63+
64+
All configuration keys returned by this method must be sub-keys of the
65+
keys returned by get_pyproject_id_keys().
66+
"""
6367
return []
6468

6569
def get_associated_ruff_rules(self) -> list[str]:
@@ -155,15 +159,21 @@ def add_pyproject_configs(self) -> None:
155159
first_addition = False
156160

157161
def remove_pyproject_configs(self) -> None:
158-
"""Remove the tool's pyproject.toml configuration."""
159-
configs = self.get_pyproject_configs()
160-
if not configs:
161-
return
162+
"""Remove all pyproject.toml configurations associated with this tool.
162163
164+
This includes any tool-specific sections in the pyproject.toml file.
165+
If no configurations exist, this method has no effect.
166+
"""
167+
# Collect all keys to remove
168+
keys_to_remove = [
169+
config.id_keys for config in self.get_pyproject_configs()
170+
] + self.get_pyproject_id_keys()
171+
172+
# Try to remove the first key to trigger the message
163173
first_removal = True
164-
for config in configs:
174+
for keys in keys_to_remove:
165175
try:
166-
remove_config_value(config.id_keys)
176+
remove_config_value(keys)
167177
except PyProjectTOMLValueMissingError:
168178
pass
169179
else:
@@ -238,6 +248,9 @@ def get_pre_commit_repos(self) -> list[LocalRepo | UriRepo]:
238248
)
239249
]
240250

251+
def get_pyproject_id_keys(self) -> list[list[str]]:
252+
return [["tool", "deptry"]]
253+
241254

242255
class PreCommitTool(Tool):
243256
@property

tests/usethis/_core/test_tool.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,21 @@ def test_bitbucket_integration(self, uv_init_repo_dir: Path):
323323
contents = (uv_init_repo_dir / "bitbucket-pipelines.yml").read_text()
324324
assert "deptry" not in contents
325325

326+
def test_use_deptry_removes_config(self, tmp_path: Path):
327+
"""Test that use_deptry removes the tool's config when removing."""
328+
# Arrange
329+
pyproject = tmp_path / "pyproject.toml"
330+
pyproject.write_text("""[tool.deptry]
331+
ignore_missing = ["pytest"]
332+
""")
333+
334+
# Act
335+
with change_cwd(tmp_path):
336+
use_deptry(remove=True)
337+
338+
# Assert
339+
assert "[tool.deptry]" not in pyproject.read_text()
340+
326341
class TestPreCommitIntegration:
327342
@pytest.mark.usefixtures("_vary_network_conn")
328343
def test_pre_commit_first(
@@ -606,14 +621,14 @@ def test_requirements_txt_used(
606621
# Act
607622
use_pre_commit(remove=True)
608623

609-
# Assert
610-
out, _ = capfd.readouterr()
611-
assert out == (
612-
"✔ Ensuring pre-commit hooks are uninstalled.\n"
613-
"✔ Removing '.pre-commit-config.yaml'.\n"
614-
"✔ Removing dependency 'pre-commit' from the 'dev' group in 'pyproject.toml'.\n"
615-
"☐ Run 'uv export --no-dev --output-file=requirements.txt' to write \n'requirements.txt'.\n"
616-
)
624+
# Assert
625+
out, _ = capfd.readouterr()
626+
assert out == (
627+
"✔ Ensuring pre-commit hooks are uninstalled.\n"
628+
"✔ Removing '.pre-commit-config.yaml'.\n"
629+
"✔ Removing dependency 'pre-commit' from the 'dev' group in 'pyproject.toml'.\n"
630+
"☐ Run 'uv export --no-dev --output-file=requirements.txt' to write \n'requirements.txt'.\n"
631+
)
617632

618633
@pytest.mark.usefixtures("_vary_network_conn")
619634
def test_pyproject_fmt_used(
@@ -628,15 +643,15 @@ def test_pyproject_fmt_used(
628643
# Act
629644
use_pre_commit(remove=True)
630645

631-
# Assert
632-
out, _ = capfd.readouterr()
633-
assert out == (
634-
"✔ Ensuring pre-commit hooks are uninstalled.\n"
635-
"✔ Removing '.pre-commit-config.yaml'.\n"
636-
"✔ Removing dependency 'pre-commit' from the 'dev' group in 'pyproject.toml'.\n"
637-
"✔ Adding dependency 'pyproject-fmt' to the 'dev' group in 'pyproject.toml'.\n"
638-
"☐ Run 'pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
639-
)
646+
# Assert
647+
out, _ = capfd.readouterr()
648+
assert out == (
649+
"✔ Ensuring pre-commit hooks are uninstalled.\n"
650+
"✔ Removing '.pre-commit-config.yaml'.\n"
651+
"✔ Removing dependency 'pre-commit' from the 'dev' group in 'pyproject.toml'.\n"
652+
"✔ Adding dependency 'pyproject-fmt' to the 'dev' group in 'pyproject.toml'.\n"
653+
"☐ Run 'pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
654+
)
640655

641656
class TestBitbucketCIIntegration:
642657
def test_prexisting(self, uv_init_repo_dir: Path):

tests/usethis/test_tool.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from usethis._integrations.pyproject.core import set_config_value
99
from usethis._integrations.uv.deps import Dependency, add_deps_to_group
1010
from usethis._test import change_cwd
11-
from usethis._tool import Tool
11+
from usethis._tool import ALL_TOOLS, DeptryTool, Tool
1212

1313

1414
class DefaultTool(Tool):
@@ -669,3 +669,64 @@ def get_pyproject_configs(self) -> list[PyProjectConfig]:
669669
out, err = capfd.readouterr()
670670
assert not err
671671
assert out == "✔ Adding mytool config to 'pyproject.toml'.\n"
672+
673+
674+
class TestDeptryTool:
675+
"""Tests for DeptryTool."""
676+
677+
def test_get_pyproject_id_keys(self):
678+
"""Test that get_pyproject_id_keys returns the correct keys."""
679+
# Arrange
680+
tool = DeptryTool()
681+
682+
# Act
683+
result = tool.get_pyproject_id_keys()
684+
685+
# Assert
686+
assert result == [["tool", "deptry"]]
687+
688+
def test_remove_pyproject_configs_removes_deptry_section(self, tmp_path: Path):
689+
"""Test that remove_pyproject_configs removes the deptry section."""
690+
# Arrange
691+
pyproject = tmp_path / "pyproject.toml"
692+
pyproject.write_text("""[tool.deptry]
693+
ignore_missing = ["pytest"]
694+
""")
695+
696+
# Act
697+
with change_cwd(tmp_path):
698+
tool = DeptryTool()
699+
tool.remove_pyproject_configs()
700+
701+
# Assert
702+
assert "[tool.deptry]" not in pyproject.read_text()
703+
assert "ignore_missing" not in pyproject.read_text()
704+
705+
def test_config_keys_are_subkeys_of_id_keys(self):
706+
"""Test that all config keys are subkeys of id keys."""
707+
# Arrange
708+
tool = DeptryTool()
709+
710+
# Act
711+
id_keys = tool.get_pyproject_id_keys()
712+
configs = tool.get_pyproject_configs()
713+
714+
# Assert
715+
for config in configs:
716+
# For each config, check if its keys are a subset of any id_keys
717+
assert any(config.id_keys[: len(id_key)] == id_key for id_key in id_keys), (
718+
f"Config keys {config.id_keys} not covered by ID keys {id_keys}"
719+
)
720+
721+
722+
@pytest.mark.parametrize("tool", ALL_TOOLS)
723+
def test_all_tools_config_keys_are_subkeys_of_id_keys(tool: Tool):
724+
"""Test that all tools' config keys are subkeys of their ID keys."""
725+
id_keys = tool.get_pyproject_id_keys()
726+
configs = tool.get_pyproject_configs()
727+
728+
for config in configs:
729+
# For each config, check if its keys are a subset of any id_keys
730+
assert any(config.id_keys[: len(id_key)] == id_key for id_key in id_keys), (
731+
f"Config keys {config.id_keys} not covered by ID keys in {tool.name}"
732+
)

uv.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)