Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/usethis/_core/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ def use_codespell(*, remove: bool = False, how: bool = False) -> None:
if not remove:
ensure_dep_declaration_file()

tool.add_dev_deps()
if not PreCommitTool().is_used():
tool.add_dev_deps()
tool.update_bitbucket_steps()
else:
tool.add_pre_commit_config()
Expand Down Expand Up @@ -272,8 +272,8 @@ def use_pyproject_fmt(*, remove: bool = False, how: bool = False) -> None:
if not remove:
ensure_dep_declaration_file()

tool.add_dev_deps()
if not PreCommitTool().is_used():
tool.add_dev_deps()
tool.update_bitbucket_steps()
else:
tool.add_pre_commit_config()
Expand Down
13 changes: 2 additions & 11 deletions src/usethis/_tool/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,7 @@ def migrate_config_to_pre_commit(self) -> None:
"""Migrate the tool's configuration to pre-commit."""
if self.is_used():
pre_commit_config = self.get_pre_commit_config()
# For tools that don't require a venv for their pre-commits, we will
# remove the dependency as an explicit dependency, and make it
# pre-commit only.
if not pre_commit_config.any_require_venv:
self.remove_dev_deps()

# N.B. don't need to modify dev deps
# We're migrating, so sometimes we might need to inform the user about the
# new way to do things.
if pre_commit_config.inform_how_to_use_on_migrate:
Expand All @@ -307,11 +302,7 @@ def migrate_config_from_pre_commit(self) -> None:
"""Migrate the tool's configuration from pre-commit."""
if self.is_used():
pre_commit_config = self.get_pre_commit_config()
# For tools that don't require a venv for their pre-commits, we will
# need to add the dependency explicitly.
if not pre_commit_config.any_require_venv:
self.add_dev_deps()

