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
3 changes: 3 additions & 0 deletions src/usethis/_core/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,10 @@ def use_pre_commit(*, remove: bool = False, how: bool = False) -> None:


def _add_all_tools_pre_commit_configs():
PreCommitTool().add_pre_commit_config()
for _tool in ALL_TOOLS:
if isinstance(_tool, PreCommitTool):
continue
if _tool.is_used():
_tool.add_pre_commit_config()

Expand Down
1 change: 1 addition & 0 deletions src/usethis/_integrations/pre_commit/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)

_HOOK_ORDER = [
"sync-with-uv",
"validate-pyproject",
"uv-export",
"pyproject-fmt",
Expand Down
1 change: 1 addition & 0 deletions src/usethis/_tool/all_.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
)

ALL_TOOLS: list[SupportedToolType] = [
# Alphabetical order
CodespellTool(),
CoveragePyTool(),
DeptryTool(),
Expand Down
23 changes: 23 additions & 0 deletions src/usethis/_tool/impl/pre_commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,40 @@
)
from usethis._integrations.ci.bitbucket.schema import Script as BitbucketScript
from usethis._integrations.ci.bitbucket.schema import Step as BitbucketStep
from usethis._integrations.pre_commit.schema import HookDefinition, UriRepo
from usethis._tool.base import Tool
from usethis._tool.pre_commit import PreCommitConfig
from usethis._types.backend import BackendEnum
from usethis._types.deps import Dependency

_SYNC_WITH_UV_VERSION = "v0.4.0" # Manually bump this version when necessary


class PreCommitTool(Tool):
# https://github.com/pre-commit/pre-commit
@property
def name(self) -> str:
return "pre-commit"

def get_pre_commit_config(self) -> PreCommitConfig:
"""Get the pre-commit configurations for the tool."""
backend = get_backend()

if backend is BackendEnum.uv:
return PreCommitConfig.from_single_repo(
UriRepo(
repo="https://github.com/tsvikas/sync-with-uv",
rev=_SYNC_WITH_UV_VERSION,
hooks=[HookDefinition(id="sync-with-uv")],
),
requires_venv=False,
inform_how_to_use_on_migrate=False,
)
elif backend is BackendEnum.none:
return super().get_pre_commit_config()
else:
assert_never(backend)

def is_used(self) -> bool:
if usethis_config.disable_pre_commit:
return False
Expand Down
4 changes: 3 additions & 1 deletion src/usethis/_tool/impl/pyproject_fmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from usethis._types.backend import BackendEnum
from usethis._types.deps import Dependency

_PYPROJECT_FMT_VERSION = "v2.10.0" # Manually bump this version when necessary


