diff --git a/src/sap_cloud_sdk/agentgateway/_customer.py b/src/sap_cloud_sdk/agentgateway/_customer.py index c3b5f289..bc602be0 100644 --- a/src/sap_cloud_sdk/agentgateway/_customer.py +++ b/src/sap_cloud_sdk/agentgateway/_customer.py @@ -59,6 +59,10 @@ _GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials" _GRANT_TYPE_JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer" +# Token response field keys +_TOKEN_FIELD_CLIENT_ID = "client_id" +_TOKEN_FIELD_ACCESS_TOKEN = "access_token" + def _cache_scope_key(credentials: CustomerCredentials) -> str: """Build a cache scope key for customer-flow tokens.""" @@ -147,7 +151,7 @@ def load_customer_credentials_from_env( # Check required environment variables required_vars = { - _INTEGRATION_CLIENT_ID_ENV: "client_id", + _INTEGRATION_CLIENT_ID_ENV: _TOKEN_FIELD_CLIENT_ID, _INTEGRATION_AUTH_URL_ENV: "auth_url", _INTEGRATION_GATEWAY_URL_ENV: "gateway_url", } @@ -200,7 +204,7 @@ def load_customer_credentials(path: str) -> CustomerCredentials: # Credential file uses camelCase, we use snake_case required_fields = { _CredentialFields.TOKEN_SERVICE_URL: "token_service_url", - _CredentialFields.CLIENT_ID: "client_id", + _CredentialFields.CLIENT_ID: _TOKEN_FIELD_CLIENT_ID, _CredentialFields.CERTIFICATE: "certificate", _CredentialFields.PRIVATE_KEY: "private_key", _CredentialFields.GATEWAY_URL: "gateway_url", @@ -324,7 +328,7 @@ def _request_token_mtls( ssl_context = _create_ssl_context(credentials.certificate, credentials.private_key) data = { - "client_id": credentials.client_id, + _TOKEN_FIELD_CLIENT_ID: credentials.client_id, "grant_type": grant_type, "resource": _AGW_RESOURCE_URN, } @@ -363,7 +367,7 @@ def _request_token_mtls( ) token_data = response.json() - access_token = token_data.get("access_token") + access_token = token_data.get(_TOKEN_FIELD_ACCESS_TOKEN) if not access_token: raise AgentGatewaySDKError( @@ -374,7 +378,7 @@ def _request_token_mtls( return token_data except httpx.RequestError as e: - raise AgentGatewaySDKError(f"Token request failed: {e}") + raise AgentGatewaySDKError(f"Token request failed: {e}") from e def _request_token_transparent( @@ -401,7 +405,7 @@ def _request_token_transparent( AgentGatewaySDKError: If token request fails. """ data = { - "client_id": credentials.client_id, + _TOKEN_FIELD_CLIENT_ID: credentials.client_id, "grant_type": grant_type, "resource": _AGW_RESOURCE_URN, } @@ -437,7 +441,7 @@ def _request_token_transparent( ) token_data = response.json() - access_token = token_data.get("access_token") + access_token = token_data.get(_TOKEN_FIELD_ACCESS_TOKEN) if not access_token: raise AgentGatewaySDKError( @@ -448,7 +452,7 @@ def _request_token_transparent( return token_data except httpx.RequestError as e: - raise AgentGatewaySDKError(f"Token request failed: {e}") + raise AgentGatewaySDKError(f"Token request failed: {e}") from e def get_system_token_mtls( @@ -499,7 +503,7 @@ def get_system_token_mtls( extra_data={"response_type": "token"}, ) - access_token = token_data["access_token"] + access_token = token_data[_TOKEN_FIELD_ACCESS_TOKEN] if token_cache: token_cache.set_system_token( @@ -571,7 +575,7 @@ def exchange_user_token( }, ) - access_token = token_data["access_token"] + access_token = token_data[_TOKEN_FIELD_ACCESS_TOKEN] if token_cache: token_cache.set_user_token( diff --git a/src/sap_cloud_sdk/agentgateway/agw_client.py b/src/sap_cloud_sdk/agentgateway/agw_client.py index 1c120adb..349fb740 100644 --- a/src/sap_cloud_sdk/agentgateway/agw_client.py +++ b/src/sap_cloud_sdk/agentgateway/agw_client.py @@ -38,10 +38,12 @@ ) from sap_cloud_sdk.agentgateway._token_cache import _GatewayUrlCache, _TokenCache from sap_cloud_sdk.agentgateway.exceptions import AgentGatewaySDKError -from sap_cloud_sdk.core._telemetry_compat import Module, Operation, record_metrics +from sap_cloud_sdk.core.telemetry import Module, Operation, record_metrics logger = logging.getLogger(__name__) +_LOG_TRANSPARENT_MODE = "Transparent mode credentials detected" + class AgentGatewayClient: """Client for discovering and invoking MCP tools via SAP Agent Gateway. @@ -197,7 +199,7 @@ async def get_system_auth(self) -> AuthResult: # Check for transparent mode if detect_transparent_credentials(): - logger.info("Transparent mode credentials detected") + logger.info(_LOG_TRANSPARENT_MODE) credentials = load_customer_credentials_from_env() loop = asyncio.get_running_loop() token = await loop.run_in_executor( @@ -283,7 +285,7 @@ async def get_user_auth( # Check for transparent mode if detect_transparent_credentials(): - logger.info("Transparent mode credentials detected") + logger.info(_LOG_TRANSPARENT_MODE) credentials = load_customer_credentials_from_env() loop = asyncio.get_running_loop() token = await loop.run_in_executor( @@ -406,7 +408,7 @@ async def list_mcp_tools( # Check for transparent mode if detect_transparent_credentials(): - logger.info("Transparent mode credentials detected") + logger.info(_LOG_TRANSPARENT_MODE) credentials = load_customer_credentials_from_env() return await get_mcp_tools_customer( credentials, auth.access_token, self._config.timeout @@ -560,7 +562,7 @@ async def call_mcp_tool( # Check for transparent mode if detect_transparent_credentials(): - logger.debug("Transparent mode credentials detected") + logger.debug(_LOG_TRANSPARENT_MODE) return await call_mcp_tool_customer( tool, auth.access_token, self._config.timeout, **kwargs diff --git a/src/sap_cloud_sdk/core/_telemetry_compat.py b/src/sap_cloud_sdk/core/_telemetry_compat.py deleted file mode 100644 index cbc4185f..00000000 --- a/src/sap_cloud_sdk/core/_telemetry_compat.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Conditional telemetry imports for modules that use telemetry as optional dependency. - -This module provides a centralized way to handle optional telemetry dependencies. -When telemetry packages are not installed, it provides no-op implementations. -""" - -try: - from sap_cloud_sdk.core.telemetry import Module, Operation, record_metrics - TELEMETRY_AVAILABLE = True -except ImportError: - TELEMETRY_AVAILABLE = False - # Provide no-op implementations when telemetry is not available - Module = None # type: ignore - Operation = None # type: ignore - - def record_metrics(*args, **kwargs): # type: ignore - """No-op decorator when telemetry is not available.""" - def decorator(func): - return func - if args and callable(args[0]): - # Called without parentheses: @record_metrics - return args[0] - # Called with parentheses: @record_metrics(...) - return decorator - - -__all__ = ["Module", "Operation", "record_metrics", "TELEMETRY_AVAILABLE"] diff --git a/src/sap_cloud_sdk/destination/certificate_client.py b/src/sap_cloud_sdk/destination/certificate_client.py index 99591198..de7589c6 100644 --- a/src/sap_cloud_sdk/destination/certificate_client.py +++ b/src/sap_cloud_sdk/destination/certificate_client.py @@ -4,7 +4,7 @@ from typing import List, Optional, TypeVar, Callable -from sap_cloud_sdk.core._telemetry_compat import Module, Operation, record_metrics +from sap_cloud_sdk.core.telemetry import Module, Operation, record_metrics from sap_cloud_sdk.destination._http import DestinationHttp, API_V1 from sap_cloud_sdk.destination._models import ( AccessStrategy, diff --git a/src/sap_cloud_sdk/destination/client.py b/src/sap_cloud_sdk/destination/client.py index 4339de33..d4ffd3e0 100644 --- a/src/sap_cloud_sdk/destination/client.py +++ b/src/sap_cloud_sdk/destination/client.py @@ -6,9 +6,7 @@ import warnings from typing import Any, Dict, List, Optional, Callable, TypeVar -# Conditional telemetry import - works even when telemetry packages are not installed -from sap_cloud_sdk.core._telemetry_compat import Module, Operation, record_metrics - +from sap_cloud_sdk.core.telemetry import Module, Operation, record_metrics from sap_cloud_sdk.core.secret_resolver import read_from_mount_and_fallback_to_env_var from sap_cloud_sdk.destination._http import DestinationHttp, API_V1, API_V2 from sap_cloud_sdk.destination._models import ( diff --git a/src/sap_cloud_sdk/destination/fragment_client.py b/src/sap_cloud_sdk/destination/fragment_client.py index a6eed88f..bd870ef9 100644 --- a/src/sap_cloud_sdk/destination/fragment_client.py +++ b/src/sap_cloud_sdk/destination/fragment_client.py @@ -4,7 +4,7 @@ from typing import Callable, List, Optional, TypeVar -from sap_cloud_sdk.core._telemetry_compat import Module, Operation, record_metrics +from sap_cloud_sdk.core.telemetry import Module, Operation, record_metrics from sap_cloud_sdk.destination._http import DestinationHttp, API_V1 from sap_cloud_sdk.destination._models import ( AccessStrategy, diff --git a/tests/agentgateway/unit/test_lob.py b/tests/agentgateway/unit/test_lob.py index 5ec781c5..53b1c34f 100644 --- a/tests/agentgateway/unit/test_lob.py +++ b/tests/agentgateway/unit/test_lob.py @@ -28,8 +28,9 @@ from sap_cloud_sdk.agentgateway._models import Agent, AgentCard, MCPTool from sap_cloud_sdk.agentgateway._token_cache import _GatewayUrlCache, _TokenCache from sap_cloud_sdk.agentgateway.config import ClientConfig +from sap_cloud_sdk.destination import ConsumptionOptions, ConsumptionLevel from sap_cloud_sdk.agentgateway.exceptions import AgentGatewaySDKError, MCPServerNotFoundError -from sap_cloud_sdk.destination import ConsumptionLevel, ConsumptionOptions +from sap_cloud_sdk.destination import ConsumptionLevel # Aliases for use in existing test assertions _LABEL_KEY = LABEL_KEY @@ -1065,4 +1066,3 @@ def test_raises_when_landscape_env_not_set(self): with patch("sap_cloud_sdk.agentgateway._lob._ias_dest_name", side_effect=EnvironmentError("APPFND_CONHOS_LANDSCAPE not set")): with pytest.raises(EnvironmentError, match="APPFND_CONHOS_LANDSCAPE"): get_ias_client_id_lob() -