Skip to main content
  1. Posts/

Pre-commit Setup for Your Project

·713 words·4 mins·
Table of Contents

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-commit

Note 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-files

and then install the hooks:

uv run pre-commit install

Local 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]
Warning

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-files

You 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
#

Related