Recipes¶
Common refactoring tasks with emend.
Migrate from unittest to pytest¶
Replace assertion methods with native pytest assertions:
emend edit replace 'self.assertEqual($A, $B)' 'assert $A == $B' tests/ --apply
emend edit replace 'self.assertNotEqual($A, $B)' 'assert $A != $B' tests/ --apply
emend edit replace 'self.assertTrue($X)' 'assert $X' tests/ --apply
emend edit replace 'self.assertFalse($X)' 'assert not $X' tests/ --apply
emend edit replace 'self.assertIsNone($X)' 'assert $X is None' tests/ --apply
emend edit replace 'self.assertIsNotNone($X)' 'assert $X is not None' tests/ --apply
emend edit replace 'self.assertIn($A, $B)' 'assert $A in $B' tests/ --apply
emend edit replace 'self.assertNotIn($A, $B)' 'assert $A not in $B' tests/ --apply
Or use a batch file for the same thing:
# migrate-pytest.yaml
operations:
- replace: {pattern: "self.assertEqual($A, $B)", replacement: "assert $A == $B", path: "tests/"}
- replace: {pattern: "self.assertTrue($X)", replacement: "assert $X", path: "tests/"}
- replace: {pattern: "self.assertFalse($X)", replacement: "assert not $X", path: "tests/"}
- replace: {pattern: "self.assertIsNone($X)", replacement: "assert $X is None", path: "tests/"}
emend edit batch migrate-pytest.yaml --apply
Replace print with logging¶
# Preview first
emend edit replace 'print($X)' 'logger.info($X)' src/
# Apply everywhere
emend edit replace 'print($X)' 'logger.info($X)' src/ --apply
Set up a lint rule to catch prints¶
# .emend/rules.yaml
rules:
no-print:
match: "print($...ARGS)"
not-within: "def test_*"
message: "Use logger instead of print in production code"
fix: "logger.info($...ARGS)"
# Check for violations
emend lint src/
# Auto-fix violations
emend lint src/ --fix
Add type annotations to function returns¶
First find all functions missing return types:
emend find src/ --kind function --json | grep -v returns
Then add them one at a time:
emend edit set api.py::get_user[returns] "User | None" --apply
emend edit set api.py::create_user[returns] "User" --apply
Add a parameter to every method in a class¶
# Preview
emend find api.py::MyClass --kind method --output selector | while read sel; do
emend edit add "$sel[params]" "ctx: Context" --after self
done
# Apply
emend find api.py::MyClass --kind method --output selector | while read sel; do
emend edit add "$sel[params]" "ctx: Context" --after self --apply
done
Replace deprecated API calls¶
# Old: requests.get(url, **kwargs)
# New: httpx.get(url, **kwargs)
emend edit replace 'requests.get($URL)' 'httpx.get($URL)' src/ --apply
emend edit replace 'requests.post($URL, $DATA)' 'httpx.post($URL, $DATA)' src/ --apply
Rename a class everywhere¶
# Preview
emend edit rename api.py::OldName --to NewName
# Apply (including docstrings)
emend edit rename api.py::OldName --to NewName --docs --apply
Move a helper function to another module¶
# Preview (shows diff for source, destination, and all files that import it)
emend move utils.py::helper_func helpers/core.py
# Apply
emend move utils.py::helper_func helpers/core.py --apply
Find all functions that raise a specific exception¶
emend find 'raise ValueError($MSG)' src/ --json
Audit all open() calls (check for missing encoding)¶
# Find open() calls without encoding kwarg
emend find 'open($PATH)' src/
emend find 'open($PATH, $MODE)' src/
# Add encoding where missing
emend edit replace 'open($PATH)' 'open($PATH, encoding="utf-8")' src/ --apply
Find all places a function is called¶
# Pattern-based search (text matching)
emend find 'process_request($X)' src/ --json
# Scope-aware callers analysis (uses tree-sitter scope analysis)
emend analyze refs src/api.py::process_request --calls-only
Understand what a function depends on¶
# What functions does main() call?
emend analyze graph src/app.py::main
# Who calls process()?
emend analyze refs src/app.py::process --calls-only
# Visualize the call graph for the whole file
emend analyze graph src/app.py --format dot | dot -Tsvg > deps.svg
Find where a variable is mutated¶
# Only write references (assignments)
emend analyze refs config.py::settings --writes-only
# Only read references (loads)
emend analyze refs config.py::settings --reads-only
Extract and move a nested function¶
# Extract (with dedent to fix indentation)
emend edit cp module.py::OuterClass.inner_func helpers.py --dedent --apply
# Then remove from original
emend edit rm module.py::OuterClass.inner_func --apply
Rename a module¶
# Rename the file and update all imports
emend edit rename old_utils.py --to new_utils --apply
Batch-add a decorator to all async functions¶
emend find src/ --kind async_function --output selector | while read sel; do
emend edit add "$sel[decorators]" "@trace" --at 0 --apply
done
Multi-step refactoring with batch¶
# refactor.yaml
operations:
- rename: {selector: "api.py::get_user", to: "fetch_user"}
- replace:
pattern: "get_user($ID)"
replacement: "fetch_user(user_id=$ID)"
path: "src/"
- add:
selector: "api.py::fetch_user[params]:KEYWORD_ONLY"
value: "timeout: float = 30.0"
# Preview all changes
emend edit batch refactor.yaml
# Apply all changes
emend edit batch refactor.yaml --apply
Use the canonical find command¶
# Pattern mode (automatically detected because of $)
emend find 'print($X)' src/
# Lookup mode (automatically detected because of ::)
emend find api.py::get_user[params]
# Lookup with filters
emend find src/ --kind function --matching '@cache'
Find imports from a specific module¶
# Only match when json is actually imported from the json module
emend find 'json.loads($X)' src/ --imported-from json
Scope-aware pattern searching¶
# Find prints only inside test functions
emend find 'print($X)' tests/ --within 'def test_*'
# Find awaits only inside async fetch functions
emend find 'await $X' src/ --within 'async def fetch_*'
# Find locally-defined config variables (not imported ones)
emend find 'config' src/ --scope-local
# Find prints NOT inside try blocks
emend find 'print($X)' src/ --not-within 'try:'
Find dict literals with specific keys¶
# Find all dicts with a 'type' key set to 'user'
emend find "{'type': 'user', ...}" src/
# Find exact dict structures
emend find "{'name': \$NAME, 'age': \$AGE}" src/
Find walrus operator usage¶
# Find walrus in if conditions
emend find 'if ($VAR := $EXPR):' src/
Find chained comparisons¶
# Find range checks
emend find '$A < $B < $C' src/
Embedded SQL analysis¶
Search inside SQL strings embedded in Python code:
# Find all SQL queries referencing a table
emend find 'FROM users' src/ --dsl sql
# Find SELECT * anti-patterns
emend find 'SELECT * FROM $TABLE' src/ --dsl sql
# Lint SQL for anti-patterns
cat > .emend/rules.yaml << 'EOF'
rules:
no-select-star:
dsl: sql
match: "SELECT * FROM $TABLE"
message: "Avoid SELECT *; enumerate columns explicitly"
EOF
emend lint src/
# See which SQL queries are affected by an ORM model change
emend analyze impact models.py::User --json