Refactoring Patterns¶
Common refactoring scenarios with Rejig.
Renaming¶
Rename a Class¶
from rejig import Rejig
rj = Rejig("src/")
# Find and rename
cls = rj.find_classes("OldClassName").first()
if cls:
result = cls.rename("NewClassName")
print(f"Changed: {result.files_changed}")
Rename a Method Across Classes¶
# Rename in a specific class
cls = rj.file("models.py").find_class("User")
cls.find_method("get_name").rename("get_full_name")
# Rename in all classes that have it
for cls in rj.find_classes():
method = cls.find_method("old_method")
if method.exists():
method.rename("new_method")
Rename with Pattern¶
# Rename all classes matching a pattern
results = rj.find_classes(pattern="^Old").rename(r"^Old", "New")
# OldUser → NewUser, OldOrder → NewOrder
Adding Code¶
Add Method to All Classes¶
# Add a `validate` method to all model classes
for cls in rj.find_classes(pattern=".*Model$"):
if not cls.find_method("validate").exists():
cls.add_method("validate", body="return True", return_type="bool")
Add Decorator to Test Classes¶
Add Type Hints¶
# Add return type to all public functions
for func in rj.find_functions():
if not func.name.startswith("_"):
func.set_return_type("None") # Or infer: func.infer_type_hints()
Add Docstrings¶
# Generate docstrings for undocumented functions
(
rj.find_functions()
.filter(lambda f: not f.has_docstring())
.generate_docstrings(style="google")
)
Removing Code¶
Remove Deprecated Decorator¶
Remove Unused Imports¶
Delete Methods¶
# Delete all methods named `_old_helper`
for cls in rj.find_classes():
method = cls.find_method("_old_helper")
if method.exists():
method.delete()
Moving Code¶
Extract Method to Function¶
cls = rj.file("views.py").find_class("UserView")
method = cls.find_method("_format_response")
method.extract_to_function("format_response")
Move Lines¶
file = rj.file("utils.py")
# Move a block of code to a different position
file.lines(50, 75).move_to(10)
# Move to another file (inserted at the given line number)
file.lines(100, 150).move_to_file("helpers.py", line_number=20)
Converting Code¶
Convert Classes to Dataclasses¶
# Find classes that look like data containers
for cls in rj.find_classes():
methods = cls.find_methods()
# If only __init__ and simple methods, convert
if len(methods) <= 3:
cls.convert_to_dataclass()
Convert Sync to Async¶
# Convert all functions in an async module
for func in rj.file("async_utils.py").find_functions():
func.convert_to_async()
Modernize Type Hints¶
# Update to Python 3.10+ style
for file in rj.find_files():
file.modernize_type_hints() # List[str] → list[str]
Bulk Updates¶
Add Logging to All Methods¶
for cls in rj.find_classes(pattern=".*Service$"):
for method in cls.find_methods():
if not method.name.startswith("_"):
method.insert_statement(
f"logger.debug('Entering {method.name}')",
position="start"
)
Add Validation to Setters¶
for cls in rj.find_classes(pattern=".*Model$"):
for method in cls.find_methods(pattern="^set_"):
method.insert_statement("self._validate()", position="start")
Standardize Error Handling¶
for func in rj.find_functions(pattern="^fetch_"):
func.wrap_with_try_except(
exceptions=["ConnectionError", "Timeout"],
handler="logger.error(f'Fetch failed: {e}'); raise"
)
Configuration Updates¶
Update pyproject.toml¶
toml = rj.toml("pyproject.toml")
# Update version
toml.set("project.version", "2.0.0")
# Configure tools
toml.set("tool.black.line-length", 110)
toml.set("tool.ruff.select", ["E", "F", "W", "I"])
Sync Versions Across Files¶
version = "2.0.0"
rj.toml("pyproject.toml").set("project.version", version)
rj.json("package.json").set("version", version)
init = rj.file("src/mypackage/__init__.py")
init.replace_pattern(r'__version__ = "[^"]+"', f'__version__ = "{version}"')
Safety Patterns¶
Dry Run First¶
# Preview changes
rj_dry = Rejig("src/", dry_run=True)
result = rj_dry.find_classes("User").first().rename("UserModel")
print(f"Would: {result.message}")
# Then apply
rj = Rejig("src/")
result = rj.find_classes("User").first().rename("UserModel")
Validate Before Bulk Changes¶
classes = rj.find_classes(pattern="^Test")
# Check what will be affected
print(f"Will modify {len(classes)} classes:")
for cls in classes:
print(f" - {cls.name} in {cls.file_path}")
# Confirm before proceeding
if input("Continue? [y/N] ").lower() == "y":
classes.add_decorator("@slow")