Skip to content

Commit 98c8dd1

Browse files
Implement correct resolution strategy for codespell (#547)
1 parent a45e582 commit 98c8dd1

File tree

2 files changed

+67
-17
lines changed

2 files changed

+67
-17
lines changed

src/usethis/_tool.py

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
from usethis._integrations.uv.used import is_uv_used
7171
from usethis._io import Key, KeyValueFileManager
7272

73-
ResolutionT: TypeAlias = Literal["first", "bespoke"]
73+
ResolutionT: TypeAlias = Literal["first", "first_content", "bespoke"]
7474

7575

7676
class ConfigSpec(BaseModel):
@@ -87,6 +87,10 @@ class ConfigSpec(BaseModel):
8787
- "first": Using the order in file_managers, the first file found to
8888
exist is used. All subsequent files are ignored. If no files are
8989
found, the preferred file manager is used.
90+
- "first_content": Using the order in file_managers, the first file
91+
to contain managed configuration (as per config_items) is used.
92+
All subsequent files are ignored. If no files are found with any
93+
managed config, the found, the preferred file manager is used.
9094
config_items: A list of configuration items that can be managed by the tool.
9195
"""
9296

@@ -341,21 +345,15 @@ def _get_active_config_file_managers_from_resolution(
341345
path = Path.cwd() / relative_path
342346
if path.exists() and path.is_file():
343347
return {file_manager}
344-
345-
file_managers = file_manager_by_relative_path.values()
346-
if not file_managers:
347-
return set()
348-
349-
# Use the preferred default file since there's no existing file.
350-
preferred_file_manager = self.preferred_file_manager()
351-
if preferred_file_manager not in file_managers:
352-
msg = (
353-
f"The preferred file manager '{preferred_file_manager}' is not "
354-
f"among the file managers '{file_managers}' for the tool "
355-
f"'{self.name}'"
356-
)
357-
raise NotImplementedError(msg)
358-
return {preferred_file_manager}
348+
elif resolution == "first_content":
349+
config_spec = self.get_config_spec()
350+
for relative_path, file_manager in file_manager_by_relative_path.items():
351+
path = Path.cwd() / relative_path
352+
if path.exists() and path.is_file():
353+
# We check whether any of the managed config exists
354+
for config_item in config_spec.config_items:
355+
if config_item.root[relative_path].keys in file_manager:
356+
return {file_manager}
359357
elif resolution == "bespoke":
360358
msg = (
361359
"The bespoke resolution method is not yet implemented for the tool "
@@ -365,6 +363,20 @@ def _get_active_config_file_managers_from_resolution(
365363
else:
366364
assert_never(resolution)
367365

366+
file_managers = file_manager_by_relative_path.values()
367+
if not file_managers:
368+
return set()
369+
370+
preferred_file_manager = self.preferred_file_manager()
371+
if preferred_file_manager not in file_managers:
372+
msg = (
373+
f"The preferred file manager '{preferred_file_manager}' is not "
374+
f"among the file managers '{file_managers}' for the tool "
375+
f"'{self.name}'"
376+
)
377+
raise NotImplementedError(msg)
378+
return {preferred_file_manager}
379+
368380
def preferred_file_manager(self) -> KeyValueFileManager:
369381
"""If there is no currently active config file, this is the preferred one."""
370382
return PyprojectTOMLManager()
@@ -615,7 +627,7 @@ def get_config_spec(self) -> ConfigSpec:
615627
SetupCFGManager(),
616628
PyprojectTOMLManager(),
617629
],
618-
resolution="first",
630+
resolution="first_content",
619631
config_items=[
620632
ConfigItem(
621633
description="Overall config",

tests/usethis/_core/test_core_tool.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,44 @@ def test_adding_twice(
193193
assert not err
194194
assert out == ("☐ Run 'codespell' to run the Codespell spellchecker.\n")
195195

196+
def test_setup_cfg_nonempty(self, uv_init_dir: Path):
197+
# https://github.com/nathanjmcdougall/usethis-python/issues/542
198+
# Basically we want to make sure the "first_content" resolution strategy
199+
# works correctly.
200+
201+
# Arrange
202+
content = """\
203+
[codespell]
204+
foo = bar
205+
"""
206+
(uv_init_dir / "setup.cfg").write_text(content)
207+
208+
with change_cwd(uv_init_dir), files_manager():
209+
# Arrange
210+
use_codespell()
211+
212+
# Assert
213+
assert (uv_init_dir / "setup.cfg").read_text() != content
214+
assert (
215+
"[tool.codespell]" not in (uv_init_dir / "pyproject.toml").read_text()
216+
)
217+
218+
def test_setup_cfg_empty(self, uv_init_dir: Path):
219+
# https://github.com/nathanjmcdougall/usethis-python/issues/542
220+
# Basically we want to make sure the "first_content" resolution strategy
221+
# works correctly.
222+
223+
# Arrange
224+
(uv_init_dir / "setup.cfg").touch()
225+
226+
with change_cwd(uv_init_dir), files_manager():
227+
# Arrange
228+
use_codespell()
229+
230+
# Assert
231+
assert (uv_init_dir / "setup.cfg").read_text() == ""
232+
assert "[tool.codespell]" in (uv_init_dir / "pyproject.toml").read_text()
233+
196234
class TestRemove:
197235
@pytest.mark.usefixtures("_vary_network_conn")
198236
def test_config_file(

0 commit comments

Comments
 (0)