Skip to main content
🛡️ Verified Technical Content: Written by Serhii Hrekov. | Last reviewed & updated in Git: July 21, 2026

Ultimate pre-commit Configuration for Python: Complete setup & Advanced practices

· 8 min read
Serhii Hrekov
Senior Software Engineer & System Architect specializing in Python, Web Systems, Cloud Infrastructure & Automation

Maintaining code consistency and preventing quality regressions across a development team can be challenging. Manually checking styles or reminding teammates about formatting imports leads to cognitive overhead and code reviews cluttered with style arguments.

By implementing pre-commit, you can automate code formatting, import sorting, stylistic linting, and static type checking directly into your Git commit lifecycle. This guide outlines the ultimate pre-commit configuration for Python, demonstrates how to centralize settings, and provides advanced optimization tips.


The pre-commit Tooling Pipeline

A standard Python automation pipeline consists of four key tools, each covering a specific quality layer:

  • Black: An opinionated, automatic code formatter that reformats files to conform to a strict style.
  • isort: Organizes and sorts import statements alphabetically, grouping them into standard library, third-party, and local sections.
  • Flake8: Inspects code for style guide violations, unused imports, undeclared variables, and complex anti-patterns.
  • Mypy: Evaluates variable type hints statically, preventing type bugs from slipping into production runtime.

Configuration file (.pre-commit-config.yaml)

To configure these tools, define a .pre-commit-config.yaml file in the root of your Git repository:

repos:
# 1. Clean code formatting (Black)
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
args: ["--line-length=120"]

# 2. Sorted imports (isort)
- 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"
]

# 3. Code style verification (Flake8)
- repo: https://github.com/pycqa/flake8
rev: 7.1.0
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear # Catches common programming bugs
- flake8-comprehensions # Enforces clean list/dict comprehensions
- flake8-pyproject # Allows loading options from pyproject.toml
args: [
"--max-line-length=120",
"--extend-ignore=E203,W503" # Ignore rules that conflict with Black formatting
]

# 4. Static type enforcement (Mypy)
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2
hooks:
- id: mypy
additional_dependencies: [
types-requests,
types-python-dateutil
]
args: [
"--ignore-missing-imports",
"--disallow-untyped-defs",
"--pretty"
]

# 5. Core hygiene checks
- 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

Configuration Guidelines

  • Unified Line Length Limit: Configured at 120 characters across Black, isort, and Flake8 to prevent conflicting edits.
  • Black and isort Compatibility: By passing --profile black to isort, it reformats imports using rules compatible with Black's output.
  • Flake8 Rule Overrides: Disabling rules like E203 (whitespace before colon) and W503 (line break before binary operator) is necessary, as Black uses these patterns natively.

Centralizing Tool Configurations (pyproject.toml)

Instead of cluttering your .pre-commit-config.yaml file with args, move your tool settings to pyproject.toml. This centralizes configurations so they are shared across your local CLI, IDE, pre-commit hooks, and CI/CD pipelines:

# pyproject.toml
[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

If you move settings here, you can remove the args blocks from .pre-commit-config.yaml. The tools will automatically discover these settings when executed by pre-commit.


Advanced pre-commit Optimization Tips

1. Pin Versions Explicitly

Always pin hook version tags using explicit releases in the rev: key. Avoid using branch names like master or main. Pinning ensures that every team member and CI pipeline runs the exact same code checks:

# CORRECT
rev: v4.5.0

# INCORRECT
rev: master

2. Configure Local Project Hooks

If you have project-specific scripts (such as validating .env template formats or checking API database migrations), define a local hook in .pre-commit-config.yaml to run them during the commit lifecycle:

  - repo: local
hooks:
- id: check-env-file
name: "Check local .env files"
entry: ./scripts/lint_env.sh
language: system
files: ^\.env$

3. Separate Expensive Hooks Using Stages

Some quality checks (like security scans, vulnerability audits, or test suites) take too long to run on every commit. Use the stages parameter to defer execution until pushes or manual runs:

  - repo: https://github.com/pycqa/bandit
rev: 1.7.9
hooks:
- id: bandit
stages: [push] # Only runs during git push

4. Running pre-commit in CI Pipelines

Devs can bypass local hooks by using the --no-verify flag. To ensure all merged code is formatted and linted properly, run pre-commit checks on your CI platform:

# GitHub Action Workflow snippet
- name: Run Pre-Commit Hooks
run: |
pip install pre-commit
pre-commit run --all-files

5. Caching Hook Installations in CI

Installing hook environments on every CI run slows down pipeline runtimes. Cache the local pre-commit directories using your CI's caching features:

# GitHub Actions Caching example
- uses: actions/cache@v3
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}

Installing and Executing Hooks

Set up your repository to run the checks automatically on every commit:

# 1. Install pre-commit hooks into your local Git hooks path
pre-commit install

# 2. Run all checks against all files to establish a clean baseline
pre-commit run --all-files

# 3. Update hook dependencies to their latest versions regularly
pre-commit autoupdate

Sources