Python, PEP8 and Git hooks
Automate your inner neat freak
In the book The Pragmatic Programmer, the authors describe a theory that states the importance of maintaining high standards in all parts of a codebase, because any time we let one poor decision in, it opens the door for more. It’s a psychological effect that makes us much more tolerant of introducing hacks and shortcuts into already substandard code than we would be when working with something that feels elegant and pristine. You let one bad idea in, and it’s all downhill from there. I’ve found this applies to the layout and formatting of code just as much as to its functionality and design, and so I try to be diligent in keeping my code as clean as possible.
When working in teams it’s also important to have a common coding style if you want to keep a consistent look and feel to a codebase, and if you want to be sure that all the code is equally readable to each team member.
Handily enough, in the Python world there’s already a somewhat established formatting standard, PEP8, which most folks seem to like well enough, and which a lot of 3rd party libraries largely adhere to. Even better, there’s a script that can check for conformance to that standard. Given that we use Git at Emma, and love automation, it seemed natural to combine the two to have our coding style checked automatically as part of our workflow.
The basic idea is to have a pre-commit script that calls pep8 on the source tree, returning non-zero to abort the commit if there are any issues. The only slight complication is that I really only want to check the parts that are included in the commit — if I have unstaged changes that I’m not ready to commit yet, I don’t want those to be checked.
Here’s the pre-commit script I use:
#!/usr/bin/env python
from __future__ import with_statement
import os
import re
import shutil
import subprocess
import sys
import tempfile
def system(*args, **kwargs):
kwargs.setdefault('stdout', subprocess.PIPE)
proc = subprocess.Popen(args, **kwargs)
out, err = proc.communicate()
return out
def main():
modified = re.compile('^[AM]+\s+(?P<name>.*\.py)', re.MULTILINE)
files = system('git', 'status', '--porcelain')
files = modified.findall(files)
tempdir = tempfile.mkdtemp()
for name in files:
filename = os.path.join(tempdir, name)
filepath = os.path.dirname(filename)
if not os.path.exists(filepath):
os.makedirs(filepath)
with file(filename, 'w') as f:
system('git', 'show', ':' + name, stdout=f)
output = system('pep8', '.', cwd=tempdir)
shutil.rmtree(tempdir)
if output:
print output,
sys.exit(1)
if __name__ == '__main__':
main()
Download pre-commit script from GitHub
Essentially this just runs git status to get a list of the Python files that are staged in the commit, copies them to a temporary folder and runs pep8 on that folder. If the scripts finds any issues, it will display them and abort the commit.
To include this script in a repository you’d save as it as pre-commit in the .git/hooks directory, making sure it’s marked as executable. You’ll also need to have the pep8 script installed (pip install pep8). If you’d like to include it in all your repositories, you can use hook templates. (Git doesn’t really support global hooks, but it does have system-wide templates that will be included in every new repository that’s created, including those cloned from elsewhere). To add the script as a template, put it in your templates/hooks directory. The location of that will depend on how you install Git; on my machine the path is:
/usr/local/git/share/git-core/templates/hooks/pre-commit
If you have some existing repositories to which you’d like to add this (or any other) template hook, you can simply run git init on the repositories to re-initialize them. If, like me, you have a lot of repositories, a find command might be a handy way to do a whole tree at once:
find . -name .git -exec git --git-dir {} init \;
Lastly, if there are occasions where you really do want to commit something that’s not pep8-compliant, you can skip the checks with the --no-verify flag, like this:
git commit --no-verify
That can be handy if you’re making changes to some existing code that’s not PEP8 compliant, and you want to separate the functional changes and formatting changes into separate commits (which is generally a good idea).
I’ve been using a script like the above in my workflow for about a year now, and I’ve found it very helpful during that time. I hope you find some use in it too.

