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
1 change: 1 addition & 0 deletions docs/cli/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ Supported options:

- `--remove` to remove the tool instead of adding it
- `--how` to only print how to use the tool, with no other side effects
- `--no-hook` to skip adding or modifying git hook configuration (e.g. pre-commit)
- `--offline` to disable network access and rely on caches
- `--frozen` to leave the virtual environment and lockfile unchanged
- `--quiet` to suppress output
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ dev = [
"deptry>=0.23.0",
"import-linter>=2.11",
"jinja2>=3.1.6",
"prek>=0.2.23",
"prek>=0.3.8",
"pyinstrument>=5.1.1",
"pyproject-fmt>=2.11.1",
"ruff>=0.14.3",
Expand Down
6 changes: 5 additions & 1 deletion src/usethis/_tool/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,12 @@ def add_pre_commit_config(self) -> None:
def remove_pre_commit_repo_configs(self) -> None:
"""Remove the tool's pre-commit configuration.

If the .pre-commit-config.yaml file does not exist, this method has no effect.
If pre-commit is disabled or if the .pre-commit-config.yaml file does not
exist, this method has no effect.
"""
if usethis_config.disable_pre_commit:
return

repo_configs = self.get_pre_commit_repos()

if not repo_configs:
Expand Down
85 changes: 73 additions & 12 deletions src/usethis/_ui/interface/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
frozen_opt,
how_opt,
linter_opt,
no_hook_opt,
offline_opt,
quiet_opt,
remove_opt,
Expand All @@ -35,13 +36,18 @@ def codespell(
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_codespell

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand All @@ -67,13 +73,18 @@ def coverage_py(
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_coverage_py

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand All @@ -92,13 +103,18 @@ def deptry(
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_deptry

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand All @@ -117,13 +133,18 @@ def import_linter(
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_import_linter

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand All @@ -142,13 +163,18 @@ def mkdocs(
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_mkdocs

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand All @@ -167,13 +193,18 @@ def pre_commit(
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_pre_commit

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand All @@ -192,13 +223,18 @@ def pyproject_fmt(
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_pyproject_fmt

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand All @@ -217,13 +253,18 @@ def pyproject_toml(
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_pyproject_toml

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand All @@ -240,13 +281,18 @@ def pytest(
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_pytest

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand All @@ -265,13 +311,18 @@ def requirements_txt(
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_requirements_txt

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand All @@ -292,13 +343,18 @@ def ruff(
backend: BackendEnum = backend_opt,
linter: bool = linter_opt,
formatter: bool = formatter_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_ruff

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand All @@ -317,13 +373,18 @@ def ty(
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
no_hook: bool = no_hook_opt,
) -> None:
from usethis._config_file import files_manager
from usethis._core.tool import use_ty

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
offline=offline,
quiet=quiet,
frozen=frozen,
backend=backend,
disable_pre_commit=no_hook,
),
files_manager(),
):
Expand Down
5 changes: 5 additions & 0 deletions src/usethis/_ui/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
backend_opt = typer.Option(
BACKEND_DEFAULT, "--backend", help="Package manager backend to use."
)
no_hook_opt = typer.Option(
False,
"--no-hook",
help="Don't add or modify git hook configuration, e.g. pre-commit",
)

# author command options
author_name_opt = typer.Option(..., "--name", help="Author name")
Expand Down
67 changes: 67 additions & 0 deletions tests/usethis/_ui/interface/test_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from usethis._config import usethis_config
from usethis._config_file import files_manager
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._integrations.pre_commit.hooks import get_hook_ids
from usethis._subprocess import SubprocessFailedError, call_subprocess
from usethis._test import CliRunner, change_cwd
from usethis._tool.all_ import ALL_TOOLS
Expand Down Expand Up @@ -42,6 +43,49 @@ def test_how(self, tmp_path: Path):
"""
)

def test_no_hook_skips_pre_commit(self, uv_init_dir: Path):
"""Test that --no-hook skips adding hooks when adding codespell."""
runner = CliRunner()
with change_cwd(uv_init_dir):
# Arrange: set up pre-commit first
result = runner.invoke_safe(app, ["pre-commit", "--frozen"])
assert result.exit_code == 0, result.output

# Act: add codespell with --no-hook
result = runner.invoke_safe(app, ["codespell", "--frozen", "--no-hook"])
assert result.exit_code == 0, result.output

# Assert: codespell hook should NOT be added to pre-commit config
with files_manager():
hook_ids = get_hook_ids()
assert "codespell" not in hook_ids

def test_no_hook_remove_preserves_hook(self, uv_init_dir: Path):
"""Test that --no-hook with --remove does not remove hooks."""
runner = CliRunner()
with change_cwd(uv_init_dir):
# Arrange: add pre-commit and codespell (with hooks)
result = runner.invoke_safe(app, ["pre-commit", "--frozen"])
assert result.exit_code == 0, result.output
result = runner.invoke_safe(app, ["codespell", "--frozen"])
assert result.exit_code == 0, result.output

# Verify codespell hook was added
with files_manager():
hook_ids = get_hook_ids()
assert "codespell" in hook_ids

# Act: remove codespell with --no-hook
result = runner.invoke_safe(
app, ["codespell", "--remove", "--frozen", "--no-hook"]
)
assert result.exit_code == 0, result.output

# Assert: codespell hook should still be in pre-commit config
with files_manager():
hook_ids = get_hook_ids()
assert "codespell" in hook_ids


class TestCoverage:
@pytest.mark.usefixtures("_vary_network_conn")
Expand Down Expand Up @@ -505,6 +549,29 @@ def test_passes_using_all_tools(self, uv_init_dir: Path):
# Act, Assert
call_uv_subprocess(["run", "ruff", "check", "."], change_toml=False)

def test_no_hook_skips_pre_commit(self, uv_init_dir: Path):
"""Test that --no-hook works for ruff too."""
runner = CliRunner()
with change_cwd(uv_init_dir):
# Arrange: set up pre-commit first
result = runner.invoke_safe(app, ["pre-commit", "--frozen"])
assert result.exit_code == 0, result.output

# Act: add ruff with --no-hook
result = runner.invoke_safe(app, ["ruff", "--frozen", "--no-hook"])
assert result.exit_code == 0, result.output

# Assert: ruff was configured but hooks were NOT added
with files_manager():
assert (
(uv_init_dir / "pyproject.toml")
.read_text()
.__contains__("[tool.ruff")
)
hook_ids = get_hook_ids()
assert "ruff-check" not in hook_ids
assert "ruff-format" not in hook_ids


class TestPytest:
@pytest.mark.usefixtures("_vary_network_conn")
Expand Down
Loading
Loading