Code Quality Tools Competent¶
🧪 Testing Track · Level 2
When you'd use this
ruff, mypy, black, isort, pre-commit hooks and enforcing standards.
Enforce consistent, correct code automatically with linters, formatters, and type checkers (ruff, black, mypy) in CI.
The modern Python quality stack¶
The tools that keep a codebase consistent and correct — linter, formatter, type checker, pre-commit — working together.
| Tool | Purpose | Speed |
|---|---|---|
| ruff | Linter + formatter (replaces flake8, isort, black) | Extremely fast |
| mypy | Static type checker | Moderate |
| black | Opinionated code formatter | Fast |
| isort | Import sorter | Fast |
| bandit | Security linter | Fast |
| pre-commit | Git hooks to run tools automatically | — |
ruff — the all-in-one tool¶
An extremely fast linter and formatter that replaces flake8/isort/black for most projects.
pip install ruff
# Lint (find issues)
ruff check .
ruff check --fix . # auto-fix what's possible
# Format (replace black + isort)
ruff format .
ruff format --check . # check only, don't modify
Configuration:
# pyproject.toml
[tool.ruff]
target-version = "py313"
line-length = 100
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
"SIM", # flake8-simplify
"TCH", # type-checking imports
"RUF", # ruff-specific rules
]
ignore = ["E501"] # line too long (handled by formatter)
[tool.ruff.lint.isort]
known-first-party = ["mypackage"]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
Common ruff fixes:¶
# Before ruff
from typing import List, Dict, Optional
import os
import sys
from pathlib import Path
x = dict()
y = list()
if type(x) == dict:
pass
# After ruff --fix
import os
import sys
from pathlib import Path
x = {}
y = []
if isinstance(x, dict):
pass
# typing imports removed (use built-in types in 3.9+)
# imports sorted
# dict()/list() → {}/[]
# type() == → isinstance()
mypy — static type checking¶
Catches type mismatches before runtime by checking your annotations; run it in CI.
# pyproject.toml
[tool.mypy]
python_version = "3.13"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
Common errors and fixes:
# Error: Function is missing a return type annotation
def process(data): # ← add -> ReturnType
...
# Fix:
def process(data: list[dict]) -> list[str]:
...
# Error: Incompatible return value type (got "Optional[str]", expected "str")
def get_name(user) -> str:
return user.get("name") # might be None!
# Fix:
def get_name(user) -> str:
return user.get("name", "Unknown")
pre-commit — automate on every commit¶
Run linters/formatters/checks automatically on staged files so bad code never gets committed.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.0
hooks:
- id: mypy
additional_dependencies: [types-requests]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
pre-commit install # set up git hooks
pre-commit run --all-files # run on everything now
# After this, tools run automatically on every git commit
CI integration¶
Enforce the whole quality stack on every push/PR so standards hold across the team.
# .github/workflows/quality.yml
name: Quality
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.13" }
- run: pip install ruff mypy
- run: ruff check .
- run: ruff format --check .
- run: mypy src/
Practice Exercises¶
- Set up ruff on an existing project and fix all reported issues.
- Add mypy with
--strictand fix all type errors. - Configure pre-commit with ruff + mypy and verify it blocks bad commits.
- Create a CI pipeline that runs lint, format check, type check and tests.
- Write a custom ruff rule using
pyproject.tomlconfiguration.
💬 Discussion
Have a question about this topic? Found an error? Share your thoughts below.