Refactoring Our Codebase with Ruff and Pre-commit: A Developer's Guide

The Challenge We Faced Our FastAPI menu service had grown rapidly, leading to inconsistent code quality across the team. Print statements were scattered everywhere, error handling was inconsistent, and code reviews spent too much time on formatting issues. # Bad practices we needed to fix def process_order(order_id): try: print(f"Processing order {order_id}") result = api.get_order(order_id) print(result) return result except Exception as e: print(f"Error: {e}") # No logging, lost when deployed raise HTTPException(status_code=500, detail="Failed") # Lost original exception The Solution: Ruff + Pre-commit We chose Ruff for its speed (100x faster than flake8) and auto-fixing capabilities, combined with pre-commit for automation. This powerful combination enforces code quality before code even reaches the repository. 1. Setting Up the Tools First, we added the necessary dependencies: pip install ruff==0.11.0 pre-commit==4.1.0 Then created two key configuration files: 2. Configure Ruff (pyproject.toml) [tool.ruff.lint] select = ["E", "F", "B", "I"] fixable = ["ALL"] ignore = ["B008"] # Ignore Depends() warnings for FastAPI [tool.ruff.lint.isort] known-third-party = ["fastapi", "sqlmodel", "pydantic"] 3. Set up Pre-commit (.pre-commit-config.yaml) repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.11.0 hooks: - id: ruff args: [--fix] - id: ruff-format - 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 A simple pre-commit install command enabled the hooks 4. Install Pre-commit Hook pre-commit install Real-World Examples Before/After: Proper Exception Handling # Before try: data = crud.read(model_name=Menu, id=menu_id) except Exception as e: print(f"DB error: {e}") raise HTTPException(status_code=500, detail="Database error") # After (with Ruff fixes) try: data = crud.read(model_name=Menu, id=menu_id) except Exception as e: logger.error(f"DB error: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail="Database error") from e Before/After: Import Sorting # Before import random from fastapi import APIRouter import logging from typing import Optional from app.models import Menu # After (auto-fixed by Ruff) import logging import random from typing import Optional from fastapi import APIRouter from app.models import Menu Handling Failed Checks When you see this error: trim trailing whitespace.................................................Failed - hook id: trailing-whitespace - files were modified by this hook Fixing .github/workflows/ci-test.yml Simply add the changes and retry: git add . git commit -m "Your message" # Try again CI Integration We updated our GitHub workflow to use Ruff: # .github/workflows/ci-test.yml jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: '3.11' - run: pip install -r requirements.txt - run: ruff check . Manual Fixes Check specific files: # Find issues ruff check app/routes/ # Auto-fix issues ruff check --fix app/routes/ # Run all pre-commit hooks manually pre-commit run --all-files Results: The Numbers Tell the Story Our refactoring achieved impressive results: 143 lines removed 198 lines added 20 files modified 0 print statements remaining 100% consistent exception handling More importantly, our team now spends less time on formatting issues and more time on application logic. Debugging production issues has become significantly easier with proper logging and exception chaining. The most valuable outcome? New developers can contribute clean, consistent code from day one without memorizing all our style conventions. The tools do the heavy lifting automatically. This refactoring wasn't just about prettier code—it was about building a foundation for better software engineering practices that will serve us well as our codebase continues to grow.

Apr 19, 2025 - 06:05
 0
Refactoring Our Codebase with Ruff and Pre-commit: A Developer's Guide

The Challenge We Faced

Our FastAPI menu service had grown rapidly, leading to inconsistent code quality across the team. Print statements were scattered everywhere, error handling was inconsistent, and code reviews spent too much time on formatting issues.

# Bad practices we needed to fix
def process_order(order_id):
    try:
        print(f"Processing order {order_id}")
        result = api.get_order(order_id)
        print(result)
        return result
    except Exception as e:
        print(f"Error: {e}")  # No logging, lost when deployed
        raise HTTPException(status_code=500, detail="Failed")  # Lost original exception

The Solution: Ruff + Pre-commit

We chose Ruff for its speed (100x faster than flake8) and auto-fixing capabilities, combined with pre-commit for automation. This powerful combination enforces code quality before code even reaches the repository.

1. Setting Up the Tools

First, we added the necessary dependencies:

pip install ruff==0.11.0 pre-commit==4.1.0

Then created two key configuration files:

2. Configure Ruff (pyproject.toml)

[tool.ruff.lint]
select = ["E", "F", "B", "I"]
fixable = ["ALL"]
ignore = ["B008"]  # Ignore Depends() warnings for FastAPI

[tool.ruff.lint.isort]
known-third-party = ["fastapi", "sqlmodel", "pydantic"]

3. Set up Pre-commit (.pre-commit-config.yaml)

repos:
-   repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.11.0
    hooks:
    -   id: ruff
        args: [--fix]
    -   id: ruff-format

-   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

A simple pre-commit install command enabled the hooks

4. Install Pre-commit Hook

pre-commit install

Real-World Examples

Before/After: Proper Exception Handling

# Before
try:
    data = crud.read(model_name=Menu, id=menu_id)
except Exception as e:
    print(f"DB error: {e}")
    raise HTTPException(status_code=500, detail="Database error")

# After (with Ruff fixes)
try:
    data = crud.read(model_name=Menu, id=menu_id)
except Exception as e:
    logger.error(f"DB error: {str(e)}", exc_info=True)
    raise HTTPException(status_code=500, detail="Database error") from e

Before/After: Import Sorting

# Before
import random
from fastapi import APIRouter
import logging
from typing import Optional
from app.models import Menu

# After (auto-fixed by Ruff)
import logging
import random
from typing import Optional

from fastapi import APIRouter

from app.models import Menu

Handling Failed Checks

When you see this error:

trim trailing whitespace.................................................Failed
- hook id: trailing-whitespace
- files were modified by this hook

Fixing .github/workflows/ci-test.yml

Simply add the changes and retry:

git add .
git commit -m "Your message"  # Try again

CI Integration

We updated our GitHub workflow to use Ruff:

# .github/workflows/ci-test.yml
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - run: pip install -r requirements.txt
      - run: ruff check .

Manual Fixes

Check specific files:

# Find issues
ruff check app/routes/

# Auto-fix issues
ruff check --fix app/routes/

# Run all pre-commit hooks manually
pre-commit run --all-files

Results: The Numbers Tell the Story

Our refactoring achieved impressive results:

  • 143 lines removed
  • 198 lines added
  • 20 files modified
  • 0 print statements remaining
  • 100% consistent exception handling

More importantly, our team now spends less time on formatting issues and more time on application logic. Debugging production issues has become significantly easier with proper logging and exception chaining.

The most valuable outcome? New developers can contribute clean, consistent code from day one without memorizing all our style conventions. The tools do the heavy lifting automatically.

This refactoring wasn't just about prettier code—it was about building a foundation for better software engineering practices that will serve us well as our codebase continues to grow.