Pre-commit hooks in Python

What they are and how they can help your team achieve more consistency without added hassle!

Ian Cleary

Posted: Sunday, June 13, 2021

4 min read

Table of Contents

Article Content

Yellow Diamong Icon which is the icon for the Pre-Commit project

Pre-commit hooks can help reduce code style discrepancies amongst a code base by performing checks on each contributors git repo before they commit code and push to the remote server.

pre/post and client/server 2x2 grid showing quadrants of where actions can be taken in a git based development flow, with examples

What can be done on the client before commiting and pushing?


This client side check can performan many things:

  • format code (check line length and style)
  • check imports
  • check white space consistency
  • check newline and end of file

Most of these checks are mundane when done consistently, but annoying if not infuriating when done inconsistently. Infuriating may be an exxageration, but repeated inconsistencies take mental effort to notice and deal with. It can break the flow of contribution and isn't something to spend time worry about.

It doesn't have to be tedious.

Git hooks and Pre-commit

https://pre-commit.com/ is the documentation for a wonderful package. Let's let them explain it!

Introduction

Git hook scripts are useful for identifying simple issues before submission to code review. We run our hooks on every commit to automatically point out issues in code such as missing semicolons, trailing whitespace, and debug statements. By pointing these issues out before code review, this allows a code reviewer to focus on the architecture of a change while not wasting time with trivial style nitpicks.

As we created more libraries and projects we recognized that sharing our pre-commit hooks across projects is painful. We copied and pasted unwieldy bash scripts from project to project and had to manually change the hooks to work for different project structures.

We believe that you should always use the best industry standard linters. Some of the best linters are written in languages that you do not use in your project or have installed on your machine. For example scss-lint is a linter for SCSS written in Ruby. If you’re writing a project in node you should be able to use scss-lint as a pre-commit hook without adding a Gemfile to your project or understanding how to get scss-lint installed.

We built pre-commit to solve our hook issues. It is a multi-language package manager for pre-commit hooks. You specify a list of hooks you want and pre-commit manages the installation and execution of any hook written in any language before every commit. pre-commit is specifically designed to not require root access. If one of your developers doesn’t have node installed but modifies a JavaScript file, pre-commit automatically handles downloading and building node to run eslint without root.

Quick start

1. Install pre-commit

Follow the install instructions above pre-commit --version should show you what version you're using

pre-commit --version
pre-commit 2.13.0

2. Add a pre-commit configuration

Create a file named .pre-commit-config.yaml You can generate a very basic configuration using pre-commit sample-config Yhe full set of options for the configuration are listed below This example uses a formatter for python code, however pre-commit works for any programming language Other supported hooks are available

repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v2.3.0
    hooks:
    -   id: end-of-file-fixer
    -   id: trailing-whitespace
    -   id: check-toml
-   repo: https://github.com/psf/black
    rev: 20.8b1
    hooks:
    -   id: black

3. Install the git hook scripts

Run pre-commit install to set up the git hook scripts

$ pre-commit install
pre-commit installed at .git/hooks/pre-commit

Now pre-commit will run automatically on git commit!

4. (optional) Run against all the files

It's usually a good idea to run the hooks against all of the files when adding new hooks (usually pre-commit will only run on the changed files during git hooks)

$ pre-commit run --all-files
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Initializing environment for https://github.com/psf/black.
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/psf/black.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
Check Yaml...............................................................Passed
Fix End of Files.........................................................Passed
Trim Trailing Whitespace.................................................Failed
- hook id: trailing-whitespace
- exit code: 1

Files were modified by this hook. Additional output:

Fixing sample.py

black....................................................................Passed

Oops! looks like I had some trailing whitespace Consider running that in CI too

Conclusion

Pre-commits are an excellent tool to automate the mundane sources of inconsistencies amongst our individual and team projects. The above just scratches the surface.