Over the course of this series I've rebuilt my Python stack with uv (Part 1), ruff (Part 2) and ty (Part 3). In Part 4 I bring it all together: the checks should run automatically, before code even reaches the repository. Nobody feels like running this stack of tools by hand all the time.
Git hooks as a quality gate
Every check that only fails in CI is a delay and pulls me out of my tasks: write, commit, push, wait five minutes, red pipeline, fix, "Fix linting" commit, push again. A pre-commit hook moves exactly those same checks to the earliest possible point: errors get fixed before they land in the history, and CI turns from the thing that finds errors into a pure safety net.
The bottleneck with classic pre-commit
The pre-commit framework has been a standard for this for years. Tools like lefthook or husky are great too. But prek is fast and, thanks to its compatibility with pre-commit, my tool of choice. Because pre-commit has a few downsides: it's a Python package itself, isolates every hook in its own virtual environment, and runs hooks sequentially. The first git commit after a fresh clone can take minutes while environments are built.
prek
prek is a reimplementation of the pre-commit framework in Rust that reads the same .pre-commit-config.yaml, just with smarter caching and without the virtualenv overhead:
# Installation (a single static binary, no Python needed)
uv tool install prek
# alternatively via brew/pacman/apt/etc.
# Enable the git hook in the repository (once per clone)
prek install
# Run hooks manually
prek run # against staged files
prek run --all-files # against the whole codebase
prek run gitleaks --all-files # a single hook specificallyMy hook setup in detail
The .pre-commit-config.yaml of my template chains together everything I built in the last three parts. I split the hooks into two kinds:
Remote hooks come from foreign repositories: the basics like YAML/TOML validation, trailing whitespace, and the secret scanner gitleaks:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-yaml
- id: check-toml
- id: check-merge-conflict
- id: trailing-whitespace
- id: end-of-file-fixer
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaksLocal hooks, on the other hand, run via uv run in my project environment. That way ruff, ty and bandit check with exactly the versions pinned in uv.lock:
- repo: local
hooks:
- id: ruff-format
name: ruff format
entry: uv run ruff format
language: system
types: [python]
- id: ruff-check
name: ruff check
entry: uv run ruff check --fix
language: system
types: [python]
- id: ty
name: ty-check
entry: uv run ty check --verbose --output-format=full .
language: system
types: [python]
pass_filenames: false
- id: bandit
name: bandit
entry: uv run bandit -r src/app -c pyproject.toml
language: system
types: [python]
pass_filenames: falseOn top of that there's import-linter, which guards my architecture: it fails as soon as someone imports infrastructure from the domain layer. That turns the architecture rules into an executable check.
One commit, the whole quality gate
With that, every git commit automatically runs: formatting, linting with auto-fix, type checking, security scan (bandit), secret scan (gitleaks) and the architecture check. Thanks to prek largely in parallel and within a few seconds, fast enough that nobody is tempted to switch it off.
For emergencies there's still the back door:
git commit --no-verifyBut that's exactly what CI is the safety net for (well, that and the cases where someone forgot to run prek install): there the same checks run again (ruff format --check, ruff check, ty check, bandit, lint-imports, pytest), just as the last line of defence.
Series conclusion
These four tools, uv, ruff, ty and prek, each replace a slow, fragmented part of the Python toolchain with a fast Rust binary, while staying compatible with what they replace (pyproject.toml, Black formatting, .pre-commit-config.yaml).
And if I'm honest, they're usually the first tools of their kind that fit: a codebase that was already perfect here is something I've never seen before.
The series:
- Part 1: uv
- Part 2: ruff
- Part 3: ty
- Part 4: prek (this part)
- Part 5: a corset for AI agents (bonus)