From python-utils
This skill should be used when performing Python refactoring operations using the rope library. It provides comprehensive guidance on using rope programmatically for safe, automated refactorings including rename, extract method/variable, move, inline, restructure, and more.
npx claudepluginhub werdnum/claude-code-plugins --plugin python-utilsThis skill uses the workspace's default tool permissions.
This skill provides comprehensive guidance on using the rope library for advanced Python refactoring operations.
Refactors code structure for better readability and maintainability without changing behavior. Applies patterns like extract/inline/rename functions, eliminates duplication, addresses code smells like long functions and deep nesting.
Performs safe refactoring: extract functions/components/hooks/modules/classes, rename/move/restructure symbols/files, inline code, detect dead code/smells using test-driven methods.
Applies disciplined refactoring in small, verifiable steps to improve code structure without changing behavior: extract functions, rename, move code.
Share bugs, ideas, or general feedback.
This skill provides comprehensive guidance on using the rope library for advanced Python refactoring operations.
Rope is the world's most advanced open source Python refactoring library. It provides:
Rope is pure Python with no dependencies, making it ideal for programmatic refactoring.
Use this skill when:
Install rope using pip:
pip install rope
A rope Project represents a Python codebase in a directory. All refactorings operate within a project context:
from rope.base.project import Project
# Create a project for the current directory
project = Project('.')
# Or specify a path
project = Project('/path/to/myproject')
# Always close when done
project.close()
Key points about projects:
.ropeproject folder for configuration and caching (can be disabled with ropefolder=None)Files and folders in rope are accessed through Resource objects (File and Folder):
from rope.base import libutils
# Get a resource for an existing file
resource = libutils.path_to_resource(project, '/path/to/file.py')
# Create a resource for a file that doesn't exist yet
new_file = libutils.path_to_resource(project, '/path/to/new_file.py', type='file')
Refactorings return Change objects that describe the modifications:
# Get changes from a refactoring
changes = refactoring.get_changes('new_name')
# Preview changes
print(changes.get_description())
# Apply changes
project.do(changes)
# Undo if needed
project.history.undo()
Rename variables, functions, classes, modules - rope updates all references:
from rope.refactor.rename import Rename
# Get the resource
resource = libutils.path_to_resource(project, 'mymodule.py')
# Create renamer at offset (character position in file)
renamer = Rename(project, resource, offset)
# Get changes with new name
changes = renamer.get_changes('new_name')
# Option: rename in docs/comments too
changes = renamer.get_changes('new_name', docs=True)
# Apply
project.do(changes)
Calculating offsets: Offsets are character positions in the file (0-indexed). DOS line-endings and multi-byte characters count as one character. To find the offset of a name, read the file and use source.find('name') or calculate from line/column positions.
Extract selected code into a new method:
from rope.refactor.extract import ExtractMethod
resource = libutils.path_to_resource(project, 'mymodule.py')
# start and end are character offsets of the region to extract
extractor = ExtractMethod(project, resource, start, end)
# Extract to a method named 'extracted_method'
changes = extractor.get_changes('extracted_method')
project.do(changes)
Options:
global_: Extract as a global function instead of methodsimilar: Extract similar code blocks tooExtract an expression into a variable:
from rope.refactor.extract import ExtractVariable
resource = libutils.path_to_resource(project, 'mymodule.py')
extractor = ExtractVariable(project, resource, start, end)
changes = extractor.get_changes('extracted_var')
project.do(changes)
Options:
similar: Extract similar expressions tooglobal_: Extract as a global variableInline a method, variable, or parameter (replace uses with the actual value):
from rope.refactor.inline import (
InlineMethod,
InlineVariable,
InlineParameter
)
resource = libutils.path_to_resource(project, 'mymodule.py')
# Inline method
inliner = InlineMethod(project, resource, offset)
changes = inliner.get_changes()
project.do(changes)
# Inline variable
inliner = InlineVariable(project, resource, offset)
changes = inliner.get_changes()
project.do(changes)
Options:
only_current: Only inline the current occurrenceremove: Remove the definition after inliningMove classes, functions, modules, or methods:
from rope.refactor.move import create_move
resource = libutils.path_to_resource(project, 'mymodule.py')
# create_move auto-detects what to move based on offset
mover = create_move(project, resource, offset)
# Move to destination resource (module or package)
dest_resource = libutils.path_to_resource(project, 'newmodule.py')
changes = mover.get_changes(dest_resource)
project.do(changes)
Modify function/method signatures (add, remove, reorder parameters):
from rope.refactor.change_signature import ChangeSignature
resource = libutils.path_to_resource(project, 'mymodule.py')
changer = ChangeSignature(project, resource, offset)
# Get current signature info
signature = changer.get_signature()
# Modify signature - add parameter, remove parameter, reorder, etc.
# See rope documentation for ArgumentReordering and ArgumentAdder
changes = changer.get_changes(new_signature)
project.do(changes)
Pattern-based code transformation - the most powerful refactoring:
from rope.refactor.restructure import Restructure
# Pattern: what to match (uses ${} for wildcards)
pattern = '${pow_func}(${param1}, ${param2})'
# Goal: what to replace it with
goal = '${param1} ** ${param2}'
# Args: constraints on wildcards
args = {'pow_func': 'name=mymodule.pow'}
restructuring = Restructure(project, pattern, goal, args)
changes = restructuring.get_changes()
project.do(changes)
Pattern syntax:
${name} - wildcard matching any expressionname=module.func, type=mymodule.MyClass, etc.Common use cases:
# Introduce factory
from rope.refactor.introduce_factory import IntroduceFactory
# Encapsulate field (add getter/setter)
from rope.refactor.encapsulate_field import EncapsulateField
# Method to method object
from rope.refactor.method_object import MethodObject
# Local variable to field
from rope.refactor.localtofield import LocalToField
# Module to package
from rope.refactor.topackage import ModuleToPackage
# Organize imports
from rope.refactor.importutils import ImportOrganizer
When files change outside rope, validate to clear caches:
# Validate the entire project
project.validate(project.root)
# Or validate a specific resource
project.validate(resource)
Call this before each refactoring operation when using rope in an IDE or when files may have changed externally.
Run SOA to improve rope's type inference:
from rope.base import libutils
# Analyze a single module
libutils.analyze_module(project, resource)
# Analyze all modules (can be slow on large projects)
libutils.analyze_modules(project)
Run this periodically, especially before large refactorings. SOA analyzes function calls and assignments to infer types.
Always preview changes before applying:
changes = refactoring.get_changes('new_name')
# Get description
print(changes.get_description())
# Or inspect the Change object directly for detailed info
for change_set in changes.changes:
print(change_set.get_description())
# Apply only if satisfied
project.do(changes)
For long-running refactorings, use TaskHandle to monitor progress and allow cancellation:
from rope.base.taskhandle import TaskHandle
handle = TaskHandle("Rename refactoring")
def update_progress():
jobset = handle.current_jobset()
if jobset:
percent = jobset.get_percent_done()
print(f"Progress: {percent}%")
handle.add_observer(update_progress)
# Pass as keyword argument
changes = renamer.get_changes('new_name', task_handle=handle)
Always close projects to flush caches and save state:
try:
project = Project('.')
# ... perform refactorings ...
finally:
project.close()
Create a context manager for automatic cleanup:
from contextlib import contextmanager
@contextmanager
def rope_project(path):
project = Project(path)
try:
yield project
finally:
project.close()
# Usage
with rope_project('.') as project:
# ... perform refactorings ...
from rope.base.project import Project
from rope.base import libutils
from rope.refactor.rename import Rename
with rope_project('.') as project:
# Validate first
project.validate(project.root)
# Find the target
resource = libutils.path_to_resource(project, 'mymodule.py')
# Read file to find offset
source = resource.read()
offset = source.find('old_name')
# Create renamer
renamer = Rename(project, resource, offset)
# Preview
changes = renamer.get_changes('new_name', docs=True)
print(changes.get_description())
# Confirm and apply
response = input("Apply changes? (y/n): ")
if response.lower() == 'y':
project.do(changes)
print("Refactoring complete!")
from rope.refactor.extract import ExtractMethod
with rope_project('.') as project:
resource = libutils.path_to_resource(project, 'messy_module.py')
# Calculate offsets for the code block to extract
source = resource.read()
# Find start/end positions...
start = source.find('# START EXTRACTION')
end = source.find('# END EXTRACTION')
extractor = ExtractMethod(project, resource, start, end)
changes = extractor.get_changes('extracted_logic', similar=True)
project.do(changes)
from rope.refactor.restructure import Restructure
# Convert all old-style format strings to f-strings
pattern = '"${text}" % ${args}'
goal = 'f"${text}"' # Note: simplified, real implementation is more complex
with rope_project('.') as project:
restructuring = Restructure(project, pattern, goal, {})
changes = restructuring.get_changes()
if changes:
print(changes.get_description())
project.do(changes)
This skill includes helper scripts for common rope operations:
Interactive rename script:
python scripts/rope_rename.py /path/to/project file.py old_name new_name
Extract method/variable interactively:
python scripts/rope_extract.py /path/to/project file.py start_line end_line extracted_name
See the scripts directory for more utilities.
If rope can't find imports:
.ropeproject/config.py for python_path settingsIf rope makes wrong changes:
resources parameterFor large projects:
resources parameter to limit refactoring scope.ropeproject cachingFor detailed information, refer to:
See references/rope_library_docs.md for the complete rope library documentation (included in this skill for offline reference).