API migrations are a common task in large codebases. Whether you’re updating a deprecated function, changing parameter names, or modifying return types, Codegen makes it easy to update all call sites consistently.

Common Migration Scenarios

Renaming Parameters

When updating parameter names across an API, you need to update both the function definition and all call sites:

# Find the API function to update
api_function = codebase.get_function("process_data")

# Update the parameter name
old_param = api_function.get_parameter("input")
old_param.rename("data")

# All call sites are automatically updated:
# process_data(input="test") -> process_data(data="test")
See dependencies and usages for more on updating parameter names and types.

Adding Required Parameters

When adding a new required parameter to an API:

# Find all call sites before modifying the function
call_sites = list(api_function.call_sites)

# Add the new parameter
api_function.add_parameter("timeout: int")

# Update all existing call sites to include the new parameter
for call in call_sites:
    call.add_argument("timeout=30")  # Add with a default value
See function calls and callsites for more on handling call sites.

Changing Parameter Types

When updating parameter types:

# Update the parameter type
param = api_function.get_parameter("user_id")
param.type = "UUID"  # Change from string to UUID

# Find all call sites that need type conversion
for call in api_function.call_sites:
    arg = call.get_arg_by_parameter_name("user_id")
    if arg:
        # Convert string to UUID
        arg.edit(f"UUID({arg.value})")
See working with type annotations for more on changing parameter types.

Deprecating Functions

When deprecating an old API in favor of a new one:

old_api = codebase.get_function("old_process_data")
new_api = codebase.get_function("new_process_data")

# Add deprecation warning
old_api.add_decorator('@deprecated("Use new_process_data instead")')

# Update all call sites to use the new API
for call in old_api.call_sites:
    # Map old arguments to new parameter names
    args = [
        f"data={call.get_arg_by_parameter_name('input').value}",
        f"timeout={call.get_arg_by_parameter_name('wait').value}"
    ]
    
    # Replace the old call with the new API
    call.replace(f"new_process_data({', '.join(args)})")

Bulk Updates to Method Chains

When updating chained method calls, like database queries or builder patterns:

# Find all query chains ending with .execute()
for execute_call in codebase.function_calls:
    if execute_call.name != "execute":
        continue
        
    # Get the full chain
    chain = execute_call.call_chain
    
    # Example: Add .timeout() before .execute()
    if "timeout" not in {call.name for call in chain}:
        execute_call.insert_before("timeout(30)")

Handling Breaking Changes

When making breaking changes to an API, it’s important to:

  1. Identify all affected call sites
  2. Make changes consistently
  3. Update related documentation
  4. Consider backward compatibility

Here’s a comprehensive example:

def migrate_api_v1_to_v2(codebase):
    old_api = codebase.get_function("create_user_v1")
    
    # Document all existing call patterns
    call_patterns = {}
    for call in old_api.call_sites:
        args = [arg.source for arg in call.args]
        pattern = ", ".join(args)
        call_patterns[pattern] = call_patterns.get(pattern, 0) + 1
    
    print("Found call patterns:")
    for pattern, count in call_patterns.items():
        print(f"  {pattern}: {count} occurrences")
    
    # Create new API version
    new_api = old_api.copy()
    new_api.rename("create_user_v2")
    
    # Update parameter types
    new_api.get_parameter("email").type = "EmailStr"
    new_api.get_parameter("role").type = "UserRole"
    
    # Add new required parameters
    new_api.add_parameter("tenant_id: UUID")
    
    # Update all call sites
    for call in old_api.call_sites:
        # Get current arguments
        email_arg = call.get_arg_by_parameter_name("email")
        role_arg = call.get_arg_by_parameter_name("role")
        
        # Build new argument list with type conversions
        new_args = [
            f"email=EmailStr({email_arg.value})",
            f"role=UserRole({role_arg.value})",
            "tenant_id=get_current_tenant_id()"
        ]
        
        # Replace old call with new version
        call.replace(f"create_user_v2({', '.join(new_args)})")
    
    # Add deprecation notice to old version
    old_api.add_decorator('@deprecated("Use create_user_v2 instead")')

# Run the migration
migrate_api_v1_to_v2(codebase)

Best Practices

  1. Analyze First: Before making changes, analyze all call sites to understand usage patterns

    # Document current usage
    for call in api.call_sites:
        print(f"Called from: {call.parent_function.name}")
        print(f"With args: {[arg.source for arg in call.args]}")
    
  2. Make Atomic Changes: Update one aspect at a time

    # First update parameter names
    param.rename("new_name")
    
    # Then update types
    param.type = "new_type"
    
    # Finally update call sites
    for call in api.call_sites:
        # ... update calls
    
  3. Maintain Backwards Compatibility:

    # Add new parameter with default
    api.add_parameter("new_param: str = None")
    
    # Later make it required
    api.get_parameter("new_param").remove_default()
    
  4. Document Changes:

    # Add clear deprecation messages
    old_api.add_decorator('''@deprecated(
        "Use new_api() instead. Migration guide: docs/migrations/v2.md"
    )''')
    

Remember to test thoroughly after making bulk changes to APIs. While Codegen ensures syntactic correctness, you’ll want to verify the semantic correctness of the changes.

Was this page helpful?