From 694eca08e51ad01d2f7dd907f4791878772bac4c Mon Sep 17 00:00:00 2001 From: "Xiang (Sean) Zhou" Date: Mon, 26 May 2025 01:57:40 -0700 Subject: [PATCH] fix: fix bigquery credentials and bigquery tool to make it compatible with python 3.9 and make the credential serializable in session PiperOrigin-RevId: 763332829 --- .../tools/bigquery/bigquery_credentials.py | 77 ++++-- .../adk/tools/bigquery/bigquery_tool.py | 6 +- .../bigquery/test_bigquery_credentials.py | 12 +- .../test_bigquery_credentials_manager.py | 238 ++++++++++++------ .../tools/bigquery/test_bigquery_tool.py | 4 +- 5 files changed, 233 insertions(+), 104 deletions(-) diff --git a/src/google/adk/tools/bigquery/bigquery_credentials.py b/src/google/adk/tools/bigquery/bigquery_credentials.py index 3738685..f334b40 100644 --- a/src/google/adk/tools/bigquery/bigquery_credentials.py +++ b/src/google/adk/tools/bigquery/bigquery_credentials.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + from typing import List from typing import Optional @@ -33,15 +35,31 @@ from ..tool_context import ToolContext BIGQUERY_TOKEN_CACHE_KEY = "bigquery_token_cache" -class BigQueryCredentials(BaseModel): +class BigQueryCredentialsConfig(BaseModel): """Configuration for Google API tools. (Experimental)""" # Configure the model to allow arbitrary types like Credentials model_config = {"arbitrary_types_allowed": True} credentials: Optional[Credentials] = None - """the existing oauth credentials to use. If set will override client ID, - client secret, and scopes.""" + """the existing oauth credentials to use. If set,this credential will be used + for every end user, end users don't need to be involved in the oauthflow. This + field is mutually exclusive with client_id, client_secret and scopes. + Don't set this field unless you are sure this credential has the permission to + access every end user's data. + + Example usage: when the agent is deployed in Google Cloud environment and + the service account (used as application default credentials) has access to + all the required BigQuery resource. Setting this credential to allow user to + access the BigQuery resource without end users going through oauth flow. + + To get application default credential: `google.auth.default(...)`. See more + details in https://cloud.google.com/docs/authentication/application-default-credentials. + + When the deployed environment cannot provide a pre-existing credential, + consider setting below client_id, client_secret and scope for end users to go + through oauth flow, so that agent can access the user data. + """ client_id: Optional[str] = None """the oauth client ID to use.""" client_secret: Optional[str] = None @@ -51,12 +69,20 @@ class BigQueryCredentials(BaseModel): """ @model_validator(mode="after") - def __post_init__(self) -> "BigQueryCredentials": + def __post_init__(self) -> BigQueryCredentialsConfig: """Validate that either credentials or client ID/secret are provided.""" if not self.credentials and (not self.client_id or not self.client_secret): raise ValueError( "Must provide either credentials or client_id abd client_secret pair." ) + if self.credentials and ( + self.client_id or self.client_secret or self.scopes + ): + raise ValueError( + "Cannot provide both existing credentials and" + " client_id/client_secret/scopes." + ) + if self.credentials: self.client_id = self.credentials.client_id self.client_secret = self.credentials.client_secret @@ -71,14 +97,14 @@ class BigQueryCredentialsManager: the same authenticated session without duplicating OAuth logic. """ - def __init__(self, credentials: BigQueryCredentials): + def __init__(self, credentials_config: BigQueryCredentialsConfig): """Initialize the credential manager. Args: - credential_config: Configuration containing OAuth details or existing - credentials + credentials_config: Credentials containing client id and client secrete + or default credentials """ - self.credentials = credentials + self.credentials_config = credentials_config async def get_valid_credentials( self, tool_context: ToolContext @@ -87,18 +113,23 @@ class BigQueryCredentialsManager: Args: tool_context: The tool context for OAuth flow and state management - required_scopes: Set of OAuth scopes required by the calling tool Returns: Valid Credentials object, or None if OAuth flow is needed """ - # First, try to get cached credentials from the instance - creds = self.credentials.credentials + # First, try to get credentials from the tool context + creds_json = tool_context.state.get(BIGQUERY_TOKEN_CACHE_KEY, None) + creds = ( + Credentials.from_authorized_user_info( + creds_json, self.credentials_config.scopes + ) + if creds_json + else None + ) - # If credentails are empty + # If credentails are empty use the default credential if not creds: - creds = tool_context.get(BIGQUERY_TOKEN_CACHE_KEY, None) - self.credentials.credentials = creds + creds = self.credentials_config.credentials # Check if we have valid credentials if creds and creds.valid: @@ -110,7 +141,7 @@ class BigQueryCredentialsManager: creds.refresh(Request()) if creds.valid: # Cache the refreshed credentials - self.credentials.credentials = creds + tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] = creds.to_json() return creds except RefreshError: # Refresh failed, need to re-authenticate @@ -140,7 +171,7 @@ class BigQueryCredentialsManager: tokenUrl="https://oauth2.googleapis.com/token", scopes={ scope: f"Access to {scope}" - for scope in self.credentials.scopes + for scope in self.credentials_config.scopes }, ) ) @@ -149,8 +180,8 @@ class BigQueryCredentialsManager: auth_credential = AuthCredential( auth_type=AuthCredentialTypes.OAUTH2, oauth2=OAuth2Auth( - client_id=self.credentials.client_id, - client_secret=self.credentials.client_secret, + client_id=self.credentials_config.client_id, + client_secret=self.credentials_config.client_secret, ), ) @@ -165,14 +196,14 @@ class BigQueryCredentialsManager: token=auth_response.oauth2.access_token, refresh_token=auth_response.oauth2.refresh_token, token_uri=auth_scheme.flows.authorizationCode.tokenUrl, - client_id=self.credentials.client_id, - client_secret=self.credentials.client_secret, - scopes=list(self.credentials.scopes), + client_id=self.credentials_config.client_id, + client_secret=self.credentials_config.client_secret, + scopes=list(self.credentials_config.scopes), ) # Cache the new credentials - self.credentials.credentials = creds - tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] = creds + tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] = creds.to_json() + return creds else: # Request OAuth flow diff --git a/src/google/adk/tools/bigquery/bigquery_tool.py b/src/google/adk/tools/bigquery/bigquery_tool.py index ba0dd48..ad330ef 100644 --- a/src/google/adk/tools/bigquery/bigquery_tool.py +++ b/src/google/adk/tools/bigquery/bigquery_tool.py @@ -17,13 +17,13 @@ import inspect from typing import Any from typing import Callable from typing import Optional -from typing import override from google.oauth2.credentials import Credentials +from typing_extensions import override from ..function_tool import FunctionTool from ..tool_context import ToolContext -from .bigquery_credentials import BigQueryCredentials +from .bigquery_credentials import BigQueryCredentialsConfig from .bigquery_credentials import BigQueryCredentialsManager @@ -41,7 +41,7 @@ class BigQueryTool(FunctionTool): def __init__( self, func: Callable[..., Any], - credentials: Optional[BigQueryCredentials] = None, + credentials: Optional[BigQueryCredentialsConfig] = None, ): """Initialize the Google API tool. diff --git a/tests/unittests/tools/bigquery/test_bigquery_credentials.py b/tests/unittests/tools/bigquery/test_bigquery_credentials.py index 7937ccc..a1f4f5e 100644 --- a/tests/unittests/tools/bigquery/test_bigquery_credentials.py +++ b/tests/unittests/tools/bigquery/test_bigquery_credentials.py @@ -14,7 +14,7 @@ from unittest.mock import Mock -from google.adk.tools.bigquery.bigquery_credentials import BigQueryCredentials +from google.adk.tools.bigquery.bigquery_credentials import BigQueryCredentialsConfig # Mock the Google OAuth and API dependencies from google.oauth2.credentials import Credentials import pytest @@ -39,7 +39,7 @@ class TestBigQueryCredentials: mock_creds.client_secret = "test_client_secret" mock_creds.scopes = ["https://www.googleapis.com/auth/calendar"] - config = BigQueryCredentials(credentials=mock_creds) + config = BigQueryCredentialsConfig(credentials=mock_creds) # Verify that the credentials are properly stored and attributes are extracted assert config.credentials == mock_creds @@ -53,7 +53,7 @@ class TestBigQueryCredentials: This tests the scenario where users want to create new OAuth credentials from scratch using their application's client ID and secret. """ - config = BigQueryCredentials( + config = BigQueryCredentialsConfig( client_id="test_client_id", client_secret="test_client_secret", scopes=["https://www.googleapis.com/auth/bigquery"], @@ -77,7 +77,7 @@ class TestBigQueryCredentials: " pair" ), ): - BigQueryCredentials(client_id="test_client_id") + BigQueryCredentialsConfig(client_id="test_client_id") def test_missing_client_id_raises_error(self): """Test that missing client ID raises appropriate validation error.""" @@ -88,7 +88,7 @@ class TestBigQueryCredentials: " pair" ), ): - BigQueryCredentials(client_secret="test_client_secret") + BigQueryCredentialsConfig(client_secret="test_client_secret") def test_empty_configuration_raises_error(self): """Test that completely empty configuration is rejected. @@ -103,4 +103,4 @@ class TestBigQueryCredentials: " pair" ), ): - BigQueryCredentials() + BigQueryCredentialsConfig() diff --git a/tests/unittests/tools/bigquery/test_bigquery_credentials_manager.py b/tests/unittests/tools/bigquery/test_bigquery_credentials_manager.py index d9d594d..e6fd38a 100644 --- a/tests/unittests/tools/bigquery/test_bigquery_credentials_manager.py +++ b/tests/unittests/tools/bigquery/test_bigquery_credentials_manager.py @@ -19,7 +19,7 @@ from unittest.mock import patch from google.adk.auth import AuthConfig from google.adk.tools import ToolContext from google.adk.tools.bigquery.bigquery_credentials import BIGQUERY_TOKEN_CACHE_KEY -from google.adk.tools.bigquery.bigquery_credentials import BigQueryCredentials +from google.adk.tools.bigquery.bigquery_credentials import BigQueryCredentialsConfig from google.adk.tools.bigquery.bigquery_credentials import BigQueryCredentialsManager from google.auth.exceptions import RefreshError # Mock the Google OAuth and API dependencies @@ -46,15 +46,13 @@ class TestBigQueryCredentialsManager: context = Mock(spec=ToolContext) context.get_auth_response = Mock(return_value=None) context.request_credential = Mock() - # Mock the get method and state dictionary for caching tests - context.get = Mock(return_value=None) context.state = {} return context @pytest.fixture def credentials_config(self): """Create a basic credentials configuration for testing.""" - return BigQueryCredentials( + return BigQueryCredentialsConfig( client_id="test_client_id", client_secret="test_client_secret", scopes=["https://www.googleapis.com/auth/calendar"], @@ -77,7 +75,7 @@ class TestBigQueryCredentialsManager: # Create mock credentials that are already valid mock_creds = Mock(spec=Credentials) mock_creds.valid = True - manager.credentials.credentials = mock_creds + manager.credentials_config.credentials = mock_creds result = await manager.get_valid_credentials(mock_tool_context) @@ -85,8 +83,6 @@ class TestBigQueryCredentialsManager: # Verify no OAuth flow was triggered mock_tool_context.get_auth_response.assert_not_called() mock_tool_context.request_credential.assert_not_called() - # Verify cache retrieval wasn't needed since we had valid creds - mock_tool_context.get.assert_not_called() @pytest.mark.asyncio async def test_get_credentials_from_cache_when_none_in_manager( @@ -99,25 +95,37 @@ class TestBigQueryCredentialsManager: doesn't have them loaded. """ # Manager starts with no credentials - manager.credentials.credentials = None + manager.credentials_config.credentials = None - # Create mock cached credentials that are valid - mock_cached_creds = Mock(spec=Credentials) - mock_cached_creds.valid = True + # Create mock cached credentials JSON that would be stored in cache + mock_cached_creds_json = { + "token": "cached_token", + "refresh_token": "cached_refresh_token", + "client_id": "test_client_id", + "client_secret": "test_client_secret", + } - # Set up the tool context to return cached credentials - mock_tool_context.get.return_value = mock_cached_creds + # Set up the tool context state to contain cached credentials + mock_tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] = mock_cached_creds_json - result = await manager.get_valid_credentials(mock_tool_context) + # Mock the Credentials.from_authorized_user_info method + with patch( + "google.oauth2.credentials.Credentials.from_authorized_user_info" + ) as mock_from_json: + mock_creds = Mock(spec=Credentials) + mock_creds.valid = True + mock_from_json.return_value = mock_creds - # Verify credentials were retrieved from cache - mock_tool_context.get.assert_called_once_with( - BIGQUERY_TOKEN_CACHE_KEY, None - ) - # Verify cached credentials were loaded into manager - assert manager.credentials.credentials == mock_cached_creds - # Verify valid cached credentials were returned - assert result == mock_cached_creds + result = await manager.get_valid_credentials(mock_tool_context) + + # Verify credentials were created from cached JSON + mock_from_json.assert_called_once_with( + mock_cached_creds_json, manager.credentials_config.scopes + ) + # Verify loaded credentials were not cached into manager + assert manager.credentials_config.credentials is None + # Verify valid cached credentials were returned + assert result == mock_creds @pytest.mark.asyncio async def test_no_credentials_in_manager_or_cache( @@ -129,17 +137,13 @@ class TestBigQueryCredentialsManager: requiring a new OAuth flow to be initiated. """ # Manager starts with no credentials - manager.credentials.credentials = None - # Cache also returns None - mock_tool_context.get.return_value = None + manager.credentials_config.credentials = None + # Cache is also empty (state dict doesn't contain the key) result = await manager.get_valid_credentials(mock_tool_context) # Should trigger OAuth flow and return None (flow in progress) assert result is None - mock_tool_context.get.assert_called_once_with( - BIGQUERY_TOKEN_CACHE_KEY, None - ) mock_tool_context.request_credential.assert_called_once() @pytest.mark.asyncio @@ -152,33 +156,62 @@ class TestBigQueryCredentialsManager: This tests the interaction between caching and refresh functionality, ensuring that expired cached credentials can be refreshed properly. """ - # Manager starts with no credentials - manager.credentials.credentials = None + # Manager starts with no default credentials + manager.credentials_config.credentials = None + + # Create mock cached credentials JSON + mock_cached_creds_json = { + "token": "expired_token", + "refresh_token": "valid_refresh_token", + "client_id": "test_client_id", + "client_secret": "test_client_secret", + } + + mock_refreshed_creds_json = { + "token": "new_token", + "refresh_token": "valid_refresh_token", + "client_id": "test_client_id", + "client_secret": "test_client_secret", + } + + # Set up the tool context state to contain cached credentials + mock_tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] = mock_cached_creds_json # Create expired cached credentials with refresh token mock_cached_creds = Mock(spec=Credentials) mock_cached_creds.valid = False mock_cached_creds.expired = True - mock_cached_creds.refresh_token = "refresh_token" + mock_cached_creds.refresh_token = "valid_refresh_token" + mock_cached_creds.to_json.return_value = mock_refreshed_creds_json # Mock successful refresh def mock_refresh(request): mock_cached_creds.valid = True mock_cached_creds.refresh = Mock(side_effect=mock_refresh) - mock_tool_context.get.return_value = mock_cached_creds - result = await manager.get_valid_credentials(mock_tool_context) + # Mock the Credentials.from_authorized_user_info method + with patch( + "google.oauth2.credentials.Credentials.from_authorized_user_info" + ) as mock_from_json: + mock_from_json.return_value = mock_cached_creds - # Verify credentials were retrieved from cache - mock_tool_context.get.assert_called_once_with( - BIGQUERY_TOKEN_CACHE_KEY, None - ) - # Verify refresh was attempted and succeeded - mock_cached_creds.refresh.assert_called_once() - # Verify refreshed credentials were loaded into manager - assert manager.credentials.credentials == mock_cached_creds - assert result == mock_cached_creds + result = await manager.get_valid_credentials(mock_tool_context) + + # Verify credentials were created from cached JSON + mock_from_json.assert_called_once_with( + mock_cached_creds_json, manager.credentials_config.scopes + ) + # Verify refresh was attempted and succeeded + mock_cached_creds.refresh.assert_called_once() + # Verify refreshed credentials were not cached into manager + assert manager.credentials_config.credentials is None + # Verify refreshed credentials were cached + assert ( + "new_token" + == mock_tool_context.state[BIGQUERY_TOKEN_CACHE_KEY]["token"] + ) + assert result == mock_cached_creds @pytest.mark.asyncio @patch("google.auth.transport.requests.Request") @@ -201,14 +234,14 @@ class TestBigQueryCredentialsManager: mock_creds.valid = True mock_creds.refresh = Mock(side_effect=mock_refresh) - manager.credentials.credentials = mock_creds + manager.credentials_config.credentials = mock_creds result = await manager.get_valid_credentials(mock_tool_context) assert result == mock_creds mock_creds.refresh.assert_called_once() # Verify credentials were cached after successful refresh - assert manager.credentials.credentials == mock_creds + assert manager.credentials_config.credentials == mock_creds @pytest.mark.asyncio @patch("google.auth.transport.requests.Request") @@ -226,7 +259,7 @@ class TestBigQueryCredentialsManager: mock_creds.expired = True mock_creds.refresh_token = "expired_refresh_token" mock_creds.refresh = Mock(side_effect=RefreshError("Refresh failed")) - manager.credentials.credentials = mock_creds + manager.credentials_config.credentials = mock_creds result = await manager.get_valid_credentials(mock_tool_context) @@ -250,16 +283,39 @@ class TestBigQueryCredentialsManager: mock_auth_response.oauth2.refresh_token = "new_refresh_token" mock_tool_context.get_auth_response.return_value = mock_auth_response - result = await manager.get_valid_credentials(mock_tool_context) + # Create a mock credentials instance that will represent our created credentials + mock_creds = Mock(spec=Credentials) + # Make the JSON match what a real Credentials object would produce + mock_creds_json = ( + '{"token": "new_access_token", "refresh_token": "new_refresh_token",' + ' "token_uri": "https://oauth2.googleapis.com/token", "client_id":' + ' "test_client_id", "client_secret": "test_client_secret", "scopes":' + ' ["https://www.googleapis.com/auth/calendar"], "universe_domain":' + ' "googleapis.com", "account": ""}' + ) + mock_creds.to_json.return_value = mock_creds_json - # Verify new credentials were created and cached - assert isinstance(result, Credentials) - assert result.token == "new_access_token" - assert result.refresh_token == "new_refresh_token" - # Verify credentials are cached in manager - assert manager.credentials.credentials == result - # Verify credentials are also cached in tool context state - assert mock_tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] == result + # Use the full module path as it appears in the project structure + with patch( + "google.adk.tools.bigquery.bigquery_credentials.Credentials", + return_value=mock_creds, + ) as mock_credentials_class: + result = await manager.get_valid_credentials(mock_tool_context) + + # Verify new credentials were created + assert result == mock_creds + # Verify credentials are created with correct parameters + mock_credentials_class.assert_called_once() + call_kwargs = mock_credentials_class.call_args[1] + assert call_kwargs["token"] == "new_access_token" + assert call_kwargs["refresh_token"] == "new_refresh_token" + + # Verify credentials are not cached in manager + assert manager.credentials_config.credentials is None + # Verify credentials are also cached in tool context state + assert ( + mock_tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] == mock_creds_json + ) @pytest.mark.asyncio async def test_oauth_flow_in_progress(self, manager, mock_tool_context): @@ -269,6 +325,7 @@ class TestBigQueryCredentialsManager: and the user hasn't completed authorization yet. """ # No existing credentials, no auth response (flow not completed) + manager.credentials_config.credentials = None mock_tool_context.get_auth_response.return_value = None result = await manager.get_valid_credentials(mock_tool_context) @@ -300,28 +357,69 @@ class TestBigQueryCredentialsManager: mock_auth_response.oauth2.refresh_token = "cached_refresh_token" mock_tool_context.get_auth_response.return_value = mock_auth_response - # Complete OAuth flow with first manager - result1 = await manager1.get_valid_credentials(mock_tool_context) + # Create the mock credentials instance that will be returned by the constructor + mock_creds = Mock(spec=Credentials) + # Make sure our mock JSON matches the structure that real Credentials objects produce + mock_creds_json = ( + '{"token": "cached_access_token", "refresh_token":' + ' "cached_refresh_token", "token_uri":' + ' "https://oauth2.googleapis.com/token", "client_id": "test_client_id",' + ' "client_secret": "test_client_secret", "scopes":' + ' ["https://www.googleapis.com/auth/calendar"], "universe_domain":' + ' "googleapis.com", "account": ""}' + ) + mock_creds.to_json.return_value = mock_creds_json + mock_creds.valid = True - # Verify credentials were cached in tool context - assert BIGQUERY_TOKEN_CACHE_KEY in mock_tool_context.state - cached_creds = mock_tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] + # Use the correct module path - without the 'src.' prefix + with patch( + "google.adk.tools.bigquery.bigquery_credentials.Credentials", + return_value=mock_creds, + ) as mock_credentials_class: + # Complete OAuth flow with first manager + result1 = await manager1.get_valid_credentials(mock_tool_context) + + # Verify credentials were cached in tool context + assert BIGQUERY_TOKEN_CACHE_KEY in mock_tool_context.state + cached_creds_json = mock_tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] + assert cached_creds_json == mock_creds_json # Create second manager instance (simulating new request/session) manager2 = BigQueryCredentialsManager(credentials_config) + credentials_config.credentials = None # Reset auth response to None (no new OAuth flow available) mock_tool_context.get_auth_response.return_value = None - # Set up get method to return cached credentials - mock_tool_context.get.return_value = cached_creds - # Get credentials with second manager - result2 = await manager2.get_valid_credentials(mock_tool_context) + # Mock the from_authorized_user_info method for the second manager + with patch( + "google.adk.tools.bigquery.bigquery_credentials.Credentials.from_authorized_user_info" + ) as mock_from_json: + mock_cached_creds = Mock(spec=Credentials) + mock_cached_creds.valid = True + mock_from_json.return_value = mock_cached_creds - # Verify second manager retrieved cached credentials successfully - assert result2 == cached_creds - assert manager2.credentials.credentials == cached_creds - # Verify no new OAuth flow was requested - assert ( - mock_tool_context.request_credential.call_count == 0 - ) # Only from first manager + # Get credentials with second manager + result2 = await manager2.get_valid_credentials(mock_tool_context) + + # Verify second manager retrieved cached credentials successfully + assert result2 == mock_cached_creds + assert manager2.credentials_config.credentials is None + assert ( + cached_creds_json == mock_tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] + ) + # The from_authorized_user_info should be called with the complete JSON structure + mock_from_json.assert_called_once() + # Extract the actual argument that was passed to verify it's the right JSON structure + actual_json_arg = mock_from_json.call_args[0][0] + # We need to parse and compare the structure rather than exact string match + # since the order of keys in JSON might differ + import json + + expected_data = json.loads(mock_creds_json) + actual_data = ( + actual_json_arg + if isinstance(actual_json_arg, dict) + else json.loads(actual_json_arg) + ) + assert actual_data == expected_data diff --git a/tests/unittests/tools/bigquery/test_bigquery_tool.py b/tests/unittests/tools/bigquery/test_bigquery_tool.py index 2accd82..c786fff 100644 --- a/tests/unittests/tools/bigquery/test_bigquery_tool.py +++ b/tests/unittests/tools/bigquery/test_bigquery_tool.py @@ -17,7 +17,7 @@ from unittest.mock import Mock from unittest.mock import patch from google.adk.tools import ToolContext -from google.adk.tools.bigquery.bigquery_credentials import BigQueryCredentials +from google.adk.tools.bigquery.bigquery_credentials import BigQueryCredentialsConfig from google.adk.tools.bigquery.bigquery_credentials import BigQueryCredentialsManager from google.adk.tools.bigquery.bigquery_tool import BigQueryTool # Mock the Google OAuth and API dependencies @@ -78,7 +78,7 @@ class TestBigQueryTool: @pytest.fixture def credentials_config(self): """Create credentials configuration for testing.""" - return BigQueryCredentials( + return BigQueryCredentialsConfig( client_id="test_client_id", client_secret="test_client_secret", scopes=["https://www.googleapis.com/auth/bigquery"],