Typechecking, linting and formatting
We are now going to see other parts of the development that be automated using GH Actions in order improve the quality of the code base. We will continue with Python and look at some modern and common tools used popular projects.
Formatting with black
How your code is formatted matters for the readability. This is a hot topic and people have different styles and preferences. When working together however, it is often more important that the style is coherent than which exact style use.
In Python some of the most popular formatters are:
We will be looking using Black
since this is the mostly commonly used
Black - The Uncompromising Code Formatter
Black is the uncompromising Python code formatter. By using it, you agree to cede control over minutiae of hand-formatting. In return, Black gives you speed, determinism, and freedom from pycodestyle nagging about formatting. You will save time and mental energy for more important matters.
Let us create a module formatting.py
import numpy as np
def the_abc_func(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z):
pass
a_short_list = [
"an element"
]
a_long_list = ["an element", "an element", "an element" "an element", "an element", "an element", "an element", "an element"]
Now we format at see what happens
Adding black
to workflow
To add black
to the workflow we need to add it to the packages being
pip
-installed as well as create a new new step.
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Python application
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest black
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Test with pytest
run: |
pytest .
- name: Formatting with black
run: |
black formatting.py --check --diff
Formatting on push
It is also possible to setup for your code to be automatically formatted when pushed to the repository. You can read more about that here.
Linting with flake8
Linting is a form of static code analysis that looks for and warns about code smells, possible errors and bad style in your code. You have probably seen this already if you are using a modern IDE like vscode or Pycharm with a built-in language server.
A popular linter for Python that can be used directly from the command line
(It can also be integrated into an IDE) is
Flake8
. Let us create a new file
called linting.py
in the same directory we used for automated testing. You
can find all the error/violation codes
here
import numpy as np # F401
from math import * # F403
if True == False: #
x = sqrt(9)
break # F701
Static type-checking with mypy
Python is an interpreted language, however, as of PEP
484 it supports Type Hints. Some very
popular packages like pydantic
even
use type hints to evaluate input at runtime.
We can now evaluate if there are any type errors using mypy
.
Benefits of Type Hints
- Improves readability (I think)
- Easier to spot bugs and maintain project
- Forces you to think
- Better autocomplete if using language server
Final workflow
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Python application
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest black mypy
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Test with pytest
run: |
pytest .
- name: Formatting with black
run: |
black formatting.py --check --diff
- name: Linting with flake8
run: |
flake8 linting.py
- name: Type checking with mypy
run: |
mypy type_checking.py
Automated refactoring with sourcery
(Bonus)
sourcery
is a new and interesting tool that is able to refactor the code to
make it more pythonic. Since it requires that you sign up for an account, I
will just quickly show its functionality. However, it can be added as a GitHub
action and just like with black
it can be used to automatically refactor the
when you push. You can read more about this here.
# assign-if-exp
condition = True
if condition:
x = 1
else:
x = 2
# Augmented assign
count = 2
other_value = 2
count = count + other_value
# Chain compares
b = 2
if 1 < b and b < 3:
print("b is between 1 and 3")
# convert-any-to-in
hats = ["basker", "cap", "bowler"]
if any(hat == "bowler" for hat in hats):
print("I have a bowler hat!")
We can check if any refactoring can be done using sourcery
Where to go from here
If you are interested in learning more tools and ways to improve the
quality of your project, a really good place to start is looking at
cookicutter templates. Two great ones for Python
are: