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
2 changes: 1 addition & 1 deletion .importlinter
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ type = layers
containers =
usethis._ui.interface
layers =
arch | author | badge | browse | ci | doc | docstyle | format_ | init | lint | list | readme | rule | show | spellcheck | status | test | tool | typecheck | version
arch | author | badge | browse | ci | doc | docstyle | format_ | hook | init | lint | list | readme | rule | show | spellcheck | status | test | tool | typecheck | version
exhaustive = true

[importlinter:contract:pipeweld]
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Additionally, the command line reference documentation can be viewed with `useth
- [`usethis arch`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-arch) — Add/Configure recommended architecture analysis tools (namely, [Import Linter](https://import-linter.readthedocs.io/en/stable/)).
- [`usethis doc`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-doc) — Add/Configure recommended documentation tools (namely, [MkDocs](https://www.mkdocs.org/)).
- [`usethis format`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-format) — Add/Configure recommended formatters (namely, [Ruff](https://docs.astral.sh/ruff/formatter/) and [pyproject-fmt](https://pyproject-fmt.readthedocs.io/en/latest/)).
- [`usethis hook`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-hook) — Add/Configure a recommended git hook framework (namely, [pre-commit](https://github.com/pre-commit/pre-commit)).
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README links to .../cli/reference#usethis-hook, but the in-repo CLI reference doc (docs/cli/reference.md) currently has no usethis hook section/anchor and still documents --pre-commit/--no-pre-commit for usethis init. Until the reference is updated, this README link is likely to 404/mislead.

Suggested change
- [`usethis hook`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-hook) — Add/Configure a recommended git hook framework (namely, [pre-commit](https://github.com/pre-commit/pre-commit)).
- [`usethis hook`](https://usethis.readthedocs.io/en/stable/cli/reference) — Add/Configure a recommended git hook framework (namely, [pre-commit](https://github.com/pre-commit/pre-commit)).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot likewise, please update reference.md to ensure this hyperlink is valid

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated docs/cli/reference.md in ebd433b — added a new ## usethis hook section (with anchor #usethis-hook) and renamed --pre-commit to --hook under usethis init.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ebd433breference.md now has the #usethis-hook anchor.

- [`usethis lint`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-lint) — Add/Configure recommended linters (namely, [Ruff](https://docs.astral.sh/ruff/linter) and [deptry](https://github.com/fpgmaas/deptry)).
- [`usethis spellcheck`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-spellcheck) — Add/Configure recommended spellcheckers (namely, [codespell](https://github.com/codespell-project/codespell)).
- [`usethis test`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-test) — Add/Configure a recommended testing framework (namely, [pytest](https://github.com/pytest-dev/pytest) with [Coverage.py](https://github.com/nedbat/coveragepy)).
Expand Down
1 change: 1 addition & 0 deletions docs/cli/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [`usethis arch`](reference.md#usethis-arch) — Add/Configure recommended architecture analysis tools (namely, [Import Linter](https://import-linter.readthedocs.io/en/stable/)).
- [`usethis doc`](reference.md#usethis-doc) — Add/Configure recommended documentation tools (namely, [MkDocs](https://www.mkdocs.org/)).
- [`usethis format`](reference.md#usethis-format) — Add/Configure recommended formatters (namely, [Ruff](https://docs.astral.sh/ruff/formatter/) and [pyproject-fmt](https://pyproject-fmt.readthedocs.io/en/latest/)).
- [`usethis hook`](reference.md#usethis-hook) — Add/Configure a recommended git hook framework (namely, [pre-commit](https://github.com/pre-commit/pre-commit)).
- [`usethis lint`](reference.md#usethis-lint) — Add/Configure recommended linters (namely, [Ruff](https://docs.astral.sh/ruff/linter) and [deptry](https://github.com/fpgmaas/deptry)).
- [`usethis spellcheck`](reference.md#usethis-spellcheck) — Add/Configure recommended spellcheckers (namely, [codespell](https://github.com/codespell-project/codespell)).
- [`usethis test`](reference.md#usethis-test) — Add/Configure a recommended testing framework (namely, [pytest](https://github.com/pytest-dev/pytest) with [Coverage.py](https://github.com/nedbat/coveragepy)).
Expand Down
30 changes: 28 additions & 2 deletions docs/cli/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Initialize a new Python project with recommended defaults, including:
- a `pyproject.toml` file and relevant configuration,
- recommended linters, formatters, spellcheckers, type checkers, and test frameworks (all opt-out),
- docstring style enforcement (opt-in),
- the pre-commit framework (opt-in),
- a recommended git hook framework (opt-in),
- CI services (opt-in),
- declared & installed dependencies via `uv add`, and
- any other relevant directories or tool-bespoke configuration files.
Expand All @@ -21,7 +21,7 @@ Supported options:
- `--spellcheck` to add a recommended spellchecker (default; or `--no-spellcheck` to opt-out)
- `--test` to add a recommended testing framework (default; or `--no-test` to opt-out)
- `--typecheck` to add a recommended type checker (default; or `--no-typecheck` to opt-out)
- `--pre-commit` to add the pre-commit framework for git hooks (but the default is `--no-pre-commit`)
- `--hook` to add a recommended git hook framework (but the default is `--no-hook`)
- `--ci` to add a CI service configuration

Possible values:
Expand Down Expand Up @@ -124,6 +124,32 @@ Supported options:

See [`usethis tool`](#usethis-tool) for more information.

## `usethis hook`

Add a recommended git hook framework to the project (namely, [pre-commit](https://github.com/pre-commit/pre-commit)), including:

- declared & installed dependencies via `uv add`,
- relevant configuration, and
- any other relevant directories or tool-bespoke configuration files.

Note if `pyproject.toml` is not present, it will be created, since this is required for declaring dependencies via `uv add`.

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
- `--offline` to disable network access and rely on caches
- `--frozen` to leave the virtual environment and lockfile unchanged
- `--quiet` to suppress output
- `--backend` to specify a package manager backend to use. The default is to auto-detect.

Possible values:
- `auto` to auto-detect the backend (default)
- `uv` to use the [uv](https://docs.astral.sh/uv) package manager
- `none` to not use a package manager backend and display messages for some operations.

See [`usethis tool`](#usethis-tool) for more information.

## `usethis lint`

Add recommended linters to the project (namely, [Ruff](https://docs.astral.sh/ruff/linter) and [deptry](https://github.com/fpgmaas/deptry)), including:
Expand Down
5 changes: 5 additions & 0 deletions src/usethis/_toolset/hook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from usethis._core.tool import use_pre_commit


def use_hook_framework(remove: bool = False, how: bool = False):
use_pre_commit(remove=remove, how=how)
7 changes: 7 additions & 0 deletions src/usethis/_ui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import usethis._ui.interface.doc
import usethis._ui.interface.docstyle
import usethis._ui.interface.format_
import usethis._ui.interface.hook
import usethis._ui.interface.init
import usethis._ui.interface.lint
import usethis._ui.interface.list
Expand Down Expand Up @@ -60,6 +61,12 @@
)(
usethis._ui.interface.format_.format_,
)
app.command(
help="Add or configure a recommended git hook framework.",
rich_help_panel=rich_help_panel,
)(
usethis._ui.interface.hook.hook,
)
app.command(
help="Add or configure recommended linters.", rich_help_panel=rich_help_panel
)(
Expand Down
41 changes: 41 additions & 0 deletions src/usethis/_ui/interface/hook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import typer

from usethis._config import usethis_config
from usethis._types.backend import BackendEnum
from usethis._ui.options import (
backend_opt,
frozen_opt,
how_opt,
offline_opt,
quiet_opt,
remove_opt,
)


def hook(
remove: bool = remove_opt,
how: bool = how_opt,
offline: bool = offline_opt,
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
) -> None:
"""Add a recommended git hook framework to the project."""
from usethis._config_file import files_manager
from usethis._console import err_print
from usethis._toolset.hook import use_hook_framework
from usethis.errors import UsethisError

assert isinstance(backend, BackendEnum)

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
),
files_manager(),
):
try:
use_hook_framework(remove=remove, how=how)
except UsethisError as err:
err_print(err)
raise typer.Exit(code=1) from None
20 changes: 10 additions & 10 deletions src/usethis/_ui/interface/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ def init(
"--typecheck/--no-typecheck",
help="Add a recommended type checker.",
),
pre_commit: bool = typer.Option(
hook: bool = typer.Option(
False,
"--pre-commit/--no-pre-commit",
help="Add the pre-commit framework for git hooks.",
"--hook/--no-hook",
help="Add a recommended git hook framework.",
),
ci: CIServiceEnum | None = typer.Option(
None,
Expand Down Expand Up @@ -103,7 +103,7 @@ def init(
spellcheck=spellcheck,
test=test,
typecheck=typecheck,
pre_commit=pre_commit,
hook=hook,
ci=ci,
docstyle=docstyle,
status=status,
Expand All @@ -122,7 +122,7 @@ def _init( # noqa: PLR0915
spellcheck: bool,
test: bool,
typecheck: bool,
pre_commit: bool,
hook: bool,
ci: CIServiceEnum | None,
docstyle: DocStyleEnum | None,
status: DevelopmentStatusEnum,
Expand All @@ -132,11 +132,11 @@ def _init( # noqa: PLR0915
from usethis._core.docstyle import use_docstyle
from usethis._core.readme import add_readme
from usethis._core.status import use_development_status
from usethis._core.tool import use_pre_commit
from usethis._init import project_init
from usethis._toolset.arch import use_arch_tools
from usethis._toolset.doc import use_doc_frameworks
from usethis._toolset.format_ import use_formatters
from usethis._toolset.hook import use_hook_framework
from usethis._toolset.lint import use_linters
from usethis._toolset.spellcheck import use_spellcheckers
from usethis._toolset.test import use_test_frameworks
Expand All @@ -148,11 +148,11 @@ def _init( # noqa: PLR0915
assert isinstance(status, DevelopmentStatusEnum)
use_development_status(status)

if pre_commit:
tick_print("Adding the pre-commit framework.")
if hook:
tick_print("Adding a recommended git hook framework.")
with usethis_config.set(instruct_only=True):
use_pre_commit()
use_pre_commit(how=True)
use_hook_framework()
use_hook_framework(how=True)

if doc:
tick_print("Adding recommended documentation tools.")
Expand Down
57 changes: 57 additions & 0 deletions tests/usethis/_ui/interface/test_hook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from pathlib import Path

from usethis._config_file import files_manager
from usethis._deps import get_deps_from_group
from usethis._test import CliRunner, change_cwd
from usethis._types.deps import Dependency
from usethis._ui.app import app


class TestHook:
def test_dependencies_added(self, tmp_path: Path):
# Act
runner = CliRunner()
with change_cwd(tmp_path):
result = runner.invoke_safe(app, ["hook"])

# Assert
assert result.exit_code == 0, result.output
with change_cwd(tmp_path), files_manager():
assert Dependency(name="pre-commit") in get_deps_from_group("dev")

def test_how_option(self, tmp_path: Path):
# Act
runner = CliRunner()
with change_cwd(tmp_path):
result = runner.invoke_safe(app, ["hook", "--how"])

# Assert
assert result.exit_code == 0, result.output
assert "pre-commit" in result.output

def test_add_then_remove(self, tmp_path: Path):
# Arrange
runner = CliRunner()

with change_cwd(tmp_path):
# Act: Add hook framework
result = runner.invoke_safe(app, ["hook"])
assert result.exit_code == 0, result.output

# Act: Remove hook framework
result = runner.invoke_safe(app, ["hook", "--remove"])

# Assert
assert result.exit_code == 0, result.output
with change_cwd(tmp_path), files_manager():
assert Dependency(name="pre-commit") not in get_deps_from_group("dev")

def test_none_backend(self, tmp_path: Path):
# Act
runner = CliRunner()
with change_cwd(tmp_path):
result = runner.invoke_safe(app, ["hook", "--backend", "none"])

# Assert
assert result.exit_code == 0, result.output
assert "pre-commit" in result.output
12 changes: 6 additions & 6 deletions tests/usethis/_ui/interface/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@

class TestInit:
@pytest.mark.usefixtures("_vary_network_conn")
def test_pre_commit_included(self, tmp_path: Path):
def test_hook_included(self, tmp_path: Path):
# Act
runner = CliRunner()
with change_cwd(tmp_path):
if not usethis_config.offline:
result = runner.invoke_safe(app, ["init", "--pre-commit"])
result = runner.invoke_safe(app, ["init", "--hook"])
else:
result = runner.invoke_safe(app, ["init", "--pre-commit", "--offline"])
result = runner.invoke_safe(app, ["init", "--hook", "--offline"])

# Assert
assert result.exit_code == 0, result.output
Expand All @@ -29,7 +29,7 @@ def test_pre_commit_included(self, tmp_path: Path):
"✔ Writing 'README.md'.\n"
"☐ Populate 'README.md' to help users understand the project.\n"
"✔ Setting the development status to '1 - Planning'.\n"
"✔ Adding the pre-commit framework.\n"
"✔ Adding a recommended git hook framework.\n"
"☐ Run 'uv run pre-commit run -a' to run the hooks manually.\n"
"✔ Adding recommended documentation tools.\n"
"☐ Run 'uv run mkdocs build' to build the documentation.\n"
Expand Down Expand Up @@ -100,7 +100,7 @@ def test_bitbucket_docstyle_and_status(self, tmp_path: Path):
"bitbucket",
"--docstyle",
"numpy",
"--pre-commit",
"--hook",
"--status",
"production",
],
Expand All @@ -114,7 +114,7 @@ def test_bitbucket_docstyle_and_status(self, tmp_path: Path):
"✔ Writing 'README.md'.\n"
"☐ Populate 'README.md' to help users understand the project.\n"
"✔ Setting the development status to '5 - Production/Stable'.\n"
"✔ Adding the pre-commit framework.\n"
"✔ Adding a recommended git hook framework.\n"
"☐ Run 'uv run pre-commit run -a' to run the hooks manually.\n"
"✔ Adding recommended documentation tools.\n"
"☐ Run 'uv run mkdocs build' to build the documentation.\n"
Expand Down
Loading