We have a Python project where there are several developers, and we have a mature CI pipeline to do code linting, formatting check for python, yaml, json, and testing etc. The problem with CI pipeline is that you can only get feedback after you commit your code and push to the remote repo. The feedback is not instant and may be delayed for several minutes. If there are some linting issues, you then need to fix the issue and make another commit. This is also making the PR history messy.
For me personally, I have configured my Neovim to show linting issues, and also format after save. I can not enforce other developers the same local setup as me, because different developers are using different local environment (editor, OS, and terminal).
After some research, I think pre-commit is a good solution for this. Basically, it will manage and run a list of hooks before you commit your code. If there are some issues with your code, you can not commit your code. This will force the developers to fix the issue before they even hit the CI pipeline.
Install and set up#
To integrate pre-commit into a project managed by uv:
uv add --dev pre-commitNote that we use --dev here, because pre-commit is only needed for development,
not necessary for the running of the project/application itself.
Then create .pre-commit-config.yaml in the project root.
Add your configuration for pre-commit hooks there, something like this:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: check-added-large-filesand then install the hooks:
uv run pre-commit installLocal repo/hooks#
Usually, for pre-commit, we use the hooks provided by some remote repositories.
For our project, since we already declare black, ruff and uv as dependencies,
we do not really want to install another set of tools somewhere else.
For this, we need to set repo to local:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-added-large-files
# the following are local hooks, the repo is local
- repo: local
hooks:
- id: black
name: black-format-check
entry: uv run black --check ./
pass_filenames: false
language: system
types: [python]Configuring hooks#
All config options for a hook can be found here.
This is a valid local hook:
- id: black
name: black-format-check
entry: uv run black --check ./
pass_filenames: false
language: system
types: [python]For local hook, you can set the language to system.
Then pre-commit will not try to set up the environment to run this hook.
pass file names or not?#
By default, pre-commit will pass file names to each hook entry,
but for some hooks, they do not work that way.
If you pass the file names to those hook, they will error out or have strange behavior.
For example, for ruff, we usually just check the whole project, not a single file.
You can set up a ruff-linting hook like this:
- id: ruff
name: ruff-linting
entry: uv run ruff check app/
pass_filenames: false
language: system
types: [python]The option pass_filenames: false is crucial here.
Without this, it seems that ruff will not fully follow the pyproject.toml,
see this github issue.
This is not essentially a pre-commit issue, but a ruff “issue”/feature? If you ignore a file in ruff config, but pass it explicitly to ruff, ruff is still going to check it.
#pyproject.toml
[tool.ruff]
exclude = ["my_file.py"]If you run ruff check my_file.py, ruff will report issues in this file.
Running pre-commit in pipeline#
If you do not want to separate steps in your CI pipeline for different checks, using pre-commit in the CI is also a good idea.
Basically, in one of the CI step, we need to run the following command:
uv run pre-commit run --all-filesYou may also consider caching the pre-commit cache to speed up pipeline, if you use a lot of external repos for the hooks. See also the official doc on managing CI caches.
Use uv under the hood to manage pre-commit env?#
This is also possible, see pre-commit-uv and also this post. The author seems to be not interested to add support directly for uv to manage the virtual env, see this closed PR.
References#
- official doc for config: https://pre-commit.com/#adding-pre-commit-plugins-to-your-project