def migrate_environments_to_v2(
    project_id: int,
    capacity_budget: Decimal,
) -> EdgeV2MigrationResult | None:
    """
    Migrate project's environments to `environments_v2` table.

    :param project_id: ID of project to migrate.
    :param capacity_budget: Max read capacity to spend when reading identities. Can be 0.

    :returns: `EdgeV2MigrationResult` object or `None`.

    If provided `capacity_budget` exceeded, including a budget of 0,
    `EdgeV2MigrationResult.status` is set to `INCOMPLETE` and no identity overrides get
    written to `environments_v2` table.
    """
    dynamo_wrapper_v2 = DynamoEnvironmentV2Wrapper()
    identity_wrapper = DynamoIdentityWrapper()

    if not (dynamo_wrapper_v2.is_enabled and identity_wrapper.is_enabled):
        return None

    logger.info("Migrating environments to v2 for project %d", project_id)

    environments_to_migrate = Environment.objects.filter_for_document_builder(
        project_id=project_id
    )

    identity_overrides_changeset = IdentityOverridesV2Changeset(to_put=[], to_delete=[])
    result_status = EdgeV2MigrationStatus.COMPLETE

    try:
        to_put = list(
            _iter_paginated_overrides(
                identity_wrapper=identity_wrapper,
                environments=environments_to_migrate,
                capacity_budget=capacity_budget,
            )
        )
    except CapacityBudgetExceeded as exc:
        result_status = EdgeV2MigrationStatus.INCOMPLETE
        logger.warning("Incomplete migration for project %d", project_id, exc_info=exc)
    else:
        identity_overrides_changeset.to_put = to_put
        logger.info(
            "Retrieved %d identity overrides to migrate for project %d",
            len(to_put),
            project_id,
        )

    dynamo_wrapper_v2.write_environments(environments_to_migrate)
    dynamo_wrapper_v2.update_identity_overrides(identity_overrides_changeset)

    logger.info("Finished migrating environments to v2 for project %d", project_id)
    return EdgeV2MigrationResult(
        identity_overrides_changeset=identity_overrides_changeset,
        status=result_status,
    )

    Migrate project's environments to `environments_v2` table.

    :param project_id: ID of project to migrate.
    :param capacity_budget: Max read capacity to spend when reading identities. Can be 0.

    :returns: `EdgeV2MigrationResult` object or `None`.

    If provided `capacity_budget` exceeded, including a budget of 0,
    `EdgeV2MigrationResult.status` is set to `INCOMPLETE` and no identity overrides get
    written to `environments_v2` table.
    
api/environments/dynamodb/services.py