Generating Pylint Badges

Badges, on github and other code sharing sites, are a way to communicate small snippets of information concerning the status of your repository. They generally appear on your README file, so they are one of the first things you see when you load a repository page.

On my yawt page, for example, you can see several badges for things like my travis build status, and my GPA at code climate.

One thing that I couldn't find, however, was a pylint badge - a simple status that displayed your pylint score. This surprised me; it seemed like it should have been a common thing to want to do. Indeed, I'm still not 100% sure that I haven't simply overlooked something glaringly obvious.

But, alas, for whatever reason, I couldn't find one, and I haven't seen any on the various python projects I've come across, so...I decided to make one. It turns out that it's not hard.

The github badges that I've seen are mostly similar, in a very general sense. There's a web endpoint of some sort which will receive and process some information and another endpoint which will provide the badge image itself. The image is often a link to a some sort of page which summarizes the status that the badge is representing.

In the case of travis, code climate and quantified code, for example, there's an endpoint which processes a github webhook. Whenever you push code to github, these endpoints are notified and the status badges (or, at least, the information used to render the status badges) are updated. The README will just display the latest badge, linked to a page specialized for each service.

Coveralls, a service for reporting test coverage, does something a bit different. For python code, it works off of travis (or some other continuous integration service) directly. This makes sense because travis is already used, in many cases, to run the tests (many projects will run their tests as part of their build process). Travis will install the application's dependencies, run the build, run the tests (collecting coverage information at the same time), and then send the coverage stats to coveralls for storage and perusal. Sending the coverage data is accomplished via a special web client that is easily downloaded via pip. Its installation forms a part of the travis set up procedure for your project.

In my case, the goal was pretty simple: put a colour coded badge on my README file that linked to the generated pylint report for the latest build. Structurally speaking, this is fairly similar to coveralls in that I needed to run a program (pylint) against an environment that had all the project's dependencies already installed. So it made sense to do something similar to what coveralls was doing, i.e. run pylint as part of my build process in travis, and send the information to a web service of some kind that I had yet to write.

All the badges I've seen so far use the SVG image format, and it's not hard to see why - it's a variant of XML and hence easy to generate dynamically. So the first thing I did was adapt my code climate badge to create a pylint version. The end result looked like this:

rating.svg:

<svg xmlns="http://www.w3.org/2000/svg" width="85" height="20">
    <linearGradient id="a" x2="0" y2="100%">
        <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
        <stop offset="1" stop-opacity=".1"/>
    </linearGradient>
    <!-- whole rectangle -->
    <rect rx="3" width="85" height="20" fill="#555"/>

    <!-- rating part -->
    <rect rx="3" x="50" width="35" height="20" fill="#44cc11"/>
    <path fill="#44cc11" d="M50 0h4v20h-4z"/>

    <!-- whole rectangle -->
    <rect rx="3" width="85" height="20" fill="url(#a)"/>

    <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
        <text x="25" y="15" fill="#010101" fill-opacity=".3">pylint</text>
        <text x="25" y="14">pylint</text>
        <text x="67" y="15" fill="#010101" fill-opacity=".3">9.27</text>
        <text x="67" y="14">9.27</text>
    </g>
</svg>

Pylint gives the yawt repository a score of 9.27 out of 10. I hardcoded this score into the rating.svg file, and decided that this deserved a colour of green. I uploaded the rating.svg to my personal external server, as well as the report generated by pylint (in HTML format). Next I updated my README file to show the badge.

So far, all I'd done is embed a static SVG image into a README file, and linked against a static HTML report. This is hardly interesting, so the next step was to update the files every time I made a commit.

Introducing pylint_server

To update the report and rating file, I wrote a small Flask app called, creatively, pylint_server that accepts a HTML formatted pylint report from a client, and saves it in a publically accessible location. In addition, the app updates the rating.svg file by extracting the rating from the report. There's nothing fancy going on here; I simply scrape the report for the rating number and save it into the rating file. In addition, I update the colour to reflect the rating: green (>= 9) is best, orange (<9, >= 7) is so-so and red (< 7) needs improvement.

The report and rating file are saved in a location that matches the repository with which the pylint score is associated. This link is made via the travis job id, which is passed along with the report. Using the job id, a query is made to travis to get the repository slug, which basically consists of my username and repository name, which are then both used to construct the path to the report and rating file (the slug is first checked against a hardcoded list of valid repository slugs).

The pylint_server app is hit every time I run a travis build. I don't need a special client here; I just use plain old curl to send off the report and the travis job id. My .travis.yml file looks more or less like this:

sudo: false
language: python
python:
    - "2.7"

# command to install dependencies
install:
    - "pip install -r requirements-test.txt" 
    - "pip install coveralls"
    - "pip install pylint"

# command to run tests
script: nosetests --with-coverage --cover-erase --cover-inclusive --cover-package=pylint_server

after_success:
    - coveralls
    - pylint --output-format=html pylint_server > /tmp/pylint-report.html
    - curl -v -m 120 -X POST -F travis-job-id=$TRAVIS_JOB_ID -F pylint-report=@/tmp/pylint-report.html https://pylint.yourserver.com/reports

Obviously, your .travis.yml file will differ.

That's pretty much it. Source code for pylint_server can be found on github. It even has its own pylint badge.