class PyprojectFmtTool(Tool):
# https://github.com/tox-dev/pyproject-fmt
Expand Down Expand Up @@ -84,7 +86,7 @@ def get_pre_commit_config(self) -> PreCommitConfig:
return PreCommitConfig.from_single_repo(
UriRepo(
repo="https://github.com/tox-dev/pyproject-fmt",
rev="v2.10.0", # Manually bump this version when necessary
rev=_PYPROJECT_FMT_VERSION,
hooks=[HookDefinition(id="pyproject-fmt")],
),
requires_venv=False,
Expand Down
90 changes: 80 additions & 10 deletions tests/usethis/_core/test_core_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from usethis._integrations.python.version import get_python_version
from usethis._test import change_cwd
from usethis._tool.all_ import ALL_TOOLS
from usethis._tool.impl.pre_commit import _SYNC_WITH_UV_VERSION
from usethis._tool.impl.ruff import RuffTool
from usethis._types.backend import BackendEnum
from usethis._types.deps import Dependency
Expand Down Expand Up @@ -689,8 +690,12 @@ def test_pre_commit_after(

# 3. Test file contents
assert (uv_init_dir / ".pre-commit-config.yaml").read_text() == (
"""\
f"""\
repos:
- repo: https://github.com/tsvikas/sync-with-uv
rev: {_SYNC_WITH_UV_VERSION}
hooks:
- id: sync-with-uv
- repo: local
hooks:
- id: deptry
Expand All @@ -712,6 +717,7 @@ def test_pre_commit_after(
"✔ Adding dependency 'pre-commit' to the 'dev' group in 'pyproject.toml'.\n"
"☐ Install the dependency 'pre-commit'.\n"
"✔ Writing '.pre-commit-config.yaml'.\n"
"✔ Adding hook 'sync-with-uv' to '.pre-commit-config.yaml'.\n"
"✔ Adding hook 'deptry' to '.pre-commit-config.yaml'.\n"
"☐ Run 'uv run pre-commit install' to register pre-commit.\n"
"☐ Run 'uv run pre-commit run --all-files' to run the hooks manually.\n"
Expand Down Expand Up @@ -850,8 +856,12 @@ def test_pre_commit_first(

# 3. Test file contents
assert (uv_init_repo_dir / ".pre-commit-config.yaml").read_text() == (
"""\
f"""\
repos:
- repo: https://github.com/tsvikas/sync-with-uv
rev: {_SYNC_WITH_UV_VERSION}
hooks:
- id: sync-with-uv
- repo: local
hooks:
- id: deptry
Expand Down Expand Up @@ -1753,12 +1763,48 @@ def test_fresh(self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]):
"✔ Adding dependency 'pre-commit' to the 'dev' group in 'pyproject.toml'.\n"
"☐ Install the dependency 'pre-commit'.\n"
"✔ Writing '.pre-commit-config.yaml'.\n"
"✔ Adding hook 'sync-with-uv' to '.pre-commit-config.yaml'.\n"
"☐ Run 'uv run pre-commit install' to register pre-commit.\n"
"☐ Run 'uv run pre-commit run --all-files' to run the hooks manually.\n"
)
# Config file
assert (uv_init_dir / ".pre-commit-config.yaml").exists()
contents = (uv_init_dir / ".pre-commit-config.yaml").read_text()
assert contents == (
f"""\
repos:
- repo: https://github.com/tsvikas/sync-with-uv
rev: {_SYNC_WITH_UV_VERSION}
hooks:
- id: sync-with-uv
"""
)

@pytest.mark.usefixtures("_vary_network_conn")
def test_fresh_no_backend(
self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]
):
# Act
with (
change_cwd(uv_init_dir),
files_manager(),
usethis_config.set(backend=BackendEnum.none),
):
use_pre_commit()

# Assert
# Correct stdout
out, _ = capfd.readouterr()
assert out == (
"☐ Add the dev dependency 'pre-commit'.\n"
"☐ Install the dependency 'pre-commit'.\n"
"✔ Writing '.pre-commit-config.yaml'.\n"
"✔ Adding placeholder hook to '.pre-commit-config.yaml'.\n"
"☐ Remove the placeholder hook in '.pre-commit-config.yaml'.\n"
"☐ Replace it with your own hooks.\n"
"☐ Alternatively, use 'usethis tool' to add other tools and their hooks.\n"
"☐ Run 'uv run pre-commit install' to register pre-commit.\n"
"☐ Run 'uv run pre-commit run --all-files' to run the hooks manually.\n"
"☐ Run 'pre-commit install' to register pre-commit.\n"
"☐ Run 'pre-commit run --all-files' to run the hooks manually.\n"
)
# Config file
assert (uv_init_dir / ".pre-commit-config.yaml").exists()
Expand Down Expand Up @@ -1797,14 +1843,18 @@ def test_config_file_already_exists(self, uv_init_repo_dir: Path):
# Assert
contents = (uv_init_repo_dir / ".pre-commit-config.yaml").read_text()
assert contents == (
"""\
f"""\
repos:
- repo: local
hooks:
- id: my hook
name: Its mine
entry: uv run --isolated --frozen --offline python -c "print('hello world!')"
language: system
- repo: https://github.com/tsvikas/sync-with-uv
rev: {_SYNC_WITH_UV_VERSION}
hooks:
- id: sync-with-uv
"""
)

Expand Down Expand Up @@ -3020,8 +3070,12 @@ def test_pre_commit(
assert (uv_init_repo_dir / "requirements.txt").exists()
content = (uv_init_repo_dir / ".pre-commit-config.yaml").read_text()
assert content == (
"""\
f"""\
repos:
- repo: https://github.com/tsvikas/sync-with-uv
rev: {_SYNC_WITH_UV_VERSION}
hooks:
- id: sync-with-uv
- repo: local
hooks:
- id: uv-export
Expand Down Expand Up @@ -3536,8 +3590,12 @@ def test_add_only_linter(

# 3. Test file contents
assert (uv_init_repo_dir / ".pre-commit-config.yaml").read_text() == (
"""\
f"""\
repos:
- repo: https://github.com/tsvikas/sync-with-uv
rev: {_SYNC_WITH_UV_VERSION}
hooks:
- id: sync-with-uv
- repo: local
hooks:
- id: ruff
Expand Down Expand Up @@ -3578,8 +3636,12 @@ def test_add_only_linter_to_existing_formatter(

# 3. Test file contents
assert (uv_init_repo_dir / ".pre-commit-config.yaml").read_text() == (
"""\
f"""\
repos:
- repo: https://github.com/tsvikas/sync-with-uv
rev: {_SYNC_WITH_UV_VERSION}
hooks:
- id: sync-with-uv
- repo: local
hooks:
- id: ruff
Expand Down Expand Up @@ -3631,8 +3693,12 @@ def test_add_only_formatter(

# 3. Test file contents
assert (uv_init_repo_dir / ".pre-commit-config.yaml").read_text() == (
"""\
f"""\
repos:
- repo: https://github.com/tsvikas/sync-with-uv
rev: {_SYNC_WITH_UV_VERSION}
hooks:
- id: sync-with-uv
- repo: local
hooks:
- id: ruff-format
Expand Down Expand Up @@ -3673,8 +3739,12 @@ def test_remove_only_linter(

# 3. Test file contents
assert (uv_init_repo_dir / ".pre-commit-config.yaml").read_text() == (
"""\
f"""\
repos:
- repo: https://github.com/tsvikas/sync-with-uv
rev: {_SYNC_WITH_UV_VERSION}
hooks:
- id: sync-with-uv
- repo: local
hooks:
- id: ruff-format
Expand Down
27 changes: 27 additions & 0 deletions tests/usethis/_tool/impl/test_pre_commit.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import os
from pathlib import Path

import pytest

from usethis._config import usethis_config
from usethis._config_file import files_manager
from usethis._integrations.ci.github.errors import GitHubTagError
from usethis._integrations.ci.github.tags import get_github_latest_tag
from usethis._integrations.pre_commit.schema import UriRepo
from usethis._test import change_cwd
from usethis._tool.impl.pre_commit import PreCommitTool

Expand All @@ -28,3 +34,24 @@ def test_empty_dir(self, tmp_path: Path):
# Act, Assert
with change_cwd(tmp_path), files_manager():
assert not PreCommitTool().is_used()

@pytest.mark.usefixtures("_vary_network_conn")
def test_latest_version(self):
if os.getenv("CI"):
pytest.skip(
"Avoid flaky pipelines by testing pyproject-fmt version bumps manually"
)

(config,) = PreCommitTool().get_pre_commit_config().repo_configs
repo = config.repo
assert isinstance(repo, UriRepo)
try:
assert repo.rev == get_github_latest_tag(
owner="tsvikas", repo="sync-with-uv"
)
except GitHubTagError as err:
if usethis_config.offline or "rate limit exceeded for url" in str(err):
pytest.skip(
"Failed to fetch GitHub tags (connection issues); skipping test"
)
raise err
36 changes: 18 additions & 18 deletions tests/usethis/_tool/impl/test_pyproject_fmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,23 @@ def test_uv_only(self, tmp_path: Path, capfd: pytest.CaptureFixture[str]):
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
)

@pytest.mark.usefixtures("_vary_network_conn")
def test_latest_version(self):
if os.getenv("CI"):
pytest.skip(
"Avoid flaky pipelines by testing pyproject-fmt version bumps manually"
)
@pytest.mark.usefixtures("_vary_network_conn")
def test_latest_version(self):
if os.getenv("CI"):
pytest.skip(
"Avoid flaky pipelines by testing pyproject-fmt version bumps manually"
)

(config,) = PyprojectFmtTool().get_pre_commit_config().repo_configs
repo = config.repo
assert isinstance(repo, UriRepo)
try:
assert repo.rev == get_github_latest_tag(
owner="tox-dev", repo="pyproject-fmt"
(config,) = PyprojectFmtTool().get_pre_commit_config().repo_configs
repo = config.repo
assert isinstance(repo, UriRepo)
try:
assert repo.rev == get_github_latest_tag(
owner="tox-dev", repo="pyproject-fmt"
)
except GitHubTagError as err:
if usethis_config.offline or "rate limit exceeded for url" in str(err):
pytest.skip(
"Failed to fetch GitHub tags (connection issues); skipping test"
)
except GitHubTagError as err:
if usethis_config.offline or "rate limit exceeded for url" in str(err):
pytest.skip(
"Failed to fetch GitHub tags (connection issues); skipping test"
)
raise err
raise err
1 change: 1 addition & 0 deletions tests/usethis/_ui/interface/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def test_pre_commit_included(self, tmp_path: Path):
with change_cwd(tmp_path):
hook_ids = get_hook_ids()
assert hook_ids == [
"sync-with-uv",
"pyproject-fmt",
"ruff",
"ruff-format",
Expand Down