Photo by Nick Morrison on Unsplash
pre-commit your Django projects
Improve your code quality by using simple scripts
Updated 08/02/2024: Updated example dependencies, use Ruff instead of flake8 and pylint
Pre-commit is a python based tool to enable easy integration of git hooks, and it's supported by plenty of tools like ruff, black, ...
While coding a Django project in a team, you would like to have unified rules and styles to avoid unnecessary changes on each Pull Request another team member will review. After setting up for the first time, this is for me a must for every project to ensure consistency and code quality.
After installing pre-commit, you will need to create a .pre-commit-config.yaml
file and run pre-commit install
inside the project to activate the git hooks.
Default pre-commit hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 24.1.1
hooks:
- id: black
Check this link for a full list of builtins hooks from pre-commit.
isort
The initial example contains configuration of python Black formatting, on top of it I strongly recommend using consistent sorted import via isort
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
If you use isort with black you will need to use black profile as follow in pyproject.toml
in your root project directory.
[tool.black]
line-length = 88
include = '\.pyi?$'
[tool.isort]
profile = "black"
py_version=311
multi_line_output = 3
line_length = 88
default_section = "THIRDPARTY"
skip = ["migrations"]
Pyupgrade
Another amazing tool you might need to use is pyupgrade, this one will ensure you will use new python feature like new style classes, removing unicode literals, use the python3 super() calls etc.
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
hooks:
- id: pyupgrade
args: ["--py3-plus", "--py311-plus"]
If you use python 3.10 you can change --py39-plus
with --py310plus
Djangoupgrade
Next similar tools is djangoupgrade, it will fix deprecated features of Django and make it easy to upgrade your Django projects, unfortunately is does only check against python files, for Django templates it's not doing anything, but I still find it useful.
- repo: https://github.com/adamchainz/django-upgrade
rev: "1.15.0"
hooks:
- id: django-upgrade
args: [--target-version, "5.0"]
Note the target-version, this depend on the current supported Django version in the project.
Bandit
Another great tool to use is Bandit, Bandit is designed to find common security issues in Python code. To do this Bandit processes each file, builds an AST from it, and runs appropriate plugins against the AST nodes. Once Bandit has finished scanning all the files it generates a report.
- repo: https://github.com/pycqa/bandit
rev: 1.7.7
hooks:
- id: bandit
args: ["-iii", "-ll"]
Ruff
Ruff is a powerful Python tool that consolidates various code quality checkers like pycodestyle, pyflakes, and mccabe, along with third-party plugins. It ensures the style and quality of Python code through a unified interface.
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: "v0.2.1"
hooks:
- id: ruff
Now we will need to setup Ruff config in pyproject.toml
[tool.ruff]
line-length = 88
target-version = "py311"
exclude = [
".git",
".mypy_cache",
".pre-commit-cache",
".ruff_cache",
".tox",
".venv",
"venv",
"docs",
"__pycache",
"**/migrations/*",
]
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.lint.mccabe]
max-complexity = 10
To run ruff manually, once installed (e.g pip install ruff
)
ruff check .
Final result
The final .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 22.8.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
hooks:
- id: pyupgrade
args: ["--py3-plus", "--py39-plus"]
- repo: https://github.com/adamchainz/django-upgrade
rev: "1.15.0"
hooks:
- id: django-upgrade
args: [--target-version, "4.0"]
- repo: https://github.com/pycqa/bandit
rev: 1.7.7
hooks:
- id: bandit
args: ["-iii", "-ll"]
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: "v0.2.1"
hooks:
- id: ruff
Bonus: CI integration
To make sure no one skip the pre-commit setup, either by intention of if someone forget to install the hooks locally after cloning the project for the first time, you can run pre-commit run --all
in your CI.
Caching might be tricky and useful as initial environment setup take some time and therefore you can use the PRE_COMMIT_HOME
for specifying which directory is used for the cache
Gitlab CI
precommit:
image:
name: python:3.10
stage: test
variables:
PRE_COMMIT_HOME: ".cache/.pre-commit-cache/"
script:
- pip install pre-commit
- pre-commit run --all
rules:
- exists:
- ".pre-commit-config.yaml"
interruptible: true
cache:
key: $CI_PROJECT_NAME-precommit-cache-v1
paths:
- .cache/.pre-commit-cache/
Github Actions
name: CI
on:
create:
tags:
- "*" # run on all tags
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.11
- name: Install pre-commit
run: pip install pre-commit
- name: Cache pre-commit hooks
id: pre-commit-cache
uses: actions/cache@v2
with:
path: $HOME/.cache/pre-commit
key: ${{ runner.os }}-pre-commit-hooks-${{ hashFiles('**/.pre-commit-config.yaml') }}
restore-keys: |
${{ runner.os }}-pre-commit-hooks-
- name: Run pre-commit
run: |
pre-commit run --all-files