Skip to content
Dev Tools Beginner Tutorial

Lint, Format, and Test Python on Every Commit with pre-commit and Ruff

Wire up pre-commit hooks so broken or unformatted Python is caught automatically before it ever reaches your branch.

Mariana Souza
Mariana Souza
Senior Editor · Jun 14, 2026 · 5 min read

What You'll Build

You'll configure pre-commit so that every git commit automatically runs Ruff (lint + format) and your pytest suite — blocking bad commits before they leave your machine.

Prerequisites

  • Python 3.8+ — check with python --version
  • Git with an initialized repo (git init)
  • pip (bundled with Python 3.4+)
  • A virtual environment activated (strongly recommended)
  • Works on macOS, Linux, and Windows

1. Install the Tools

pip install pre-commit ruff pytest

Verify the installs:

pre-commit --version   # e.g. pre-commit 3.7.0
ruff --version         # e.g. ruff 0.4.4

2. Create the Hook Configuration

Create .pre-commit-config.yaml in your repo root:

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.4.4
    hooks:
      - id: ruff          # linter; --fix applies safe auto-fixes
        args: [--fix]
      - id: ruff-format   # formatter (replaces Black)

  - repo: local
    hooks:
      - id: pytest
        name: pytest
        entry: pytest
        language: system
        pass_filenames: false
        always_run: true

Tip: Replace v0.4.4 with the latest tag from github.com/astral-sh/ruff-pre-commit/releases, or run pre-commit autoupdate after setup.

The local hook with language: system uses the Python that's currently active in your shell, so it picks up your project's installed dependencies automatically.

3. Install the Git Hooks

pre-commit install

Expected output:

pre-commit installed at .git/hooks/pre-commit

This writes a small script into .git/hooks/pre-commit that fires automatically on every git commit.

4. Add a Python File and a Test

Create calculator.py:

def add(a, b):
    return a + b

Create test_calculator.py:

from calculator import add

def test_add():
    assert add(2, 3) == 5

5. Stage and Commit

git add calculator.py test_calculator.py .pre-commit-config.yaml
git commit -m "Add calculator with pre-commit hooks"

The first run downloads the Ruff hook environment from GitHub (one-time, ~10 seconds). If all checks pass you'll see green Passed lines and the commit completes.

Verify It Works

Introduce a deliberate lint error — an unused import:

import os  # unused

def add(a, b):
    return a + b

Then try to commit:

git add calculator.py
git commit -m "test lint blocking"

Expected output (commit is blocked):

ruff.....................................................................Failed
- hook id: ruff
- exit code: 1

calculator.py:1:1: F401 `os` imported but unused

Because you passed --fix, Ruff removes safe issues automatically. Re-stage the corrected file and commit again to proceed.

Troubleshooting

Symptom Cause Fix
pre-commit: command not found pre-commit not on PATH Activate your venv, or install globally with pipx install pre-commit
ruff-format hook not recognized Pinned rev is too old (pre-v0.2.0) Run pre-commit autoupdate to bump revisions
pytest can't import your modules venv not active when committing Always commit from an active venv; language: system depends on it
Hooks run slowly on large repos First run downloads + caches environments Subsequent runs use the cache and are fast

Next Steps

  • pre-commit autoupdate — bumps all hook rev pins to their latest releases in one command.
  • Ruff configuration — add a [tool.ruff.lint] section in pyproject.toml to enable extra rule sets (e.g., select = ["E", "F", "I"] adds isort-style import sorting).
  • CI parity — run pre-commit run --all-files in GitHub Actions or your CI pipeline to catch issues on pull requests even if someone bypassed local hooks with git commit --no-verify.
  • Type checking — once you're comfortable here, add a mypy or pyright local hook for static type analysis.
Mariana Souza
Written by
Mariana Souza · Senior Editor

Mariana covers the fast-moving world of machine learning and generative AI, with a particular focus on how these technologies are reshaping development workflows. When she isn't stress-testing the latest foundation models, she's usually at a local hackathon.

Discussion 0

Join the discussion

Sign in or create an account to comment and vote.

No comments yet

Be the first to weigh in.

Related Reading