"Use when splitting a Python module (>400 LOC) into a package. mock.patch target update rules and checklist."
Install
npx skillscat add shimo4228/claude-code-learned-skills/python-module-to-package-refactor Install via the SkillsCat registry.
SKILL.md
Python Module-to-Package Refactoring with Patch Target Updates
Extracted: 2026-02-09
Context: Splitting a monolithic Python module into a package while maintaining test compatibility
Problem
When refactoring module.py (700+ LOC) into module/ package with sub-modules, unittest.mock.patch() targets in tests break because they still point to the old flat module path. __init__.py re-exports fix regular imports but NOT patch() targets.
Solution
Rule: patch() must target where the name is LOOKED UP, not where it's defined or re-exported
Three cases to handle:
1. Function called directly within its own module
# critique.py defines _call_critique_api() and critique_cards() calls it
# OLD: patch("pkg.quality._call_critique_api")
# NEW: patch("pkg.quality.critique._call_critique_api")2. Function imported by another sub-module
# pipeline.py does: from .critique import critique_cards
# To mock critique_cards inside run_quality_pipeline():
# OLD: patch("pkg.quality.critique_cards")
# NEW: patch("pkg.quality.pipeline.critique_cards") # where it's looked up!3. Third-party module references
# critique.py does: import anthropic
# OLD: patch("pkg.quality.anthropic.Anthropic")
# NEW: patch("pkg.quality.critique.anthropic.Anthropic")Checklist for safe refactoring
- Create package directory and sub-modules (bottom-up: leaf deps first)
- Create
__init__.pywith re-exports (preservesfrom pkg.module import X) - grep all test files for
patch("pkg.module.and update targets - Delete old
module.py - Run full test suite (not just the module's tests - e2e tests often have patches too)
Example
# Before: quality.py (706 LOC)
# After:
quality/
__init__.py (re-exports)
heuristic.py (scoring)
duplicate.py (similarity)
critique.py (LLM API)
pipeline.py (orchestration)
# Dependency flow (no cycles):
# pipeline.py -> heuristic.py -> duplicate.py
# pipeline.py -> critique.pyGotcha: Split-Target Pattern (Cross-Module Imports)
When two modules import the same function for different purposes, each needs its own patch target:
# main.py imports extract_text for preview()
# service.py imports extract_text for process_file()
# Preview tests → patch main (preview calls it directly)
@patch("pdf2anki.main.extract_text")
def test_preview_shows_text(...):
# Convert tests → patch service (convert delegates to service.process_file)
@patch("pdf2anki.service.extract_text")
def test_convert_txt_to_tsv(...):Migration checklist (when moving functions between modules)
grep -r '@patch("module_a.' tests/to find all patches- For each patch, trace the call chain: which module looks up this function at runtime?
- Update target to the module where
from X import funcappears - Run tests - failures reveal missed patches
When to Use
- Splitting any Python module (>400 LOC) into a package
- Any refactoring that moves functions between modules when tests use
patch() - Introducing a service/adapter layer that changes import paths
- Grep pattern to find affected tests:
grep -r 'patch("pkg.old_module\.' tests/