A structured approach to Python refactoring using characterization tests and type checking.
Install
mkdir -p .claude/skills/py-refactor && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/12306" && unzip -o skill.zip -d .claude/skills/py-refactor && rm skill.zipInstalls to .claude/skills/py-refactor
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Use when refactoring Python code, cleaning up legacy codebases, optimizing performance, enforcing type safety, or improving clean architecture in a FastAPI backendKey capabilities
- →Write characterization tests for existing behavior
- →Enforce type safety with mypy
- →Apply common refactoring moves
- →Optimize asynchronous I/O with `asyncio.gather`
- →Optimize SQLAlchemy queries with eager loading
- →Identify and fix dangerous patterns in legacy code
How it works
The skill applies safe refactoring patterns by first writing characterization tests, then running mypy, performing refactoring, and verifying changes. It also includes specific moves for FastAPI backends and incremental typing for legacy code.
Inputs & outputs
When to use py-refactor
- →Optimizing Python performance
- →Enforcing type safety
- →Cleaning up legacy codebases
- →Improving clean architecture
About this skill
Python Refactoring & Performance
Overview
Safe refactoring patterns, type safety enforcement, and performance optimization for Python/FastAPI backends.
Core principle: Never refactor without characterization tests. Never optimize without profiling. Always improve type coverage.
3-strikes rule: If the same refactoring approach fails 3 times, stop. The problem is architectural — escalate to system-design for a deeper review instead of thrashing.
Safe Refactoring Process
- Write characterization tests — capture current behavior
- Run mypy — baseline type errors
- Refactor — change structure, preserve behavior
- Verify — tests pass, mypy errors same or fewer
- Clean up — remove temporary tests if redundant
Architecture Enforcement
# Domain should not import infrastructure
grep -r "from src.infrastructure\|from sqlalchemy\|from fastapi" src/domain/
# Application should not import infrastructure
grep -r "from src.infrastructure\|from sqlalchemy" src/application/
Common Refactoring Moves
| Smell | Move |
|---|---|
| Fat router | Extract use case class |
| ABC with single impl | Use Protocol instead |
| Inheritance hierarchy | Composition with Protocol |
| Sync blocking calls | async with asyncio.gather |
| Untyped dict passing | Pydantic model or dataclass |
| God class | Split into focused services |
Protocol Over ABC
# Before: ABC (tight coupling)
from abc import ABC, abstractmethod
class UserRepository(ABC):
@abstractmethod
async def create(self, user: User) -> User: ...
# After: Protocol (structural typing)
from typing import Protocol
class UserRepository(Protocol):
async def create(self, user: User) -> User: ...
Async Optimization
# Bad: sequential I/O
users = await user_repo.list()
posts = await post_repo.list()
# Good: concurrent I/O
users, posts = await asyncio.gather(
user_repo.list(),
post_repo.list(),
)
SQLAlchemy Query Optimization
# N+1 detection: enable echo
engine = create_async_engine(url, echo=True)
# Fix N+1 with eager loading
from sqlalchemy.orm import selectinload, joinedload
stmt = select(UserModel).options(selectinload(UserModel.posts))
# joinedload for single related object
stmt = select(PostModel).options(joinedload(PostModel.author))
Performance Profiling
# py-spy for flamegraphs
py-spy record -o profile.svg --pid $(pgrep uvicorn)
# cProfile for function-level
python -m cProfile -o output.prof -m pytest tests/
mypy Strict Compliance
# Find remaining type errors
mypy src/ --strict 2>&1 | head -50
# Common fixes:
# - Add return type annotations
# - Replace Any with specific types
# - Add type: ignore[specific-error] with comment explaining why
Legacy Code Rescue
When working with legacy Python code — untyped, untested, messy:
Step 1: Characterize Before Touching
# Characterization test: capture current behavior as-is
def test_legacy_create_user_current_behavior():
"""Documents what legacy code ACTUALLY does — not what it should do."""
result = legacy_create_user({"email": "[email protected]"})
assert result["id"] is not None # whatever it currently returns
assert result["email"] == "[email protected]"
Never refactor untested code. Add characterization tests first.
Step 2: Add Types Incrementally
Don't try to type the whole codebase at once. Start with boundaries:
# 1. Type the public API first (routers, service interfaces)
async def create_user(body: CreateUserRequest) -> UserResponse: ...
# 2. Then type domain entities
@dataclass
class User:
id: UUID
email: str
name: str
# 3. Then type infrastructure (repos, clients)
class UserRepository(Protocol):
async def create(self, user: User) -> User: ...
# 4. Run mypy incrementally
mypy src/interfaces/ --strict # start here
mypy src/domain/ --strict # then here
mypy src/ --strict # goal
Step 3: Identify the Worst Offenders
Prioritize by risk:
- Bare
except:orexcept Exception:— hiding real errors - SQL string concatenation — SQL injection, fix immediately
- No input validation — user data flows unchecked into DB
- God files (500+ lines) — split by responsibility
- Sync blocking in async —
time.sleep(), sync DB calls in async handlers - Untyped dict passing — replace with Pydantic models or dataclasses
Step 4: Strangler Fig Pattern
# 1. Extract protocol from legacy code
class UserService(Protocol):
async def create(self, data: CreateUserInput) -> User: ...
# 2. Legacy class implements protocol (add type annotations)
class LegacyUserService:
async def create(self, data: CreateUserInput) -> User:
# existing messy code stays for now
...
# 3. New clean implementation
class CleanUserService:
def __init__(self, repo: UserRepository) -> None:
self._repo = repo
async def create(self, data: CreateUserInput) -> User:
user = User.from_input(data)
return await self._repo.create(user)
# 4. Swap via dependency injection
def get_user_service() -> UserService:
return CleanUserService(repo=PostgresUserRepository(session))
Step 5: Fix Dangerous Patterns
# Before: bare except hides bugs
try:
result = do_something()
except:
pass
# After: specific exceptions, proper logging
try:
result = do_something()
except ValueError as e:
logger.warning("Invalid input", error=str(e))
raise
except DatabaseError as e:
logger.error("Database failure", error=str(e))
raise
Step 6: Remove Dead Code
# Find dead code
uvx vulture src/
# Find unused imports
ruff check --select F401 .
Delete it. Git has history.
Chains
- REQUIRED: Use
superpowers:systematic-debuggingfor performance investigation - REQUIRED: Write characterization tests before any refactoring — no exceptions
- REQUIRED: Update CLAUDE.md with discovered gotchas and conventions (
claude-md) - Legacy codebases: Run
fullstack-healthcheckfirst to prioritize what to fix
When not to use it
- →When the same refactoring approach fails 3 times
- →When optimizing without profiling
- →When refactoring without characterization tests
Limitations
- →Requires characterization tests before refactoring
- →Requires profiling before optimizing
- →Architectural problems require escalation to `system-design`
How it compares
This skill emphasizes a test-driven and type-safe approach to refactoring, ensuring behavior preservation and incremental improvement, unlike ad-hoc code changes.
Compared to similar skills
py-refactor side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| py-refactor (this skill) | 0 | 4mo | Review | Intermediate |
| development-rules | 0 | 4mo | No flags | Intermediate |
| repo-testing | 0 | 4mo | Review | Intermediate |
| migrate | 1 | 5mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
development-rules
Pravin-surawase
Hard-learned development rules by domain — Python, FastAPI, React, Testing. Prevents the specific mistakes that caused 70+ audit findings across v0.21.0-v0.21.3.
repo-testing
ppenumatsa1
Use for test strategy, pytest execution patterns, and test-safe changes in this FastAPI reference architecture.
migrate
alirezarezvani
Migrate from Cypress or Selenium to Playwright. Use when user mentions "cypress", "selenium", "migrate tests", "convert tests", "switch to playwright", "move from cypress", or "replace selenium".
agent-implementer-sparc-coder
ruvnet
Agent skill for implementer-sparc-coder - invoke with $agent-implementer-sparc-coder
tdd-migrate
parcadei
TDD workflow for migrations - orchestrate agents, zero main context growth
kaizen
Peadarpol
Guide for continuous improvement, error proofing (Poka-Yoke), and standardization. Use this skill when the user wants to improve code quality, refactor, or discuss process improvements.