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