Emma Tech

  • Emma Home
  • Emma Blog
  • Job Openings
  • RSS

Python, PEP8 and Git hooks

Automate your inner neat freak

Kevin McConnell 11 Feb 2011 Git Python 9 Comments

In the book The Pragmatic Programmer, the authors describe a the­ory that states the impor­tance of main­tain­ing high stan­dards in all parts of a code­base, because any time we let one poor deci­sion in, it opens the door for more. It’s a psy­cho­log­i­cal effect that makes us much more tol­er­ant of intro­duc­ing hacks and short­cuts into already sub­stan­dard code than we would be when work­ing with some­thing that feels ele­gant and pris­tine. You let one bad idea in, and it’s all down­hill from there. I’ve found this applies to the lay­out and for­mat­ting of code just as much as to its func­tion­al­ity and design, and so I try to be dili­gent in keep­ing my code as clean as possible.

When work­ing in teams it’s also impor­tant to have a com­mon cod­ing style if you want to keep a con­sis­tent look and feel to a code­base, and if you want to be sure that all the code is equally read­able to each team member.

Handily enough, in the Python world there’s already a some­what estab­lished for­mat­ting stan­dard, PEP8, which most folks seem to like well enough, and which a lot of 3rd party libraries largely adhere to. Even bet­ter, there’s a script that can check for con­for­mance to that stan­dard. Given that we use Git at Emma, and love automa­tion, it seemed nat­ural to com­bine the two to have our cod­ing style checked auto­mat­i­cally as part of our workflow.

The basic idea is to have a pre-commit script that calls pep8 on the source tree, return­ing non-zero to abort the com­mit if there are any issues. The only slight com­pli­ca­tion is that I really only want to check the parts that are included in the com­mit — if I have unstaged changes that I’m not ready to com­mit 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 com­mit, copies them to a tem­po­rary folder and runs pep8 on that folder. If the scripts finds any issues, it will dis­play them and abort the commit.

To include this script in a repos­i­tory you’d save as it as pre-commit in the .git/hooks direc­tory, mak­ing sure it’s marked as exe­cutable. You’ll also need to have the pep8 script installed (pip install pep8). If you’d like to include it in all your repos­i­to­ries, you can use hook tem­plates. (Git doesn’t really sup­port global hooks, but it does have system-wide tem­plates that will be included in every new repos­i­tory that’s cre­ated, includ­ing those cloned from else­where). To add the script as a tem­plate, put it in your templates/hooks direc­tory. The loca­tion 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 exist­ing repos­i­to­ries to which you’d like to add this (or any other) tem­plate hook, you can sim­ply run git init on the repos­i­to­ries to re-initialize them. If, like me, you have a lot of repos­i­to­ries, a find com­mand might be a handy way to do a whole tree at once:

find . -name .git -exec git --git-dir {} init \;

Lastly, if there are occa­sions where you really do want to com­mit some­thing 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 mak­ing changes to some exist­ing code that’s not PEP8 com­pli­ant, and you want to sep­a­rate the func­tional changes and for­mat­ting changes into sep­a­rate com­mits (which is gen­er­ally a good idea).

I’ve been using a script like the above in my work­flow for about a year now, and I’ve found it very help­ful dur­ing that time. I hope you find some use in it too.

9 comments

Will McGugan commented:

2011-02-11, 17:41

Good solu­tion, but I can’t say I approve of such dra­con­ian pep-8 enforce­ment. I’d pre­fer to use my own judge­ment for those rare sit­u­a­tions where its rea­son­able to devi­ate from pep-8, which does say “when in doubt, use your best judgment”.

Kevin McConnell

Kevin McConnell commented:

2011-02-11, 18:29

Hi Will, thanks for your com­ment. You do always have the option to skip the check when­ever you want to devi­ate 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:

2011-06-10, 09:20

I’ve gen­er­ally felt that “pre con­di­tions” to com­mit can eas­ily get out con­trol. The last organ­i­sa­tion where I worked was a good exam­ple and peo­ple had to run mul­ti­ple test suites which took over 5 hours, then man­u­ally file a “checkin request” which had to approved by a release man­ager before they could com­mit their code. That is tak­ing it to the extreme but it’s a slip­pery slope. OTOH, this would be use­ful to keep for my per­sonal clones so that I can keep my own code clean (I pre­fer inte­grat­ing pylint with my edi­tor to give me style warn­ings but nev­er­the­less). I wouldn’t put this in a repo where mul­ti­ple peo­ple col­lab­o­rate though.

Kevin McConnell

Kevin McConnell commented:

2011-06-10, 10:50

Hey Noufal,

Thanks for your com­ment. I totally agree. It’s one thing to have tools or pro­ce­dures that help, but another thing entirely if they become obsta­cles to doing your work.

Personally, I run this hook script on all my local repos, but we don’t put any­thing like it into the shared repos. That way all my com­mits are run through it while I’m devel­op­ing, before I push that code up to oth­ers, but it’s not impos­ing any­thing else on the other folks if they’d rather work in a dif­fer­ent way.

