A command-line tool for performing refactorings from Martin Fowler's "Refactoring: Improving the Design of Existing Code" on Python codebases.
Built on libCST and rope for safe, automated code transformations.
uv tool install molting-climolting [refactoring-name] <target> [arguments] [flags]Targets use pytest-style syntax with optional GitHub-style line numbers:
- Module:
path/to/file.py - Class:
path/to/file.py::ClassName - Method:
path/to/file.py::ClassName::method_name - Symbol:
path/to/file.py::ClassName::method_name::variable_name - With line range:
path/to/file.py::ClassName::method_name#L10-L15 - Single line:
path/to/file.py::ClassName::method_name#L10
Rename a variable, method, class, or module.
# Rename a method
molting rename src/foo.py::Calculator::add_numbers calculate_sum
# Rename a class
molting rename src/foo.py::OldClassName NewClassName
# Rename a variable
molting rename src/foo.py::Calculator::compute::result outputRemove a variable by replacing all references with its value.
molting inline-temp src/foo.py::Calculator::compute::temp_valueReplace calls to a method with the method's body.
molting inline src/foo.py::Calculator::simple_helperExtract an expression into a named variable.
# Extract expression on lines 10-12 into a variable (line-based targeting)
molting extract-variable src/foo.py::Calculator::compute#L10-L12 discount_rate
# Introduce explaining variable using expression-based targeting
# This is useful when expressions span multiple lines or don't have clean line boundaries
molting introduce-explaining-variable src/foo.py \
--in calculate_total \
--expression "order.quantity * order.item_price" \
--name base_price
# With --replace-all to replace all occurrences of the expression
molting introduce-explaining-variable src/foo.py \
--in calculate_total \
--expression "order.quantity * order.item_price" \
--name base_price \
--replace-allExtract a code block into a new method.
# Extract lines 15-20 into a new method
molting extract-method src/foo.py::Calculator::compute#L15-L20 calculate_tax
# Extract from a single line
molting extract-method src/foo.py::Calculator::compute#L18 validate_inputExtract code into a module-level function.
molting extract-function src/foo.py::Calculator::helper#L5-L10 format_currencyMove a method from one class to another.
molting move-method src/foo.py::Customer::calculate_discount --to OrderMove a field from one class to another.
molting move-field src/foo.py::Customer::address --to AccountExtract fields and methods into a new class.
molting extract-class src/foo.py::Person \
--fields name,phone,email \
--methods get_contact_info,update_contact \
--name ContactInfoMove a method from a subclass to its superclass.
molting pull-up-method src/foo.py::Manager::calculate_bonus --to EmployeeMove a method from a superclass to specific subclasses.
molting push-down-method src/foo.py::Employee::specialized_task --to Manager,EngineerReplace multiple parameters with a parameter object.
molting introduce-parameter-object src/foo.py::Order::create \
--params customer_name,customer_email,customer_phone \
--name CustomerInfoReplace a temporary variable with a method call.
molting replace-temp-with-query src/foo.py::Order::calculate_total::base_priceReplace conditional logic with polymorphic method calls.
molting replace-conditional-with-polymorphism src/foo.py::Bird::get_speed#L10-L20For detailed documentation on each refactoring, see:
- Composing Methods - Refactorings for improving method structure
- Moving Features Between Objects - Refactorings for moving functionality between classes
- Organizing Data - Refactorings for data structures and encapsulation
- Simplifying Conditional Expressions - Refactorings for cleaner conditionals
- Simplifying Method Calls - Refactorings for better method interfaces
- Dealing with Generalization - Refactorings for inheritance hierarchies
extract-method- Extract code into a new methodextract-function- Extract code into a module-level functioninline-method- Inline a method into its callersinline-temp- Inline a temporary variablereplace-temp-with-query- Replace temporary variable with a methodintroduce-explaining-variable- Extract complex expression into a variablesplit-temporary-variable- Make separate variables for separate purposesremove-assignments-to-parameters- Use a temporary variable insteadreplace-method-with-method-object- Turn a method into its own objectsubstitute-algorithm- Replace algorithm with a clearer one
move-method- Move a method to another classmove-field- Move a field to another classextract-class- Split a class into two classesinline-class- Merge a class into anotherhide-delegate- Create delegating methods to hide delegationremove-middle-man- Call the delegate directlyintroduce-foreign-method- Add method to a class you can't modifyintroduce-local-extension- Create a subclass or wrapper for extensions
self-encapsulate-field- Create getter/setter for field accessreplace-data-value-with-object- Turn data item into an objectchange-value-to-reference- Change value object to reference objectchange-reference-to-value- Change reference object to value objectreplace-array-with-object- Replace array with an objectduplicate-observed-data- Copy data to domain objectchange-unidirectional-association-to-bidirectional- Add back pointerschange-bidirectional-association-to-unidirectional- Remove back pointersreplace-magic-number-with-symbolic-constant- Create named constantencapsulate-field- Make field private with accessorsencapsulate-collection- Return read-only view of collectionreplace-type-code-with-class- Replace type code with a classreplace-type-code-with-subclasses- Replace type code with subclassesreplace-type-code-with-state-strategy- Replace type code with State/Strategy
decompose-conditional- Extract condition and branches into methodsconsolidate-conditional-expression- Combine conditions into oneconsolidate-duplicate-conditional-fragments- Move common code outside conditionalsremove-control-flag- Use break or return insteadreplace-nested-conditional-with-guard-clauses- Use guard clausesreplace-conditional-with-polymorphism- Use polymorphic dispatchintroduce-null-object- Replace null checks with null objectintroduce-assertion- Make assumptions explicit with assertions
rename-method- Rename a method for clarityadd-parameter- Add a parameter to a methodremove-parameter- Remove an unused parameterseparate-query-from-modifier- Split read and write operationsparameterize-method- Combine similar methods with a parameterreplace-parameter-with-explicit-methods- Create separate methodspreserve-whole-object- Pass entire object instead of valuesreplace-parameter-with-method-call- Call method instead of passing valueintroduce-parameter-object- Group parameters into an objectremove-setting-method- Remove setter for field set in constructorhide-method- Make method privatereplace-constructor-with-factory-function- Use factory functionreplace-error-code-with-exception- Throw exception insteadreplace-exception-with-test- Use conditional check instead
pull-up-field- Move field to superclasspull-up-method- Move method to superclasspull-up-constructor-body- Move constructor code to superclasspush-down-method- Move method to subclasspush-down-field- Move field to subclassextract-subclass- Create subclass for special caseextract-superclass- Create superclass for common featuresextract-interface- Create interface for common methodscollapse-hierarchy- Merge subclass into superclassform-template-method- Extract common algorithm to superclassreplace-inheritance-with-delegation- Use composition insteadreplace-delegation-with-inheritance- Use inheritance instead
# Clone the repository
git clone https://github.com/marshally/molting-cli.git
cd molting-cli
# Install uv, then sync the locked project environment
curl -LsSf https://astral.sh/uv/install.sh | sh
uv sync --locked
# Install git hooks (optional but recommended)
cp hooks/pre-commit .git/hooks/pre-commit
cp hooks/pre-push .git/hooks/pre-push
chmod +x .git/hooks/pre-commit
chmod +x .git/hooks/pre-pushWe use make to run code quality tools:
make help # Show all available commands
make format # Auto-fix formatting with black and ruff
make lint # Check code style without modifying
make typecheck # Run mypy type checking
make test # Run tests
make all # Format, typecheck, and test everythingRuns automatically before each commit on staged Python files only:
# Install the pre-commit hook
cp hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commitWhat it does:
- Runs black, ruff, mypy on staged files
- If linters made changes: Creates a separate commit with just the formatting fixes, then asks you to run
git commitagain - If no changes: Proceeds with your commit
- Keeps linter auto-fixes separate from your code changes
Limitation: Only checks staged files, not the entire codebase.
Runs automatically before each push on the entire codebase:
# Install the pre-push hook
cp hooks/pre-push .git/hooks/pre-push
chmod +x .git/hooks/pre-pushWhat it does:
- Runs black --check, ruff check, mypy on entire codebase
- Blocks the push if any checks fail
- Catches issues that pre-commit hook might miss
Recommended: Install both hooks. Pre-commit for fast feedback on staged files, pre-push as final check before pushing.
To skip hooks temporarily (not recommended):
# Skip pre-commit hook
git commit --no-verify
# Skip pre-push hook
git push --no-verifyTo remove the hooks:
rm .git/hooks/pre-commit
rm .git/hooks/pre-pushIf hooks are updated in the repository:
cp hooks/pre-commit .git/hooks/pre-commit
cp hooks/pre-push .git/hooks/pre-push
chmod +x .git/hooks/pre-commit
chmod +x .git/hooks/pre-push# Run all tests
make test
# Run with verbose output
make test-verbose
# Run specific test file
uv run pytest tests/test_composing_methods.py -v
# Run specific test
uv run pytest tests/test_composing_methods.py::TestExtractMethod::test_simple -vEvery push to a pull request automatically runs:
- Black - Code formatting check
- Ruff - Linting
- Mypy - Type checking
- Pytest - Test suite with coverage
- Runs on Python 3.10, 3.11, and 3.12
All checks must pass before merging. You can run the same checks locally:
make lint # Check formatting and linting
make typecheck # Run type checking
make test # Run testsContributions welcome! Please open an issue or PR.
All pull requests must pass CI checks (linting and tests) before merging.
MIT