still don't have a title

Introducing Noir

tl;dr

Noir is a drop-in replacement for Black (the uncompromising code formatter), with the default line length set to PEP-8’s preferred 79 characters. If you want to use it, just replace black with noir in your requirements.txt and/or setup.py and you’re good to go.

Black is a Python code formatter that reformats your code to make it more PEP-8 compliant. It implements a subset of PEP-8, most notably it deliberately ignores PEP-8’s suggestion for a line length of 79 characters and defaults to a length of 88. I find the decision and the reasoning behind that somewhat arbitrary. PEP-8 is a good standard and there’s a lot of value in having a style guide that is generally accepted and has a lot of tooling to support it.

When people ask to change Black’s default line length to 79, the issue is usually closed with a reference to the reasoning in the README. But Black’s developers are at least aware of this controversial decision, as Black’s only option that allows to configure the (otherwise uncompromising) code formatter, is in fact the line length.

Apart from that, Black is a good formatter that’s gaining more and more popularity. And, of course, the developers have every right to follow their own taste. However, since Black is licensed under the terms of the MIT license, I tried to see what needs to be done in order to fix the line length issue.

Step 1: Changing the Default

This is the easiest part. You only have to change the DEFAULT_LINE_LENGTH value in black.py from 88 to 79, and black works as expected. Bonus points for doing the same in black.vim and pyproject.toml, but not strictly necessary.

Step 2a: Fixing the Tests

Now comes the fun part. Black has an extensive test suite and suddenly a lot of tests are failing because the fixtures that compare the unformatted input with the expected, formatted output were written with a line length of 88 characters in mind. To make it more interesting the expected output comes in two forms: (1) as normal reformatted Python code (which is rather easy to fix) and (2) as a diff between the input and the expected output. The latter was really painful to fix – although I’m very much used to reading diffs, I don’t usually write them.

Step 2b: Fixing the Tests

After all fixtures were updated, some tests were still failing. And it turned out that Black is running itself on its own source code as part of its test suite, making the tests fail if Black’s code does not conform to Black’s coding standards. While this is a genius idea, it meant that I had to reformat Black’s code to match the new 79 characters line length, generating a giant diff, that is functionally unrelated to the fix I wanted to make but now part of the fix anyway. This of course makes the whole patch horrible to maintain if you plan to follow along upstream’s master branch.

Step 3: Publish

Since we already got this far, why not publish the fixed version of Black? To my surprise the name noir was still available on PyPi, so I renamed my version of Black to Noir and uploaded it to PyPi.

You can install it via:

$ pip install noir

Since I didn’t change anything else, this is literally a drop-in replacement for Black. All you have to do is replace black with noir in your requirements.txt and/or setup.py and you’re good to go. The script that executes Black is still called black and the server is still called blackd.

Outlook

While this was a fun exercise, the question remains what to do with it. I’ll try to follow upstream and update my patch whenever a new version will come out. As new versions of Black are released only a handful of times a year, this might be feasible.

Depending on how painful it is to maintain the patch for the tests, I might either drop the tests altogether, relying on upstream’s tests passing on their side and just maintaining the trivial patch from Step 1: Changing the DEFAULT_LINE_LENGTH. The latter can probably be automated somehow using github actions – and I’ll probably look into that at some point.

Best case scenario, of course, would be if Python changes its recommended line length to 88 and I wouldn’t have to maintain noir in the first place :)