The recording for this lesson has not been published yet.
Code style is not about beauty. It is about three very practical things:
except that swallows everything.| Tool | Job | Typical command |
|---|---|---|
black (or ruff format) |
Formatter - rewrites layout, never logic | black . |
| ruff | Linter - finds unused imports, bad practices, sorts imports | ruff check --fix . |
ruff is written in Rust and is fast enough that you stop noticing it. It also
ships its own formatter, ruff format, which is a drop-in replacement for
black - one tool instead of two. Pick either; just do not run two different
formatters over the same file.
pip install ruff black pre-commit
ruff check . # report problems
ruff check --fix . # fix what can be fixed automatically
ruff format . # format (or: black .)
Both tools read pyproject.toml from the repository root, so the settings
travel with the code and every contributor gets the same result.
# pyproject.toml
[tool.ruff]
line-length = 88
target-version = "py312"
exclude = ["migrations", ".venv", "staticfiles"]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"F", # pyflakes
"I", # isort - import order
"UP", # pyupgrade - modern syntax
"B", # flake8-bugbear - likely bugs
"DJ", # flake8-django - Django specific rules
]
ignore = ["E501"] # the formatter already handles line length
[tool.ruff.lint.isort]
known-first-party = ["core", "blog", "accounts", "comments"]
[tool.black]
line-length = 88
extend-exclude = "migrations"
The DJ rules are worth enabling on a Django project: they catch
null=True on a CharField, a model without
__str__, and exclude in a ModelForm.
Running the commands by hand works exactly until the day you forget. A git hook does not
forget. pre-commit installs one for you and runs the checks on the files you
are about to commit.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- id: detect-private-key
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.9
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaks
pre-commit install # writes .git/hooks/pre-commit
pre-commit run --all-files # first run: fixes the whole repository
pre-commit autoupdate # bump the hook versions from time to time
detect-private-key and gitleaks. After lesson 1.2 you know
why: the cheapest moment to stop a secret is before it enters the history.
The first run will touch almost every file. Keep that churn out of your feature branches:
git switch -c chore/formatting
pre-commit run --all-files
git add -A
git commit -m "chore: apply ruff and formatter to the whole project"
Then tell git blame to skip that commit, so it keeps pointing at the person
who wrote the logic rather than the person who ran the formatter:
# .git-blame-ignore-revs
# formatting-only commit
a1b2c3d4e5f60718293a4b5c6d7e8f9012345678
git config blame.ignoreRevsFile .git-blame-ignore-revs
A hook lives on a developer's machine and can be bypassed with
git commit --no-verify. The real gate is continuous integration, which we set
up in lesson 8.5:
# .github/workflows/ci.yml (preview of module 8)
name: CI
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install pre-commit
- run: pre-commit run --all-files
pyproject.toml with the ruff configuration above and run
ruff check .. Read every reported rule before you fix it.pre-commit and run it over the whole repository in a separate
formatting commit.DJ rules and see what ruff thinks about the models you wrote
during the Django Girls tutorial.