Module 4: Coordinated Multi-File Refactoring

Move Module and Updating Imports

Move Module and Updating Imports

Capsule description

Rename and extract change names and logic, but the directory structure stays the same. Move module is the refactoring that reorganizes that structure: moving a file from one directory to another, splitting a module into several files, or consolidating scattered files into a single module. And every move requires updating all the imports that point to the old location.

In real projects, the directory structure degrades over time. A utils/ directory grows to 30 files. A services/ mixes business logic with infrastructure. A file that started in src/ should be in src/api/. Move module fixes this — but manually it's one of the most error-prone refactorings because a single forgotten import breaks everything at runtime.

Claude Code handles this because it can read all the project's imports, understand the dependency structure, and update each reference coherently. In this capsule you're going to reorganize the directory structure with confidence.


Why Moving Modules Is Hard

The imports problem

# Current structure (disorganized):
# src/
#   utils/
#     email_helper.py          # ← should be in services/
#     payment_calculator.py    # ← should be in services/
#     string_utils.py          # ← it's fine here
#     auth_middleware.py       # ← should be in middleware/
#   services/
#     order_service.py
#     user_service.py

# payment_calculator.py is imported by:
# src/services/order_service.py:   from utils.payment_calculator import calculate_total
# src/api/routes/checkout.py:       from utils.payment_calculator import calculate_total
# src/api/routes/invoices.py:       from utils.payment_calculator import PaymentCalculator
# src/tasks/billing.py:             from utils.payment_calculator import calculate_total
# tests/test_payment.py:            from utils.payment_calculator import calculate_total
# tests/test_orders.py:             from utils.payment_calculator import PaymentCalculator

# Moving payment_calculator.py to services/ requires updating
# 6 imports in 6 different files

A forgotten import doesn't generate a syntax error — it generates a ModuleNotFoundError at runtime. If that import is in an infrequent code path, the bug can reach production.


Simple Move: One File

The complete cycle with Claude Code

Step 1: Tests

> "Run all the tests and confirm that they pass before
   moving payment_calculator.py"

Step 2: Identify the impact

> "List all the files that import payment_calculator.
   For each one, show the exact import line."

# Expected output:
# 1. src/services/order_service.py:3 → from utils.payment_calculator import calculate_total
# 2. src/api/routes/checkout.py:5 → from utils.payment_calculator import calculate_total
# 3. src/api/routes/invoices.py:4 → from utils.payment_calculator import PaymentCalculator
# 4. src/tasks/billing.py:2 → from utils.payment_calculator import calculate_total
# 5. tests/test_payment.py:1 → from utils.payment_calculator import calculate_total
# 6. tests/test_orders.py:3 → from utils.payment_calculator import PaymentCalculator

Step 3: Move and update

> "Move src/utils/payment_calculator.py to
   src/services/payment_calculator.py.
   Update all the imports in the 6 files
   that import it. The imports should change from
   'from utils.payment_calculator' to
   'from services.payment_calculator'."

# Claude Code:
# 1. Moves the file
# 2. Updates the 6 imports
# 3. Verifies there are no more references to the old path

Step 4: Verify

> "Run the tests and also grep for
   'from utils.payment_calculator' to confirm
   there are no references to the old path left"

# Expected output:
# ✅ Tests: 47 passed
# ✅ grep: 0 results (no old references left)

Complex Move: Reorganize a Directory

Scenario: utils/ has 30 files

# Before (disorganized):
# src/utils/
#   string_utils.py
#   date_utils.py
#   email_helper.py          # → services/email/
#   sms_helper.py            # → services/notifications/
#   payment_calculator.py    # → services/billing/
#   invoice_generator.py     # → services/billing/
#   auth_middleware.py       # → middleware/
#   cors_middleware.py       # → middleware/
#   rate_limiter.py          # → middleware/
#   logger.py                # → infrastructure/
#   cache.py                 # → infrastructure/
#   db_connection.py         # → infrastructure/
#   ... (18 more files)

# After (organized):
# src/utils/          → Only pure utilities (string, date, etc.)
# src/services/       → Business logic
# src/middleware/      → HTTP middleware
# src/infrastructure/ → Logging, cache, DB

Reorganization plan with Claude Code

# Step 1: Analyze and plan
> "Analyze all the files in src/utils/ and classify them
   into categories: pure utils, services, middleware, and
   infrastructure. For each file, suggest the destination
   directory and list how many files import each one."

# Expected output:
# Pure utils (stay): string_utils.py, date_utils.py (4 importers)
# Services (move to src/services/): email_helper.py (8), payment_calculator.py (6), ...
# Middleware (move to src/middleware/): auth_middleware.py (3), cors.py (2), ...
# Infrastructure (move to src/infrastructure/): logger.py (12), cache.py (7), ...

