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")
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
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})")
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:
- Identify all affected call sites
- Make changes consistently
- Update related documentation
- 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
-
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]}")
-
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
-
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()
-
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.