Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "sap-cloud-sdk"
version = "0.45.1"
version = "0.45.2"
description = "SAP Cloud SDK for Python"
readme = "README.md"
license = "Apache-2.0"
Expand Down
5 changes: 4 additions & 1 deletion src/sap_cloud_sdk/agentgateway/agw_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
MCPTool,
MCPToolFilter,
)
from sap_cloud_sdk.core._tenant import _validate_tenant_subdomain
from sap_cloud_sdk.agentgateway._token_cache import _GatewayUrlCache, _TokenCache
from sap_cloud_sdk.agentgateway.exceptions import AgentGatewaySDKError
from sap_cloud_sdk.core.telemetry import Module, Operation, record_metrics
Expand Down Expand Up @@ -155,10 +156,12 @@ def _resolve_value(

def _resolve_tenant_subdomain(self) -> str:
"""Resolve tenant subdomain from string or callable."""
return self._resolve_value(
resolved_tenant_subdomain = self._resolve_value(
self._tenant_subdomain,
"tenant_subdomain is required for LoB agent flow.",
)
_validate_tenant_subdomain(resolved_tenant_subdomain)
return resolved_tenant_subdomain

@record_metrics(Module.AGENTGATEWAY, Operation.AGENTGATEWAY_GET_SYSTEM_AUTH)
async def get_system_auth(self) -> AuthResult:
Expand Down
4 changes: 2 additions & 2 deletions src/sap_cloud_sdk/agentgateway/user-guide.md
Comment thread
cassiofariasmachado marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,9 @@ Both fields default to empty lists. `agent_names` is applied after fetching; `or
from sap_cloud_sdk.agentgateway import MCPToolFilter

MCPToolFilter(
names=[], # tool names to include (matched against MCPTool.name); empty = no filter
names=[], # tool names to include (matched against MCPTool.name); empty = no filter
ord_ids=[], # ORD IDs to include (extracted from fragment URL for LoB, or matched
# against IntegrationDependency.ord_id for customer agents); empty = no filter
# against IntegrationDependency.ord_id for customer agents); empty = no filter
)
```

Expand Down
20 changes: 20 additions & 0 deletions src/sap_cloud_sdk/core/_tenant.py
Comment thread
cassiofariasmachado marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import re


_SUBDOMAIN_RE = re.compile(r"^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$")


def _validate_tenant_subdomain(tenant_subdomain: str | None) -> None:
"""Validate that *tenant_subdomain* is a single RFC 1123 DNS label.

A valid label contains only ASCII letters, digits, and hyphens, must not
start or end with a hyphen, and is at most 63 characters long.
If *tenant_subdomain* is ``None``, the call is a no-op.

Raises:
ValueError: If *tenant_subdomain* does not match the expected format.
"""
if tenant_subdomain is None:
return
if not _SUBDOMAIN_RE.fullmatch(tenant_subdomain):
raise ValueError(f"Invalid tenant_subdomain: {tenant_subdomain!r}")
13 changes: 7 additions & 6 deletions src/sap_cloud_sdk/core/telemetry/user-guide.md
Comment thread
cassiofariasmachado marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ from litellm import completion

logger = logging.getLogger(__name__)


async def handle_request(query: str, user_id: str):
set_tenant_id("bh7sjh...")

Expand Down Expand Up @@ -435,10 +436,10 @@ The `record_metrics` decorator records request and error counters for any SDK mo
```python
from sap_cloud_sdk.core.telemetry import record_metrics


class MyClient:
@record_metrics("my_module", "my_operation")
def my_method(self):
...
def my_method(self): ...
```

Each call to the decorated method increments `sap.cloud_sdk.capability.requests`. On exception it increments `sap.cloud_sdk.capability.errors` and re-raises. Metrics are emitted only when `OTEL_EXPORTER_OTLP_ENDPOINT` is set — no-op otherwise.
Expand All @@ -450,10 +451,10 @@ For modules that live inside this package, use the `Module` and `Operation` enum
```python
from sap_cloud_sdk.core.telemetry import record_metrics, Module, Operation


class DestinationClient:
@record_metrics(Module.DESTINATION, Operation.DESTINATION_GET_DESTINATION)
def get_destination(self, name: str):
...
def get_destination(self, name: str): ...
```

### Using plain strings (external packages)
Expand All @@ -463,10 +464,10 @@ External packages that depend on `sap-cloud-sdk` can pass plain strings directly
```python
from sap_cloud_sdk.core.telemetry import record_metrics


class MyExternalClient:
@record_metrics("my_module", "my_operation")
def my_method(self):
...
def my_method(self): ...
```

The `Module` enum values are still the canonical form for OSS modules. Plain strings are the extension point for packages that have their own release lifecycle.
Expand Down
9 changes: 4 additions & 5 deletions src/sap_cloud_sdk/destination/_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from sap_cloud_sdk.destination.config import DestinationConfig
from sap_cloud_sdk.destination.exceptions import HttpError
from sap_cloud_sdk.core._tenant import _validate_tenant_subdomain

# API version constants
API_V1 = "v1"
Expand Down Expand Up @@ -64,12 +65,10 @@ def get_token(self, tenant_subdomain: Optional[str] = None) -> str:
token_url = self._config.token_url
identityzone = self._config.identityzone

_validate_tenant_subdomain(tenant_subdomain)

if tenant_subdomain:
try:
token_url = token_url.replace(str(identityzone), tenant_subdomain)
except Exception:
# Fallback to base token_url if replacement fails
token_url = self._config.token_url
token_url = token_url.replace(str(identityzone), tenant_subdomain)

token: Dict[str, Any] = self._session.fetch_token(
token_url=token_url,
Expand Down
3 changes: 3 additions & 0 deletions src/sap_cloud_sdk/dms/_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
DMSPermissionDeniedException,
)
from sap_cloud_sdk.dms.model import DMSCredentials
from sap_cloud_sdk.core._tenant import _validate_tenant_subdomain

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -65,6 +66,8 @@ def get_token(self, tenant_subdomain: Optional[str] = None) -> str:
def _resolve_token_url(self, tenant_subdomain: Optional[str]) -> str:
if not tenant_subdomain:
return self._credentials.token_url
_validate_tenant_subdomain(tenant_subdomain)

logger.debug("Resolving token URL for tenant '%s'", tenant_subdomain)
return self._credentials.token_url.replace(
self._credentials.identityzone,
Expand Down
Loading
Loading