# Step 2: Move from lowest to highest impact
> "Start with the files with the fewest importers.
   Move cors_middleware.py to src/middleware/cors.py.
   Update the 2 imports. Run tests."

# Step 3: Repeat for each file
> "Move auth_middleware.py to src/middleware/auth.py.
   Update the 3 imports. Run tests."

# Step 4: The highest-impact ones last
> "Move logger.py to src/infrastructure/logger.py.
   Update the 12 imports. Run tests."

Rule: move one file at a time, run tests after each move. Never move 5 files at once.


Move with Re-export (Backwards Compatibility)

When to use it

If the module is public (other projects import it) or if you want to do the migration gradually, you can leave a re-export in the old location:

# src/utils/payment_calculator.py (old file, now re-exports)
"""
DEPRECATED: Moved to src/services/payment_calculator.py
This file exists only for backwards compatibility.
Remove when all consumers have updated their imports.
"""
from services.payment_calculator import *  # re-export everything

import warnings
warnings.warn(
    "Importing from utils.payment_calculator is deprecated. "
    "Use services.payment_calculator instead.",
    DeprecationWarning,
    stacklevel=2
)
# Prompt to Claude Code:
> "Move payment_calculator.py to src/services/.
   In the old location, leave a file that re-exports
   everything from the new location with a DeprecationWarning.
   Update the project's internal imports to the new path.
   The external imports (if any) will keep working
   with the re-export."

Move with Split: One File → Multiple

When to split

When a file has 500+ lines and contains multiple classes/functions with different responsibilities:

# src/services/user_service.py (600 lines, 3 responsibilities)
class UserAuthService:          # Authentication
    def login(self): ...
    def logout(self): ...
    def reset_password(self): ...

class UserProfileService:       # Profile
    def get_profile(self): ...
    def update_profile(self): ...
    def upload_avatar(self): ...

class UserNotificationService:  # Notifications
    def send_welcome(self): ...
    def send_password_reset(self): ...

Execute the split with Claude Code

> "Split src/services/user_service.py into 3 files:
   1. src/services/user_auth_service.py — UserAuthService
   2. src/services/user_profile_service.py — UserProfileService
   3. src/services/user_notification_service.py — UserNotificationService
   
   For each class:
   - Move the class and its imports to the new file
   - Update all the imports in the project
   
   If any file imported multiple classes from user_service.py,
   update it to separate imports from each new file.
   
   Run the tests after each move."

Move with Merge: Multiple Files → One

When to consolidate

The opposite of the split: files of 20-30 lines that should be a single module:

# Before (fragmented):
# src/validators/
#   email_validator.py    (25 lines, 1 function)
#   phone_validator.py    (30 lines, 1 function)
#   age_validator.py      (20 lines, 1 function)
#   name_validator.py     (25 lines, 1 function)

# After (consolidated):
# src/validators/
#   validators.py         (100 lines, 4 functions)
> "Consolidate the 4 validator files into
   src/validators/validators.py. Move all the functions
   into the consolidated file. Update all the imports.
   Remove the old files. Run tests."

Comparison: Manual Move vs Claude Code

CriterionManualClaude Code
Finding all the importsgrep + manual reviewAutomatic and complete
Updating importsFind-and-replace (error-prone)Semantic and contextual
Relative importsEasy to forget .. pathsComputes automatically
Re-exportsWrite manuallyGenerates with a deprecation warning
VerificationRun tests manuallyRuns and reports
Split/MergeTedious, many stepsCoordinated in one prompt

Connection with the Project

In the Module Project (capsule 06), if the codebase has organization problems (files in the wrong directories, a bloated utils/, inconsistent naming), move module is one of the techniques you're going to use. Combine it with rename (capsule 02) and extract (capsule 02) for a complete reorganization.


Troubleshooting

Problem 1: ModuleNotFoundError after the move

Cause: An import was left pointing to the old path.

Solution:

> "Search for any import that still points to
   'utils.payment_calculator' (the old path).
   Update it to the new path 'services.payment_calculator'."

Problem 2: Circular import after moving

Cause: The new directory creates a circular dependency that didn't exist before.

Solution: Analyze before moving:

> "If I move payment_calculator.py to services/,
   will any circular dependency be created? Analyze
   the imports of payment_calculator and the imports
   of the files in services/."

Problem 3: __init__.py not updated

Cause: The destination directory has an __init__.py that doesn't export the new module.

Solution:

> "After moving the file, also update
   the destination directory's __init__.py to export
   the classes/functions of the new module."

Problem 4: Relative paths break

Cause: The moved file used relative imports that are no longer valid.

Solution:

