Skip to content

Commit 694eca0

Browse files
seanzhougooglecopybara-github
authored andcommitted
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
1 parent 55cb36e commit 694eca0

File tree

5 files changed

+239
-110
lines changed

5 files changed

+239
-110
lines changed

src/google/adk/tools/bigquery/bigquery_credentials.py

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from __future__ import annotations
16+
1517
from typing import List
1618
from typing import Optional
1719

@@ -33,15 +35,31 @@
3335
BIGQUERY_TOKEN_CACHE_KEY = "bigquery_token_cache"
3436

3537

36-
class BigQueryCredentials(BaseModel):
38+
class BigQueryCredentialsConfig(BaseModel):
3739
"""Configuration for Google API tools. (Experimental)"""
3840

3941
# Configure the model to allow arbitrary types like Credentials
4042
model_config = {"arbitrary_types_allowed": True}
4143

4244
credentials: Optional[Credentials] = None
43-
"""the existing oauth credentials to use. If set will override client ID,
44-
client secret, and scopes."""
45+
"""the existing oauth credentials to use. If set,this credential will be used
46+
for every end user, end users don't need to be involved in the oauthflow. This
47+
field is mutually exclusive with client_id, client_secret and scopes.
48+
Don't set this field unless you are sure this credential has the permission to
49+
access every end user's data.
50+
51+
Example usage: when the agent is deployed in Google Cloud environment and
52+
the service account (used as application default credentials) has access to
53+
all the required BigQuery resource. Setting this credential to allow user to
54+
access the BigQuery resource without end users going through oauth flow.
55+
56+
To get application default credential: `google.auth.default(...)`. See more
57+
details in https://cloud.google.com/docs/authentication/application-default-credentials.
58+
59+
When the deployed environment cannot provide a pre-existing credential,
60+
consider setting below client_id, client_secret and scope for end users to go
61+
through oauth flow, so that agent can access the user data.
62+
"""
4563
client_id: Optional[str] = None
4664
"""the oauth client ID to use."""
4765
client_secret: Optional[str] = None
@@ -51,12 +69,20 @@ class BigQueryCredentials(BaseModel):
5169
"""
5270

5371
@model_validator(mode="after")
54-
def __post_init__(self) -> "BigQueryCredentials":
72+
def __post_init__(self) -> BigQueryCredentialsConfig:
5573
"""Validate that either credentials or client ID/secret are provided."""
5674
if not self.credentials and (not self.client_id or not self.client_secret):
5775
raise ValueError(
5876
"Must provide either credentials or client_id abd client_secret pair."
5977
)
78+
if self.credentials and (
79+
self.client_id or self.client_secret or self.scopes
80+
):
81+
raise ValueError(
82+
"Cannot provide both existing credentials and"
83+
" client_id/client_secret/scopes."
84+
)
85+
6086
if self.credentials:
6187
self.client_id = self.credentials.client_id
6288
self.client_secret = self.credentials.client_secret
@@ -71,14 +97,14 @@ class BigQueryCredentialsManager:
7197
the same authenticated session without duplicating OAuth logic.
7298
"""
7399

74-
def __init__(self, credentials: BigQueryCredentials):
100+
def __init__(self, credentials_config: BigQueryCredentialsConfig):
75101
"""Initialize the credential manager.
76102
77103
Args:
78-
credential_config: Configuration containing OAuth details or existing
79-
credentials
104+
credentials_config: Credentials containing client id and client secrete
105+
or default credentials
80106
"""
81-
self.credentials = credentials
107+
self.credentials_config = credentials_config
82108