# N.B. don't need to modify dev deps
# We're migrating, so sometimes we might need to inform the user about
# the new way to do things.
if pre_commit_config.inform_how_to_use_on_migrate:
Expand Down
127 changes: 112 additions & 15 deletions tests/usethis/_core/test_core_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ def test_pre_commit_integration(
use_codespell()

# Assert
# Check dependencies - shouldn't have installed codespell
# Check dependencies - should have installed codespell (issue #1020)
dev_deps = get_deps_from_group("dev")
assert all(dep.name != "codespell" for dep in dev_deps)
assert any(dep.name == "codespell" for dep in dev_deps)

# Check hook names
hook_names = get_hook_ids()
Expand All @@ -162,10 +162,14 @@ def test_pre_commit_integration(
# Check output
out, err = capfd.readouterr()
assert not err
# Note: Since deps are now added (issue #1020), the message is "uv run codespell"
# not "pre-commit run codespell" because get_install_method() returns "devdep"
assert out.replace("\n", "") == (
"✔ Adding dependency 'codespell' to the 'dev' group in 'pyproject.toml'."
"☐ Install the dependency 'codespell'."
"✔ Adding hook 'codespell' to '.pre-commit-config.yaml'."
"✔ Adding Codespell config to 'pyproject.toml'."
"☐ Run 'uv run pre-commit run codespell --all-files' to run the Codespell spellchecker."
"☐ Run 'uv run codespell' to run the Codespell spellchecker."
)

@pytest.mark.usefixtures("_vary_network_conn")
Expand Down Expand Up @@ -854,6 +858,10 @@ def test_pre_commit_first(
use_deptry()

# Assert
# Check dependencies - SHOULD have installed deptry (issue #1020)
dev_deps = get_deps_from_group("dev")
assert any(dep.name == "deptry" for dep in dev_deps)

hook_names = get_hook_ids()

# 1. File exists
Expand Down Expand Up @@ -1675,10 +1683,15 @@ def test_config(
with change_cwd(uv_init_repo_dir), files_manager():
use_import_linter()

# Assert
# Assert
# Check dependencies - should have installed import-linter (issue #1020)
dev_deps = get_deps_from_group("dev")
assert any(dep.name == "import-linter" for dep in dev_deps)

contents = (uv_init_repo_dir / ".pre-commit-config.yaml").read_text()
assert "import-linter" in contents
assert "placeholder" not in contents

out, err = capfd.readouterr()
assert not err
assert out == (
Expand Down Expand Up @@ -1977,9 +1990,9 @@ def test_pyproject_fmt_used(self, uv_init_repo_dir: Path):
hook_names = get_hook_ids()
assert "pyproject-fmt" in hook_names

# Issue #1020: Deps should remain even when using pre-commit
dev_deps = get_deps_from_group("dev")
for dev_dep in dev_deps:
assert dev_dep.name != "pyproject-fmt"
assert any(dep.name == "pyproject-fmt" for dep in dev_deps)

@pytest.mark.usefixtures("_vary_network_conn")
def test_codespell_used(self, uv_init_repo_dir: Path):
Expand All @@ -1994,9 +2007,9 @@ def test_codespell_used(self, uv_init_repo_dir: Path):
hook_names = get_hook_ids()
assert "codespell" in hook_names

# Issue #1020: Deps should remain even when using pre-commit
dev_deps = get_deps_from_group("dev")
for dep in dev_deps:
assert dep.name != "codespell"
assert any(dep.name == "codespell" for dep in dev_deps)

class TestRemove:
@pytest.mark.usefixtures("_vary_network_conn")
Expand Down Expand Up @@ -2091,17 +2104,18 @@ def test_pyproject_fmt_used(

# Assert
out, _ = capfd.readouterr()
# Note: Since deps are now added when tool is used with pre-commit (issue #1020),
# the dependency is already present and doesn't need to be re-added when
# removing pre-commit.
assert out == (
"☐ Run 'uv run --with pre-commit pre-commit uninstall' to deregister pre-commit.\n"
"✔ Removing '.pre-commit-config.yaml'.\n"
"✔ Removing dependency 'pre-commit' from the 'dev' group in 'pyproject.toml'.\n"
"✔ Adding dependency 'pyproject-fmt' to the 'dev' group in 'pyproject.toml'.\n"
"☐ Install the dependency 'pyproject-fmt'.\n"
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
)

@pytest.mark.usefixtures("_vary_network_conn")
def test_codepsell_used(
def test_codespell_used(
self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]
):
with change_cwd(uv_init_dir), files_manager():
Expand All @@ -2117,12 +2131,13 @@ def test_codepsell_used(
# Assert
out, err = capfd.readouterr()
assert not err
# Note: Since deps are now added when tool is used with pre-commit (issue #1020),
# the dependency is already present and doesn't need to be re-added when
# removing pre-commit.
assert out == (
"☐ Run 'uv run --with pre-commit pre-commit uninstall' to deregister pre-commit.\n"
"✔ Removing '.pre-commit-config.yaml'.\n"
"✔ Removing dependency 'pre-commit' from the 'dev' group in 'pyproject.toml'.\n"
"✔ Adding dependency 'codespell' to the 'dev' group in 'pyproject.toml'.\n"
"☐ Install the dependency 'codespell'.\n"
"☐ Run 'uv run codespell' to run the Codespell spellchecker.\n"
)

Expand Down Expand Up @@ -2267,6 +2282,53 @@ def test_hooks_run_all_tools_empty_repo(self, uv_env_dir: Path):
change_toml=False,
)

class TestMigration:
"""Test migration of tools to/from pre-commit (issue #1020)."""

@pytest.mark.usefixtures("_vary_network_conn")
def test_deps_not_removed_when_migrating_to_pre_commit(self, uv_init_dir: Path):
"""Test that deps are NOT removed when migrating to pre-commit."""
with change_cwd(uv_init_dir), files_manager():
# Arrange - Add a tool first
use_codespell()

# Verify dep is present before migration
dev_deps_before = get_deps_from_group("dev")
assert any(dep.name == "codespell" for dep in dev_deps_before)

# Act - Add pre-commit (which triggers migration)
use_pre_commit()

# Assert - Dep should STILL be present after migration (issue #1020)
dev_deps_after = get_deps_from_group("dev")
assert any(dep.name == "codespell" for dep in dev_deps_after)

@pytest.mark.usefixtures("_vary_network_conn")
def test_deps_not_added_when_migrating_from_pre_commit(self, uv_init_dir: Path):
"""Test that deps are NOT re-added when migrating from pre-commit."""
with change_cwd(uv_init_dir), files_manager():
# Arrange - Add pre-commit first, then a tool
use_pre_commit()
use_codespell()

# Verify dep is present before removal
dev_deps_before = get_deps_from_group("dev")
codespell_deps_before = [
dep for dep in dev_deps_before if dep.name == "codespell"
]
assert len(codespell_deps_before) == 1

# Act - Remove pre-commit (which triggers migration)
use_pre_commit(remove=True)

# Assert - Dep should still be present exactly once (not duplicated)
dev_deps_after = get_deps_from_group("dev")
codespell_deps_after = [
dep for dep in dev_deps_after if dep.name == "codespell"
]
assert len(codespell_deps_after) == 1
assert codespell_deps_after == codespell_deps_before


class TestPyprojectFmt:
class TestAdd:
Expand Down Expand Up @@ -2347,6 +2409,39 @@ def test_bitbucket_integration(
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
)

@pytest.mark.usefixtures("_vary_network_conn")
def test_pre_commit_integration(
self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]
):
# Issue #1020: deps should be added even when pre-commit is used
with change_cwd(uv_init_dir), files_manager():
# Arrange
use_pre_commit()
capfd.readouterr()

# Act
use_pyproject_fmt()

# Assert
# Check dependencies - should have installed pyproject-fmt (issue #1020)
dev_deps = get_deps_from_group("dev")
assert any(dep.name == "pyproject-fmt" for dep in dev_deps)

# Check hook names
hook_names = get_hook_ids()
assert "pyproject-fmt" in hook_names

# Check output
out, err = capfd.readouterr()
assert not err
assert out.replace("\n", "") == (
"✔ Adding dependency 'pyproject-fmt' to the 'dev' group in 'pyproject.toml'."
"☐ Install the dependency 'pyproject-fmt'."
"✔ Adding hook 'pyproject-fmt' to '.pre-commit-config.yaml'."
"✔ Adding pyproject-fmt config to 'pyproject.toml'."
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt."
)

class TestRemove:
@pytest.mark.usefixtures("_vary_network_conn")
def test_config_file(self, uv_init_dir: Path):
Expand Down Expand Up @@ -2438,12 +2533,12 @@ def test_remove_with_precommit(
assert "pyproject-fmt" not in contents
out, err = capfd.readouterr()
assert not err
# Issue #1020: Deps are now present even with pre-commit, so they're removed
assert out == (
"✔ Removing pyproject-fmt config from 'pyproject.toml'.\n"
"✔ Removing hook 'pyproject-fmt' from '.pre-commit-config.yaml'.\n"
"✔ Removing dependency 'pyproject-fmt' from the 'dev' group in 'pyproject.toml'.\n"
)
# N.B. we don't remove it as a dependency because it's not a dep when
# pre-commit is used.

@pytest.mark.usefixtures("_vary_network_conn")
def test_remove_without_precommit(
Expand Down Expand Up @@ -3648,10 +3743,12 @@ def test_remove(
assert "ruff" not in contents
out, err = capfd.readouterr()
assert not err
# Issue #1020: Deps are now present even with pre-commit, so they're removed
assert out == (
"✔ Removing hook 'ruff-check' from '.pre-commit-config.yaml'.\n"
"✔ Removing hook 'ruff-format' from '.pre-commit-config.yaml'.\n"
"✔ Removing Ruff config from 'pyproject.toml'.\n"
"✔ Removing dependency 'ruff' from the 'dev' group in 'pyproject.toml'.\n"
)

@pytest.mark.usefixtures("_vary_network_conn")
Expand Down
10 changes: 4 additions & 6 deletions tests/usethis/_ui/interface/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@ def test_pre_commit_included(self, tmp_path: Path):
"☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.\n"
"✔ Adding recommended formatters.\n"
"☐ Run 'uv run ruff format' to run the Ruff formatter.\n"
"☐ Run 'uv run pre-commit run pyproject-fmt --all-files' to run pyproject-fmt.\n"
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
"✔ Adding recommended spellcheckers.\n"
"☐ Run 'uv run pre-commit run codespell --all-files' to run the Codespell \n"
"spellchecker.\n"
"☐ Run 'uv run codespell' to run the Codespell spellchecker.\n"
"✔ Adding recommended test frameworks.\n"
"☐ Add test files to the '/tests' directory with the format 'test_*.py'.\n"
"☐ Add test functions with the format 'test_*()'.\n"
Expand Down Expand Up @@ -121,11 +120,10 @@ def test_bitbucket_docstyle_and_status(self, tmp_path: Path):
"☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.\n"
"✔ Adding recommended formatters.\n"
"☐ Run 'uv run ruff format' to run the Ruff formatter.\n"
"☐ Run 'uv run pre-commit run pyproject-fmt --all-files' to run pyproject-fmt.\n"
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
"✔ Setting docstring style to numpy.\n"
"✔ Adding recommended spellcheckers.\n"
"☐ Run 'uv run pre-commit run codespell --all-files' to run the Codespell \n"
"spellchecker.\n"
"☐ Run 'uv run codespell' to run the Codespell spellchecker.\n"
"✔ Adding recommended test frameworks.\n"
"☐ Add test files to the '/tests' directory with the format 'test_*.py'.\n"
"☐ Add test functions with the format 'test_*()'.\n"
Expand Down