diff --git a/pyproject.toml b/pyproject.toml index b17c29ff21..be9f7afa8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ dependencies = [ "google-cloud-storage>=2.18.0, <4.0.0", # For GCS Artifact service "google-genai>=1.56.0, <2.0.0", # Google GenAI SDK "graphviz>=0.20.2, <1.0.0", # Graphviz for graph rendering + "httpx>=0.27.0, <1.0.0", # HTTP client library "jsonschema>=4.23.0, <5.0.0", # Agent Builder config validation "mcp>=1.10.0, <2.0.0", # For MCP Toolset "opentelemetry-api>=1.37.0, <=1.37.0", # OpenTelemetry - limit upper version for sdk and api to not risk breaking changes from unstable _logs package. diff --git a/src/google/adk/tools/openapi_tool/auth/auth_helpers.py b/src/google/adk/tools/openapi_tool/auth/auth_helpers.py index 197b7fac11..d7be471ed9 100644 --- a/src/google/adk/tools/openapi_tool/auth/auth_helpers.py +++ b/src/google/adk/tools/openapi_tool/auth/auth_helpers.py @@ -26,9 +26,9 @@ from fastapi.openapi.models import OAuth2 from fastapi.openapi.models import OpenIdConnect from fastapi.openapi.models import Schema +import httpx from pydantic import BaseModel from pydantic import ValidationError -import requests from ....auth.auth_credential import AuthCredential from ....auth.auth_credential import AuthCredentialTypes @@ -287,14 +287,14 @@ def openid_url_to_scheme_credential( Raises: ValueError: If the OpenID URL is invalid, fetching fails, or required fields are missing. - requests.exceptions.RequestException: If there's an error during the + httpx.HTTPStatusError or httpx.RequestError: If there's an error during the HTTP request. """ try: - response = requests.get(openid_url, timeout=10) + response = httpx.get(openid_url, timeout=10) response.raise_for_status() config_dict = response.json() - except requests.exceptions.RequestException as e: + except httpx.RequestError as e: raise ValueError( f"Failed to fetch OpenID configuration from {openid_url}: {e}" ) from e diff --git a/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py b/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py index 5c27b16851..55328b4ea7 100644 --- a/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +++ b/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py @@ -27,7 +27,7 @@ from fastapi.openapi.models import Operation from fastapi.openapi.models import Schema from google.genai.types import FunctionDeclaration -import requests +import httpx from typing_extensions import override from ....agents.readonly_context import ReadonlyContext @@ -299,7 +299,7 @@ def _prepare_request_params( Returns: A dictionary containing the request parameters for the API call. This - initializes a requests.request() call. + initializes an httpx.AsyncClient.request() call. Example: self._prepare_request_params({"input_id": "test-id"}) @@ -477,13 +477,13 @@ async def call( if provider_headers: request_params.setdefault("headers", {}).update(provider_headers) - response = requests.request(**request_params) + response = await _request(**request_params) # Parse API response try: - response.raise_for_status() # Raise HTTPError for bad responses + response.raise_for_status() # Raise HTTPStatusError for bad responses return response.json() # Try to decode JSON - except requests.exceptions.HTTPError: + except httpx.HTTPStatusError: error_details = response.content.decode("utf-8") return { "error": ( @@ -509,3 +509,8 @@ def __repr__(self): f' auth_scheme="{self.auth_scheme}",' f' auth_credential="{self.auth_credential}")' ) + + +async def _request(**request_params) -> httpx.Response: + async with httpx.AsyncClient() as client: + return await client.request(**request_params) diff --git a/tests/unittests/tools/openapi_tool/auth/test_auth_helper.py b/tests/unittests/tools/openapi_tool/auth/test_auth_helper.py index af7bf4f6d0..7b9777cbc3 100644 --- a/tests/unittests/tools/openapi_tool/auth/test_auth_helper.py +++ b/tests/unittests/tools/openapi_tool/auth/test_auth_helper.py @@ -36,8 +36,8 @@ from google.adk.tools.openapi_tool.auth.auth_helpers import service_account_dict_to_scheme_credential from google.adk.tools.openapi_tool.auth.auth_helpers import service_account_scheme_credential from google.adk.tools.openapi_tool.auth.auth_helpers import token_to_scheme_credential +import httpx import pytest -import requests def test_token_to_scheme_credential_api_key_header(): @@ -272,7 +272,7 @@ def test_openid_dict_to_scheme_credential_missing_credential_fields(): openid_dict_to_scheme_credential(config_dict, scopes, credential_dict) -@patch("requests.get") +@patch("httpx.get") def test_openid_url_to_scheme_credential(mock_get): mock_response = { "authorization_endpoint": "auth_url", @@ -303,7 +303,7 @@ def test_openid_url_to_scheme_credential(mock_get): mock_get.assert_called_once_with("openid_url", timeout=10) -@patch("requests.get") +@patch("httpx.get") def test_openid_url_to_scheme_credential_no_openid_url(mock_get): mock_response = { "authorization_endpoint": "auth_url", @@ -326,9 +326,11 @@ def test_openid_url_to_scheme_credential_no_openid_url(mock_get): assert scheme.openIdConnectUrl == "openid_url" -@patch("requests.get") +@patch("httpx.get") def test_openid_url_to_scheme_credential_request_exception(mock_get): - mock_get.side_effect = requests.exceptions.RequestException("Test Error") + mock_get.side_effect = httpx.HTTPStatusError( + "Test Error", request=None, response=None + ) credential_dict = {"client_id": "client_id", "client_secret": "client_secret"} with pytest.raises( @@ -337,7 +339,7 @@ def test_openid_url_to_scheme_credential_request_exception(mock_get): openid_url_to_scheme_credential("openid_url", [], credential_dict) -@patch("requests.get") +@patch("httpx.get") def test_openid_url_to_scheme_credential_invalid_json(mock_get): mock_get.return_value.json.side_effect = ValueError("Invalid JSON") mock_get.return_value.raise_for_status.return_value = None diff --git a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py index 560813e619..87b77ba96c 100644 --- a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py +++ b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py @@ -35,6 +35,7 @@ from google.adk.tools.tool_context import ToolContext from google.genai.types import FunctionDeclaration from google.genai.types import Schema +import httpx import pytest import requests @@ -201,7 +202,7 @@ def test_get_declaration( assert isinstance(declaration.parameters, Schema) @patch( - "google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool.requests.request" + "google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool._request" ) @pytest.mark.asyncio async def test_call_success( @@ -233,7 +234,7 @@ async def test_call_success( assert result == {"result": "success"} @patch( - "google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool.requests.request" + "google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool._request" ) @pytest.mark.asyncio async def test_call_http_failure(