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.

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.