Will McGugan commented:
Good solution, but I can’t say I approve of such draconian pep-8 enforcement. I’d prefer to use my own judgement for those rare situations where its reasonable to deviate from pep-8, which does say “when in doubt, use your best judgment”.
Kevin McConnell commented:
Hi Will, thanks for your comment. You do always have the option to skip the check whenever you want to deviate from the PEP8 style (I do that myself every now and again). I agree that my approach might not be to everyone’s taste, though :)
Cheers
Kevin
Noufal Ibrahim commented:
I’ve generally felt that “pre conditions” to commit can easily get out control. The last organisation where I worked was a good example and people had to run multiple test suites which took over 5 hours, then manually file a “checkin request” which had to approved by a release manager before they could commit their code. That is taking it to the extreme but it’s a slippery slope. OTOH, this would be useful to keep for my personal clones so that I can keep my own code clean (I prefer integrating pylint with my editor to give me style warnings but nevertheless). I wouldn’t put this in a repo where multiple people collaborate though.
Kevin McConnell commented:
Hey Noufal,
Thanks for your comment. I totally agree. It’s one thing to have tools or procedures that help, but another thing entirely if they become obstacles to doing your work.
Personally, I run this hook script on all my local repos, but we don’t put anything like it into the shared repos. That way all my commits are run through it while I’m developing, before I push that code up to others, but it’s not imposing anything else on the other folks if they’d rather work in a different way.
For example, Michelle, one of our Portland developers, uses a fancy Emacs mode that highlights formatting errors while she’s typing. I prefer to keep that stuff hidden while I’m working (well, except line length & trailing whitespace, those I do like seeing all the time), and so I just check my formatting at commit time. I don’t think either way is necessarily better than another. The most important thing is probably that folks have the freedom to work in whatever way makes most sense for them, while still aiming for consistent results. What do you think?
Cheers,
Kevin
Nico commented:
Thanks for a comprehensive tut. Inasmuch as there might be ‘best judgement’ tests, for me its better to stick to a strict coding standard with arguably poor readability here and there than to have no code format checking at all. The hook worked beautifully and I’ve implemented it in all my repos.
Kevin McConnell commented:
Thanks Nico! Glad to hear this was useful to you.
Cheers,
Kevin
fetzig! commented:
great read, thx. would love to hear your opinion on this:
* ~5 developers working on a project/git repo
* most of them don’t fancy code style at all -> every developer is coding in a inconsistent “code style” himself :( (the sad smiley can’t even nearly describe how i feel about that)
approach:
* do meetings aka. code/commit reviews to establish pep8,…
* wanna go even further in future by adding this hook on the remote repo to at least make sure everybody is following the basics (pep8)
i myself validate with pyling/pep8 anyways. so i love your hook. but is it — in your opinion — to much to “force” it upon a team? did you customized pep8 (i.e.: do you stay under 79chars per line)?
Kevin McConnell commented:
That’s a great question. I lean more towards only using hooks like this to automate the things that I want to do anyway, and less as a way to enforce the rules. So for me, I’d want to get the team in agreement first about adopting a common style, and then use the hook to help people get into the habit. If someone really doesn’t want to follow the agreed style, trying to force it on them at commit time will probably just end up being annoying for them, so it might not be the best way to win them over. It does take a while to change habits sometimes. However, if you can get everyone’s support to give it a try for a while, then they might find that they start to enjoy it, and that the automated check feels more like a friendly assistant than an angry overlord ;) That’s how it feels to me. So I’d say, start with the meetings and discussions first, and only add the script when people are comfortable with the idea.
But, it all depends on the dynamics of your team or organization, of course. Some folks prefer to nominate a team member or team lead to be responsible for code quality, and adhering to common conventions could definitely be considered part of that quality. So in that case, being stricter about the rules could be OK.
I don’t really customize pep8, but I do break the rules occasionally when it feels right. So I try to stay within that line length limit most of the time, but if I find something where adding breaks makes it less readable, I’ll just commit it with a –no-verify. But I find that most of the time the default rules work out pretty well.
Thanks for your comment. And if you try it out with your coworkers, let me know how you get on!
Kevin
Django developer commented:
I love this. Great article Kev! I work in a team who’ve recently moved from PHP code-bases to Python. So enforcing a common set of conventions like PEP8 on the dev via a Git hook is a simply brilliant idea.