All checks passed, happy developer

UV, Pre-commit and no global python

Working on different projects in the same time, I aim to keep my development environment minimal, predictable, and free of hidden global dependencies. This post explains why that philosophy led me to replace pre-commit with prek — and why a single, fast, dependency-free binary fits better than a Python-based tool.

Intro

For about a year now, I’ve been using uv for Python project management. One of its most underrated benefits is that I no longer need a global Python installation. When you create a new project or environment with uv init or uv venv, uv automatically installs a project-local Python version for you.

I’ve fully leaned into that model. To avoid conflicts, confusion, and the classic “oh right, I forgot to activate the virtualenv” moment, I don’t have Python installed globally at all. That way, if I run uv add <package> or pip install <package> without an active environment, it simply fails — because there is no Python on my $PATH. This makes mistakes obvious and keeps my setup clean. So far, so good.

The downside appears when you want to use Python-based tools that are not really part of the project itself.

Some tools are useful across many projects and languages, and don’t belong in the runtime or even development dependencies of a single project. uv supports dependency groups, so you could put such tools in a dev group — but that still means every project ends up with its own copy of the same tool. It also means those tools are only available in Python projects, which feels unnecessarily limiting.

That’s where things like pre-commit come into play.

The pre-commit issue

Pre-commit is widely used to run checks, linters, and tests before code is committed to Git. The idea is simple and solid: don’t commit code that hasn’t been validated. In practice, pre-commit has become a core part of many development workflows.

The catch is that pre-commit is built in Python. You either install it globally (which requires a global Python installation), or you add it as a dependency to each project. Neither option fits particularly well with a setup where Python environments are intentionally isolated and disposable.

Screenshot of pre-commit output example

Prek to the rescue

Prek is a re-imagined version of pre-commit, built in Rust. It’s designed to be a faster, dependency-free, drop-in replacement, while also adding some long-requested features.

You can run it explicitly using the prek command, or you can use it as a transparent replacement for pre-commit. Existing configurations continue to work.

Because prek is a compiled Rust binary, it doesn’t need Python at all. That means I can install it once and use it everywhere — in Python, Rust, Ruby, or Node.js projects — without pulling in language-specific tooling or duplicating dependencies across projects.

It’s also noticeably faster. Prek can clone hook repositories in parallel and benefits from Rust’s performance characteristics, rather than Python’s. The result is a snappier feedback loop with fewer moving parts.

For my workflow – no global Python, strict project isolation, and shared tooling across languages – prek fits naturally where pre-commit does not.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.