Ultimate pre-commit Configuration for Python
Maintaining a clean and consistent codebase is crucial for any successful project. Enforcing standards across your team can be challenging, but with pre-commit
, you can automate this process directly into your development workflow. This guide outlines a powerful pre-commit
configuration that combines formatting, linting, and static type checking to ensure your code is always in top shape.
The pre-commit
Toolkit: What Each Tool Does
Our setup uses a combination of four essential tools, each with a distinct purpose:
- Black: The uncompromising code formatter. It automatically restructures your code to adhere to a strict style, eliminating style-related debates and making your code instantly consistent.
- isort: The import sorter. It organizes your imports alphabetically and separates them into logical sections, which makes your import statements clean and readable.
- Flake8: The style guide enforcement tool. It checks for style violations, common programming errors, and bug-prone patterns, helping you catch potential issues early.
- Mypy: The static type checker. It analyzes your code for type inconsistencies and errors without running the code, which helps you prevent a whole class of bugs before they ever make it to runtime.
The pre-commit-config.yaml
Configuration
This is the core of your setup. The .pre-commit-config.yaml
file defines which hooks to run and how to configure them.
repos:
# Black formatter
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
args: ["--line-length=120"]
# isort for import sorting (synced with VS Code args)
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
args: [
"--profile", "black",
"--line-length", "120",
"--py", "39",
"--atomic",
"--trailing-comma",
"--multi-line", "3",
"--lines-after-imports", "2",
"--force-alphabetical-sort-within-sections"
]
# flake8 linting
- repo: https://github.com/pycqa/flake8
rev: 7.1.0
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear # catches common Python bugs
- flake8-comprehensions # checks list/dict/set comprehensions
- flake8-pyproject # allow config in pyproject.toml
args: [
"--max-line-length=120",
"--extend-ignore=E203,W503" # compatible with Black
]
# mypy type checker
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2
hooks:
- id: mypy
additional_dependencies: [
types-requests, # example: add stubs for requests
types-python-dateutil
]
args: [
"--ignore-missing-imports",
"--disallow-untyped-defs",
"--pretty"
]
# Pre-commit hygiene hooks
- 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
- id: check-added-large-files
Key Configuration Points
- Unified Line Length: All tools (
black
,isort
,flake8
) are configured to a120
character line length. This single setting ensures consistency across your entire toolchain. - Black & isort Integration: By setting
isort
's--profile
to"black"
, you ensure that the two formatters do not conflict. - Flake8 Compatibility: The
--extend-ignore
flag onflake8
is crucial to prevent conflicts withBlack
.Black
andflake8
have some rules that can clash, and these ignores handle the most common ones. - Mypy Customization: The
mypy
args like--ignore-missing-imports
and--disallow-untyped-defs
are excellent starting points. You can adjust these to match your team's specific type-checking strictness.
Centralizing Configurations with pyproject.toml
For true consistency, it's highly recommended to move tool-specific arguments from .pre-commit-config.yaml
to pyproject.toml
. This makes your configurations sharable across your entire development environment, including your IDE, CI/CD pipeline, and your local pre-commit
setup.
[tool.black]
line-length = 120
[tool.isort]
profile = "black"
line_length = 120
multi_line_output = 3
include_trailing_comma = true
lines_after_imports = 2
force_alphabetical_sort_within_sections = true
[tool.flake8]
max-line-length = 120
extend-ignore = ["E203", "W503"]
[tool.mypy]
ignore_missing_imports = true
disallow_untyped_defs = true
pretty = true
This is the best practice for modern Python development. Your pre-commit
hooks can then simply run without arguments, as the tools will automatically discover and use the settings in pyproject.toml
.
Next Steps: Installing and Running
Once your configuration files are in place, all that's left is to install and run the hooks.
# Install the pre-commit hooks into your Git repository
pre-commit install
# To check all files in your repository, not just staged ones
pre-commit run --all-files
# To ensure all your hooks are using the latest versions
pre-commit autoupdate
This setup gives you a robust and automated system for maintaining code quality, which is a powerful asset for any software project.