How to Protect Your Python Code Health 🐍🩺
Tags: devops, python, tutorial, showdev
Published: 2021-06-06 22:25:53

Code can be long and complex. Let's configure a few good tools to help keep our Python codebase healthy. Flake8, Black, and isort.

## Linting and Formatting

[Flake8](https://flake8.pycqa.org/en/latest/) is a [linter](https://en.wikipedia.org/wiki/Lint_(software)). It's designed to find mistakes in your code that can later become bugs when your code is run.

A formatter arranges our code so that it's more readable on the screen, but does not change what our code does. [Black](https://black.readthedocs.io/en/stable/) and [isort](https://pycqa.github.io/isort/) are formatters.

---

## Installation

Let's install our linting and formatting tools. For a healthy coding environment use a virtual environment like `venv` or `virtualenv` (`venv` comes with Python).

Create a virtual environment and activate it.

```
# bash
python3 -m venv venv
source venv/bin/activate

# pwsh
py -3 -m venv venv
venv\Scripts\activate
```

Install the packages.

```bash
pip install flake8 black isort
```

---

## Configuration

You'll want to configure these tools a little to make them play nicely together.

### Flake8 📋

There's a lot you can configure with **Flake8**, but you don't have to. Add the bare minimum first to get **Black** and **Flake8** to cooperate (also we'll exclude a few directories from linting). In the root of your project create a file called `.flake8`.

```toml
# .flake8

[flake8]
max-line-length = 88
extend-ignore = E203
exclude =
    .git,
    __pycache__,
    build,
    dist,
    venv
```

### Black 🖊️

**Black** is famous as a highly opinionated "uncompromising code formatter". While there are a few settings you can configure, nearly all of the formatting choices are made for you. That's actually half of its appeal. The other half is getting back all the time you previously used to tweak where this comma was or that spacing. Embrace the way **Black** will do it all for you and you'll thank me later.

In the root of your project create a file called `pyproject.toml`.

```
# pyproject.toml

[tool.black]
exclude = 'venv'
```

### isort 🗂️

Short for "import sort" or just "I Sort", this utility will arrange your module's imports in a consistent opinionated way, much like **Black** does for the rest of your code.

Let's configure **isort** to play nicely with **Black**. Add the following to our `pyproject.toml`.

```toml
# pyproject.toml

[tool.isort]
profile = "black"
multi_line_output = 3
skip = ["venv"]
```

---

## Checking Your Code's Health 🩺

Let's run our tools individually first.

```
python -m flake8

python -m black .

python -m isort .
```

Now let's make this a little easier for ourselves by setting up a **pre-commit hook**. In your project create a file called `.git/hooks/pre-commit`.

```bash
#!/bin/sh
echo "----------------------"
echo "--- Pre-Commit Hook --"
echo "----------------------"
echo "📋 Flake8 📋"
python -m flake8 && echo "No Errors"
echo ""

echo "🖊️  Black 🖊️"
python -m black .
echo ""

echo "🗂️  isort 🗂️"
python -m isort . && echo "Imports Sorted"
echo ""
echo "----------------------"
```

When you run `git commit` this script will run and check your code health before a commit is made. It will show you what issues **Flake8** has found and it will format your code. You'll then get to go back and fix the issues. Finally, you'll run `git commit` again.

Here's what it looks like with errors.

```bash
----------------------
--- Pre-Commit Hook --
----------------------
📋 Flake8 📋
.\src\packagecake\__main__.py:2:1: F401 'sys' imported but unused
.\src\packagecake\__main__.py:6:1: E302 expected 2 blank lines, found 1

🖊️  Black 🖊️
reformatted src\packagecake\__main__.py
All done! ✨ 🍰 ✨
1 file reformatted, 4 files left unchanged.

🗂️  isort 🗂️
Skipped 1 files
Imports Sorted

-----------------
```

Here's what it will look like when you're code is healthy.

```bash
----------------------
--- Pre-Commit Hook --
----------------------
📋 Flake8 📋
No Errors

🖊️  Black 🖊️
All done! ✨ 🍰 ✨
5 files left unchanged.

🗂️  isort 🗂️
Skipped 1 files
Imports Sorted

-----------------
```

---

## Automation 🤖

With code health, the more often you can check it the better. You've seen how to setup a **Git hook**. Here's a few other ways to run **Flake8**, **Black**, and **isort**.

### IDE

Your code editor likely integrates with these tools using only some light configuration. You'll benefit from spellcheck-like behavior from **Flake8**, and format on save from **Black** and **isort**.

Some info on setting up [VSCode](https://code.visualstudio.com/docs/python/linting) for linting and formatting.

### Pre-Commit

The [Pre-Commit Framework](https://pre-commit.com/) is **Git hooks** with super powers. Its easy setup and configuration give you access to an [extensive list of shared Git hooks](https://pre-commit.com/hooks.html).

### CI/CD Pipelines

While a Git hook is great for catching errors before they go to GitHub/GitLab you still want the safety of your CI/CD Pipeline running these tools again. That's because sometimes people forget to configure their Git hooks 🙃.

For our formatters, there is a special CI/CD mode that will throw an error when the formatting is incorrect.

```bash
python -m black . --check
python -m isort . --check
```

---

## Source Code Example 💻

You're all set with the tools you'll need to keep your code healthy. Remember the goal is to make our code bug free, and easy to maintain. **Flake8**, **Black**, and **isort** can help you get there.

Check out **packagecake** for an example of what we've covered here.

{% github sirfuzzalot/packagecake no-readme %}

Find out what type of cake your Python package is by running the following:

```
>>> python -m pip install packagecake
>>> python -m packagecake bake [your package name]
🍰
```
---

Cover Photo Credit: [Alexander Sinn](https://unsplash.com/@swimstaralex)