This helps a lot, but even the most diligent developer has a bad day. A pre-commit hook removes the human error and acts as a final frontier to prevent bad code from entering your repository.

Check the whole project with flake8 before every commit

When starting with a new Python project, you can configure a Git pre-commit hook that runs flake8 before every commit. That ensures that you never accidentally introduce any issues and that your codebase is always clean.

#!/bin/sh
flake8

Once you created the file, you have to make it executable. On Linux or MacOS the command would be:

chmod u+x .git/hooks/pre-commit

Now every time you commit, your code is checked with flake8, and if it doesn’t pass, your commit is rejected.

The pre-commit hook is running flake8 against the current working copy, which contains the fix. What we really want to do is run flake8 against what is actually committed, i.e. the changes that are currently staged. This can be done, but it makes the hook a bit more complicated.

At some point, your project will probably grow so large that checking the whole project takes too long. If you (and everyone on your team) used this hook, most of your codebase doesn’t trigger any warnings anyway, so let’s change our pre-commit hook to only check modified files. Find the CHECK variable at the beginning and change it like this.

CHECK=flake8 `git diff --cached --name-only` || errors=1

Edit: the maintainer of flake8 chimed in on the Reddit discussion of this post to note that flake8 actually has the ability to install a pre-commit hook with more or less the same functionality. Go check out the flake8 documentation on using hooks and see if it fits your needs. One advantage of the approach presented here is that it also works with other tools.

Check only the code that was modified

When working on a legacy project that already has a ton of flake8 warnings, such a pre-commit hook that checks the whole file is to probably restrictive. It won’t let you push before your resolved all issues. While it is certainly a good idea to clean up the portions of the code that you are working on, you probably don’t want to fix up every PEP8 violation in every file you touch in a commit. If you need any convincing that this might be a bad idea, check out the talk Beyond PEP 8 — Best practices for beautiful intelligible code, given by Raymond Hettinger at PyCon 2015. It’s almost an hour long, but quiet entertaining and worth every minute.

Now, wouldn’t it be awesome to just check the lines you changed?

Today is your lucky day, because flake8 can actually do exactly that! Just pipe a diff to stdin, e.g. from git:

git diff -U0 | flake8 --diff

Working with the diff means we don’t have to stash any changes, so the pre-commit hook now looks like this:

#!/bin/sh
git diff --cached -U0 | flake8 --diff

Limiting flake8 to check only lines that changed has one limitation: it doesn’t catch issues that your changes introduced in other parts of the code. The most common case is probably removing a line that uses an import, but forgetting to remove the corresponding import statement.

A solution to this could be hook that checks that your changes do not increase the total number of flake8 warnings in the project. This requires checking the whole project twice and is better suited for a pre-push hook. I will show you how to write such a pre-push hook in a future post. If you want to be notified of the post, follow @consideratecode on Twitter or subscribe to my mailing list.