Skip to content

DevX: Add pre-commit hooks to catch formatting/linting issues early #127

@dougborg

Description

@dougborg

Summary

Add pre-commit hooks to automatically catch formatting, linting, and other code quality issues before commit, preventing CI failures like the one seen in PR #125 (Black formatting issues).

Problem

Current State:

  • Developers can commit code without running formatters/linters
  • CI catches issues after PR creation (wasting time and CI resources)
  • Example: PR Add static file infrastructure for generated clients #125 failed all CI jobs due to Black formatting issues
  • Contributors need to manually remember to run:
    • poetry run black .
    • poetry run ruff check .
    • poetry run ty check .

Pain Points:

  • ❌ Formatting issues discovered in CI (after push)
  • ❌ Failed CI runs waste resources and time
  • ❌ Broken commits in git history
  • ❌ PR review cycle delayed by fixup commits

Solution

Add pre-commit hooks to run checks automatically before each commit:

  • ✅ Black formatting
  • ✅ Ruff linting
  • ✅ Type checking (ty)
  • ✅ Trailing whitespace/EOL fixes
  • ✅ YAML/JSON validation
  • ✅ Large file detection

Benefits:

  • Issues caught before commit (not in CI)
  • Clean git history (no "fix formatting" commits)
  • Faster PR review cycles
  • Reduced CI resource usage
  • Better developer experience

Implementation

1. Add .pre-commit-config.yaml

# See https://pre-commit.com for more information
repos:
  # General fixes
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-json
      - id: check-added-large-files
      - id: check-merge-conflict
      - id: check-toml
      - id: mixed-line-ending
  
  # Python formatting
  - repo: https://github.com/psf/black
    rev: 23.12.1
    hooks:
      - id: black
        language_version: python3.11
  
  # Python linting
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.1.9
    hooks:
      - id: ruff
        args: [--fix]
  
  # Type checking (optional - can be slow)
  - repo: local
    hooks:
      - id: ty-check
        name: ty type check
        entry: poetry run ty check
        language: system
        types: [python]
        pass_filenames: false
        stages: [manual]  # Run only on demand: pre-commit run --hook-stage manual

2. Update pyproject.toml

Add pre-commit to dev dependencies:

[tool.poetry.group.dev.dependencies]
pre-commit = "^3.6.0"
# ... existing dev deps

3. Update CI Workflow

Option A: Keep full CI checks (redundant but thorough)

# No changes - CI runs everything as backup

Option B: Skip formatting checks in CI (pre-commit handles it)

- name: Check code formatting
  run: echo "Skipping - handled by pre-commit hooks"

4. Update Documentation

README.md:

## Development Setup

1. Install dependencies:
   ```bash
   poetry install
  1. Install pre-commit hooks:

    poetry run pre-commit install
  2. (Optional) Run hooks manually:

    poetry run pre-commit run --all-files

**CONTRIBUTING.md**:
```markdown
### Pre-commit Hooks

This project uses pre-commit hooks to ensure code quality. Hooks run automatically before each commit.

**First-time setup:**
```bash
poetry run pre-commit install

Skip hooks (not recommended):

git commit --no-verify

Run hooks manually:

poetry run pre-commit run --all-files

## Example Workflow

**Before (Current):**
```bash
# Developer makes changes
git add .
git commit -m "Add feature"
git push

# CI fails due to formatting
# Developer fixes formatting
git add .
git commit -m "Fix formatting"
git push

# CI passes (2 commits, wasted time)

After (With Pre-commit):

# Developer makes changes
git add .
git commit -m "Add feature"

# Pre-commit runs automatically:
# - Black formats code
# - Ruff fixes lint issues
# - Files are automatically updated

# Developer reviews auto-fixes
git add .
git commit -m "Add feature"
git push

# CI passes (1 clean commit)

Configuration Options

Fast Mode (Recommended)

Run only fast checks automatically:

  • ✅ Black (formatting) - ~1-2s
  • ✅ Ruff (linting) - ~0.5-1s
  • ⏭️ Type checking - manual only (pre-commit run --hook-stage manual)

Strict Mode

Run all checks including type checking:

  • May slow down commits (type checking can take 5-10s)
  • Optional: configure ty to run only on push, not commit

Compatibility

With uv (#126)

Pre-commit works seamlessly with uv:

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: ruff
        name: ruff
        entry: uv run ruff check --fix
        language: system
        types: [python]

With Existing Workflows

  • ✅ Works alongside CI (redundant checks ok)
  • ✅ Optional (developers can skip with --no-verify)
  • ✅ Can be run manually (pre-commit run --all-files)

Implementation Plan

  • Phase 1: Setup

    • Add .pre-commit-config.yaml with basic hooks
    • Add pre-commit to dev dependencies
    • Test locally with pre-commit run --all-files
  • Phase 2: Documentation

    • Update README.md with setup instructions
    • Update CONTRIBUTING.md with pre-commit workflow
    • Add troubleshooting guide
  • Phase 3: CI Integration

    • Add pre-commit CI check (or remove redundant checks)
    • Ensure CI fails if hooks weren't run
  • Phase 4: Team Onboarding

    • Announce in PR/issue
    • Provide migration guide for existing contributors
    • Offer help/support for setup issues

Edge Cases

Large Codebase

Pre-commit only runs on staged files (fast):

# Only checks files you modified
git add file.py
git commit  # Checks only file.py

Automated Fixes

Some hooks auto-fix issues:

  • Black reformats code automatically
  • Ruff can fix many lint issues with --fix
  • Files are updated but not staged (review before committing)

Skipping Hooks

When needed (emergencies, WIP commits):

git commit --no-verify -m "WIP: work in progress"

Success Metrics

Expected Improvements:

  • ❌ → ✅ Zero formatting-related CI failures
  • ⏱️ Reduced time from PR creation to merge
  • 📉 Fewer "fix formatting" commits in history
  • 😊 Better developer experience

Real Example:

Related Issues

Resources

Labels

  • enhancement
  • devx (developer experience)
  • tooling

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions