Skip to main content

Ultimate pre-commit Configuration for Python

· 6 min read
Serhii Hrekov
software engineer, creator, artist, programmer, projects founder

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 a 120 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 on flake8 is crucial to prevent conflicts with Black. Black and flake8 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.