Best pre-commit hooks setup for your Python project

Best pre-commit hooks setup for your Python project

Photo by M ZHA on Unsplash

Have you ever pushed your code or created a PR and then you realized that you forgot to remove that print statement or that your code formatting is messed up or even better you forgot to remove those unused imports?

If all of the above problems you face every day then you don't need to worry anymore, pre-commit hooks are here for the rescue.

So now the question is what is a pre-commit hook in the first place? A pre-commit hook is a bunch of scripts that reside in your .git directory & is run before every git commit. It is in the name only...

It will not allow you to commit your code if any of the scripts throws an error.

So basically while writing code you need to do some repeated & housekeeping work before you push your changes. We as developers don't like any manual work, right? So git provides you with a feature called hooks. These are scripts that run automatically every time a particular event occurs in a Git repository.

The most important event we are interested in is the pre-commit event which occurs before committing your code. There is actually a framework created that can help you set up these pre-commit hooks called pre-commit.

This is a Python package & can be installed using the pip or homebrew if you are using a MacBook.

Using pip

pip install pre-commit

Once you have installed this package you have to install hook scripts using this command.

pre-commit install

This command will modify your .git directory to insert some scripts related to hooks. This pre-commit package comes with some scripts or hooks which you use out of the box (It will download those scripts for you actually).

Now you need to create a configuration file that tells pre-commit to what actions to perform before committing your code. This file is called .pre-commit-config.yaml and should be present at the root of your project folder. As you guessed it by the .yaml extension of the file, all the configurations are done using YAML way.

After you have created this file, now add these lines to that.

repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v2.3.0
    hooks:
    -   id: check-yaml
    -   id: end-of-file-fixer

-   repo: https://github.com/psf/black
    rev: 21.12b0
    hooks:
    -   id: black

Let's understand the structure of it bit by bit. At the top, you can see a key called repos. It is the list of repositories or hook scripts you want to use. Inside this, you will have to list down of URLs of the repositories from which you need to download all the scripts for your pre-commit hooks.

To specify a list of scripts you need to use - with key repo and colon & the actual URL pointing to your GitHub repo. The rev is the revision or tag to clone at. The hooks are a list of hook mappings. Generally in your git repo, you can have multiple scripts & the hooks tag specifies that you only want to use some specific scripts.

Now you understand the structure of the configuration file. Now you look at the file you will see that we are using two different repos here. The first one is the official set of hooks maintained by the team behind the package pre-commit. To know more about those specific hooks you can go to the repo link & read their README.md file. It does a pretty good job to explain you all the hooks available there.

The second repo is for the black package. It is a very famous Python package to format your python code using the PEP8 style guide.

After this every time you try to commit your code, these scripts will run. If there is any issue in your code then you will start seeing some error messages on your console pointing to the filename & line number. If everything is okay then something like this.

Fix End of Files..............................................Passed
Trim Trailing Whitespace..............................Passed
black......................................................Passed

One thing to note is that these scripts will be only run against the files under commit. If you want to run it for all the files then use this command,

pre-commit run --all-files

Here are the pre-commit hooks which I use personally in my projects which have done a pretty good job for me & fitted my workflow.

isort for sorting imports.

It basically sorts your import statements alphabetically & separates them into a group.

hooks:
    - id: black
      exclude: \.py-tpl$
  - repo: https://github.com/PyCQA/isort

flake8 for linting.

flake8 has become pretty much a standard for Python linting. It just basically scans your Python code and finds which line of code does not adhere to Python's style guide. It enforces the PEP8 style guide on you.

repo: https://github.com/PyCQA/flake8
    rev: 5.0.0
    hooks:
      - id: flake8
        additional_dependencies: [flake8-print, pep8-naming, flake8-bugbear]

As you can see I have an additional package with flake8.

  • flake8-print - Checks for print statement in your code.
  • pep8-naming - Check your code against PEP 8 naming conventions.
  • flake7-bugbear - finding likely bugs and design problems in your program. Contains warnings that don’t belong in pyflakes and pycodestyle. Check the docs here

pycln for removing unused imports.

- repo: https://github.com/hadialqattan/pycln
    rev: v0.0.1-beta.3
    hooks:
    -   id: pycln

This is how my configuration file looks like,

exclude: (migrations)
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.2.0
    hooks:
    - id: check-ast
    - id: check-case-conflict
    - id: check-docstring-first
    - id: check-executables-have-shebangs
    - id: check-json
    - id: pretty-format-json
    - id: debug-statements
    - id: detect-private-key
    - id: end-of-file-fixer
    - id: trailing-whitespace
  - repo: https://github.com/psf/black
    rev: 22.6.0
    hooks:
    - id: black
  - repo: https://github.com/PyCQA/isort
    rev: 5.10.1
    hooks:
      - id: isort
  - repo: https://github.com/PyCQA/flake8
    rev: 5.0.0
    hooks:
      - id: flake8
        additional_dependencies: [flake8-print, pep8-naming, flake8-bugbear]
  - repo: https://github.com/hadialqattan/pycln
    rev: v0.0.1-beta.3
    hooks:
    -   id: pycln

These hooks will help you write & ship better code. There are plenty of hooks available out there. You can find any specific hook for your specific requirement or you write one for yourself.

Hope you learned something new today...