> "Update the imports INSIDE the moved file.
   The relative imports (from . import X) may
   have changed when the directory changed."

Exercises

Exercise 1: Plan a move (Easy)

src/helpers/email_sender.py needs to move to src/services/email/sender.py. Write the 4 steps of the cycle with prompts for Claude Code.

See solution
# Step 1: Tests
> "Run tests and confirm that they pass."

# Step 2: Impact
> "List all the files that import email_sender
   from src/helpers/. Show the exact import line."

# Step 3: Move
> "Create the directory src/services/email/ with __init__.py.
   Move src/helpers/email_sender.py to
   src/services/email/sender.py. Update all the imports
   from 'from helpers.email_sender' to 'from services.email.sender'."

# Step 4: Verify
> "Run tests. Also search for 'helpers.email_sender'
   across the whole project to confirm 0 old references."

Exercise 2: Reorganize utils/ (Medium)

Your src/utils/ has these 8 files. Classify them and propose the destination structure:

string_utils.py, date_utils.py, db_connection.py,
redis_cache.py, auth_check.py, rate_limiter.py,
csv_exporter.py, pdf_generator.py
See solution
# Classification:
# Pure utils (stay in utils/):
#   string_utils.py, date_utils.py

# Infrastructure (move to infrastructure/):
#   db_connection.py, redis_cache.py

# Middleware (move to middleware/):
#   auth_check.py, rate_limiter.py

# Services (move to services/):
#   csv_exporter.py, pdf_generator.py

# Final structure:
# src/
#   utils/           → string_utils.py, date_utils.py
#   infrastructure/  → db_connection.py, redis_cache.py
#   middleware/       → auth_check.py, rate_limiter.py
#   services/        → csv_exporter.py, pdf_generator.py

Criterion: pure utils = no side effects, no I/O. Infrastructure = external connections. Middleware = intercepts requests. Services = business logic.

Exercise 3: Design a split (Medium)

src/services/app_service.py has 800 lines with 4 classes: AuthService, UserService, OrderService, NotificationService. Write the prompt for Claude Code that does the split.

See solution
> "Split src/services/app_service.py into 4 files:
   1. src/services/auth_service.py — AuthService + its imports
   2. src/services/user_service.py — UserService + its imports
   3. src/services/order_service.py — OrderService + its imports
   4. src/services/notification_service.py — NotificationService + its imports
   
   For each class:
   - Include only the imports that class needs
   - Update all the imports in the project that pointed
     to app_service.AuthService → auth_service.AuthService (etc.)
   - If any class depends on another from the same file,
     add the import to the new file

   After moving the 4 classes, remove app_service.py.
   Run tests after each individual move."

Exercise 4: Move with backwards compatibility (Hard)

Your module src/utils/logger.py is imported by 15 internal files and 3 external projects. Design a migration plan that doesn't break the external projects.

See solution
# Phase 1: Move with re-export
> "Move src/utils/logger.py to src/infrastructure/logger.py.
   In src/utils/logger.py, leave a file that:
   1. Re-exports everything from infrastructure.logger
   2. Emits a DeprecationWarning on import
   3. Has a comment 'Remove after v2.0'

   Update the 15 internal imports to the new path.
   The 3 external projects will keep working with the re-export."

# Phase 2: Communicate to external projects
# (outside Claude Code: send a PR/notification to the 3 projects)

# Phase 3: Remove the re-export (after the external ones migrated)
> "Remove the re-export file src/utils/logger.py.
   Verify that no internal import uses it."

Key: re-export + DeprecationWarning enables a gradual migration without breaking changes.


Summary

In this capsule you learned:

  • Move module reorganizes the directory structure by updating all the imports
  • The cycle is: tests → identify impact → move → verify — one file at a time
  • Simple move: move a file and update N imports
  • Move with re-export: backwards compatibility for external consumers
  • Split: divide a large file into multiple specialized ones
  • Merge: consolidate small files into a coherent module
  • Claude Code finds all the imports including relative ones, __init__.py, and dynamic paths
  • Moving from lowest to highest impact reduces risk

Next capsule: Interface Changes and Propagation. You're going to learn the most complex refactoring: changing a function's signature and propagating it to all the consumers.


Additional Resources

  1. Python Import System - Official documentation of the Python import system
  2. PEP 328 - Imports Multi-Line and Absolute/Relative - The PEP that defines absolute and relative imports
  3. Refactoring Guru - Move Method/Class - Visual explanation of the move refactoring
  4. importlib - Python Docs - To understand how Python resolves imports
  5. isort - Python Import Sorter - A tool to organize imports automatically
  6. absolufy-imports - Convert relative imports to absolute

Module 4, Capsule 03 — Refactoring & Legacy Code with Claude Code Guide