83109
async def get_valid_credentials(
84110
self, tool_context: ToolContext
@@ -87,18 +113,23 @@ async def get_valid_credentials(
87113
88114
Args:
89115
tool_context: The tool context for OAuth flow and state management
90-
required_scopes: Set of OAuth scopes required by the calling tool
91116
92117
Returns:
93118
Valid Credentials object, or None if OAuth flow is needed
94119
"""
95-
# First, try to get cached credentials from the instance
96-
creds = self.credentials.credentials
120+
# First, try to get credentials from the tool context
121+
creds_json = tool_context.state.get(BIGQUERY_TOKEN_CACHE_KEY, None)
122+
creds = (
123+
Credentials.from_authorized_user_info(
124+
creds_json, self.credentials_config.scopes
125+
)
126+
if creds_json
127+
else None
128+
)
97129

98-
# If credentails are empty
130+
# If credentails are empty use the default credential
99131
if not creds:
100-
creds = tool_context.get(BIGQUERY_TOKEN_CACHE_KEY, None)
101-
self.credentials.credentials = creds
132+
creds = self.credentials_config.credentials
102133

103134
# Check if we have valid credentials
104135
if creds and creds.valid:
@@ -110,7 +141,7 @@ async def get_valid_credentials(
110141
creds.refresh(Request())
111142
if creds.valid:
112143
# Cache the refreshed credentials
113-
self.credentials.credentials = creds
144+
tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] = creds.to_json()
114145
return creds
115146
except RefreshError:
116147
# Refresh failed, need to re-authenticate
@@ -140,7 +171,7 @@ async def _perform_oauth_flow(
140171
tokenUrl="https://oauth2.googleapis.com/token",
141172
scopes={
142173
scope: f"Access to {scope}"
143-
for scope in self.credentials.scopes
174+
for scope in self.credentials_config.scopes
144175
},
145176
)
146177
)
@@ -149,8 +180,8 @@ async def _perform_oauth_flow(
149180
auth_credential = AuthCredential(
150181
auth_type=AuthCredentialTypes.OAUTH2,
151182
oauth2=OAuth2Auth(
152-
client_id=self.credentials.client_id,
153-
client_secret=self.credentials.client_secret,
183+
client_id=self.credentials_config.client_id,
184+
client_secret=self.credentials_config.client_secret,
154185
),
155186
)
156187

@@ -165,14 +196,14 @@ async def _perform_oauth_flow(
165196
token=auth_response.oauth2.access_token,
166197
refresh_token=auth_response.oauth2.refresh_token,
167198
token_uri=auth_scheme.flows.authorizationCode.tokenUrl,
168-
client_id=self.credentials.client_id,
169-
client_secret=self.credentials.client_secret,
170-
scopes=list(self.credentials.scopes),
199+
client_id=self.credentials_config.client_id,
200+
client_secret=self.credentials_config.client_secret,
201+
scopes=list(self.credentials_config.scopes),
171202
)
172203

173204
# Cache the new credentials
174-
self.credentials.credentials = creds
175-
tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] = creds
205+
tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] = creds.to_json()
206+
176207
return creds
177208
else:
178209
# Request OAuth flow

src/google/adk/tools/bigquery/bigquery_tool.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
from typing import Any
1818
from typing import Callable
1919
from typing import Optional
20-
from typing import override
2120

2221
from google.oauth2.credentials import Credentials
22+
from typing_extensions import override
2323

2424
from ..function_tool import FunctionTool
2525
from ..tool_context import ToolContext
26-
from .bigquery_credentials import BigQueryCredentials
26+
from .bigquery_credentials import BigQueryCredentialsConfig
2727
from .bigquery_credentials import BigQueryCredentialsManager
2828

2929

@@ -41,7 +41,7 @@ class BigQueryTool(FunctionTool):
4141
def __init__(
4242
self,
4343
func: Callable[..., Any],
44-
credentials: Optional[BigQueryCredentials] = None,
44+
credentials: Optional[BigQueryCredentialsConfig] = None,
4545
):
4646
"""Initialize the Google API tool.
4747

tests/unittests/tools/bigquery/test_bigquery_credentials.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from unittest.mock import Mock
1616

17-
from google.adk.tools.bigquery.bigquery_credentials import BigQueryCredentials
17+
from google.adk.tools.bigquery.bigquery_credentials import BigQueryCredentialsConfig
1818
# Mock the Google OAuth and API dependencies
1919
from google.oauth2.credentials import Credentials
2020
import pytest
@@ -39,7 +39,7 @@ def test_valid_credentials_object(self):
3939
mock_creds.client_secret = "test_client_secret"
4040
mock_creds.scopes = ["https://www.googleapis.com/auth/calendar"]
4141

42-
config = BigQueryCredentials(credentials=mock_creds)
42+
config = BigQueryCredentialsConfig(credentials=mock_creds)
4343

4444
# Verify that the credentials are properly stored and attributes are extracted
4545
assert config.credentials == mock_creds
@@ -53,7 +53,7 @@ def test_valid_client_id_secret_pair(self):
5353
This tests the scenario where users want to create new OAuth credentials
5454
from scratch using their application's client ID and secret.
5555
"""
56-
config = BigQueryCredentials(
56+
config = BigQueryCredentialsConfig(
5757
client_id="test_client_id",
5858
client_secret="test_client_secret",
5959
scopes=["https://www.googleapis.com/auth/bigquery"],
@@ -77,7 +77,7 @@ def test_missing_client_secret_raises_error(self):
7777
" pair"
7878
),
7979
):
80-
BigQueryCredentials(client_id="test_client_id")
80+
BigQueryCredentialsConfig(client_id="test_client_id")
8181

8282
def test_missing_client_id_raises_error(self):
8383
"""Test that missing client ID raises appropriate validation error."""
@@ -88,7 +88,7 @@ def test_missing_client_id_raises_error(self):
8888
" pair"
8989
),
9090
):
91-
BigQueryCredentials(client_secret="test_client_secret")
91+
BigQueryCredentialsConfig(client_secret="test_client_secret")
9292

9393
def test_empty_configuration_raises_error(self):
9494
"""Test that completely empty configuration is rejected.
@@ -103,4 +103,4 @@ def test_empty_configuration_raises_error(self):
103103
" pair"
104104
),
105105
):
106-
BigQueryCredentials()
106+
BigQueryCredentialsConfig()

0 commit comments

Comments
 (0)