Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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