For exam­ple, Michelle, one of our Portland devel­op­ers, uses a fancy Emacs mode that high­lights for­mat­ting errors while she’s typ­ing. I pre­fer to keep that stuff hid­den while I’m work­ing (well, except line length & trail­ing white­space, those I do like see­ing all the time), and so I just check my for­mat­ting at com­mit time. I don’t think either way is nec­es­sar­ily bet­ter than another. The most impor­tant thing is prob­a­bly that folks have the free­dom to work in what­ever way makes most sense for them, while still aim­ing for con­sis­tent results. What do you think?

Cheers,
Kevin

Nico commented:

2012-04-11, 02:40

Thanks for a com­pre­hen­sive tut. Inasmuch as there might be ‘best judge­ment’ tests, for me its bet­ter to stick to a strict cod­ing stan­dard with arguably poor read­abil­ity here and there than to have no code for­mat check­ing at all. The hook worked beau­ti­fully and I’ve imple­mented it in all my repos.

Kevin McConnell

Kevin McConnell commented:

2012-04-11, 08:48

Thanks Nico! Glad to hear this was use­ful to you.

Cheers,
Kevin

fetzig! commented:

2012-04-19, 06:44

great read, thx. would love to hear your opin­ion on this:

* ~5 devel­op­ers work­ing on a project/git repo
* most of them don’t fancy code style at all -> every devel­oper is cod­ing in a incon­sis­tent “code style” him­self :( (the sad smi­ley can’t even nearly describe how i feel about that)

approach:
* do meet­ings aka. code/commit reviews to estab­lish pep8,…
* wanna go even fur­ther in future by adding this hook on the remote repo to at least make sure every­body is fol­low­ing the basics (pep8)

i myself val­i­date with pyling/pep8 any­ways. so i love your hook. but is it — in your opin­ion — to much to “force” it upon a team? did you cus­tomized pep8 (i.e.: do you stay under 79chars per line)?

Kevin McConnell

Kevin McConnell commented:

2012-04-23, 12:34

That’s a great ques­tion. I lean more towards only using hooks like this to auto­mate the things that I want to do any­way, and less as a way to enforce the rules. So for me, I’d want to get the team in agree­ment first about adopt­ing a com­mon style, and then use the hook to help peo­ple get into the habit. If some­one really doesn’t want to fol­low the agreed style, try­ing to force it on them at com­mit time will prob­a­bly just end up being annoy­ing for them, so it might not be the best way to win them over. It does take a while to change habits some­times. However, if you can get everyone’s sup­port to give it a try for a while, then they might find that they start to enjoy it, and that the auto­mated check feels more like a friendly assis­tant than an angry over­lord ;) That’s how it feels to me. So I’d say, start with the meet­ings and dis­cus­sions first, and only add the script when peo­ple are com­fort­able with the idea.

But, it all depends on the dynam­ics of your team or orga­ni­za­tion, of course. Some folks pre­fer to nom­i­nate a team mem­ber or team lead to be respon­si­ble for code qual­ity, and adher­ing to com­mon con­ven­tions could def­i­nitely be con­sid­ered part of that qual­ity. So in that case, being stricter about the rules could be OK.

I don’t really cus­tomize pep8, but I do break the rules occa­sion­ally when it feels right. So I try to stay within that line length limit most of the time, but if I find some­thing where adding breaks makes it less read­able, I’ll just com­mit it with a –no-verify. But I find that most of the time the default rules work out pretty well.

Thanks for your com­ment. And if you try it out with your cowork­ers, let me know how you get on!
Kevin

Django developer commented:

2012-05-31, 00:44

I love this. Great arti­cle Kev! I work in a team who’ve recently moved from PHP code-bases to Python. So enforc­ing a com­mon set of con­ven­tions like PEP8 on the dev via a Git hook is a sim­ply bril­liant idea.

About Kevin McConnell

Kevin McConnell

Kevin serves as our Director of Engineering. Hailing originally from Edinburgh, Kevin has injected the office with words like "dodgy" and "wee." As in a wee spot of tea to go with his toast snack. Did we mention Kevin's devotion to toast? Just say the word and he lights up.

Read more from Kevin

Emma Tech on Twitter

    Follow Emma Tech »
    Help wanted

    • Popular Tags

      Python13 api7 UX5 conferences4 postgres4 workflow4 time4 javascript3 PHP3 jQuery3 tools3 CSS2 editors2 travel2 server maintenance2 Git2 maintenance windows2 Haml1 Frank1 PyCon1 Ruby1 community1 collaboration1 Sass1 office1 downtime1 post‑mortems1 cgit1 books1 Trac1 Convore1 Vim1 usability testing1 github1 Redis1 Facebook1 Twitter1 Social Posting1 salesforce1 BeautifulSoup1 open source1 soupselect1 InlineStyler1 integration1 objects programming refactoring1 OAuth1 cool sites1 legacy data1 testing1 releases1 bugs1 san francisco1 TextExpander1 TDD1 PgCon1 music1 coding1 productivity1 reading1 Django1 HTML1

    Emma is a member of the Email Sender & Provider Coalition and the Messaging Anti-Abuse Working Group.

    Copyright © 2003 - 2018 Emma.
    All rights reserved.

    • Get Emma's Newsletter
    • Visit the Emma Blog
    • @emmaemailtech on Twitter
    • @emmaemail on Twitter
    • Emma on Facebook

    Emma's email marketing makes communicating simple and stylish.
    Inquire now for more details.