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 a120character line length. This single setting ensures consistency across your entire toolchain. - Black & isort Integration: By setting
isort's--profileto"black", you ensure that the two formatters do not conflict. - Flake8 Compatibility: The
--extend-ignoreflag onflake8is crucial to prevent conflicts withBlack.Blackandflake8have some rules that can clash, and these ignores handle the most common ones. - Mypy Customization: The
mypyargs like--ignore-missing-importsand--disallow-untyped-defsare 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.
