Skip to content

Commit 0d10885

Browse files
committed
Merge upstream/main — resolve conflicts, bump version to 0.44.0
- pyproject.toml: version 0.44.0 (minor bump above upstream 0.43.0 for new dpi_ng module); keep python-odata (dpi_ng dep) and add cryptography (upstream added) - test_operation.py: count 252 (249 + 3 new output_management ops from upstream) - test_module.py: count 16 (upstream added OUTPUT_MANAGEMENT module)
2 parents e07a50c + 5057100 commit 0d10885

78 files changed

Lines changed: 7370 additions & 848 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env_integration_tests.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,11 @@ CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_HOST=your-objectstore-host-here
8080
CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_ACCESS_KEY_ID=your-access-key-id-here
8181
CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_SECRET_ACCESS_KEY=your-secret-access-key-here
8282
CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_BUCKET=your-bucket-name-here
83+
84+
# OUTPUT MANAGEMENT
85+
# For cloud mode (destination-based):
86+
CLOUD_SDK_OMS_DESTINATION_NAME=your-output-management-destination-name-here
87+
CLOUD_SDK_OMS_ACCESS_STRATEGY=PROVIDER_ONLY
88+
CLOUD_SDK_OMS_INSTANCE=default
89+
# For local mode (no credentials needed):
90+
# CLOUD_SDK_OMS_TEST_MODE=local

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ repos:
99
hooks:
1010
- id: ruff-format
1111
name: ruff format
12-
entry: uvx ruff format --force-exclude --check
12+
entry: uv run ruff format --force-exclude --check
1313
language: system
1414
types: [python]
1515
- id: ruff-check
1616
name: ruff check
17-
entry: uvx ruff check --force-exclude --fix
17+
entry: uv run ruff check --force-exclude --fix
1818
language: system
1919
types: [python]
2020
- id: ty-check
2121
name: ty type check
22-
entry: uvx ty check .
22+
entry: uv run ty check .
2323
language: system
2424
types: [python]
2525
pass_filenames: false

docs/DEVELOPMENT.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Tip:
3434
## Type Check
3535

3636
```bash
37-
uvx ty check .
37+
uv run ty check .
3838
```
3939

4040
## Code Quality Checks
@@ -49,9 +49,12 @@ uv run ruff check .
4949
uv run ruff format --check .
5050

5151
# Type check
52-
uvx ty check .
52+
uv run ty check .
5353
```
5454

55+
> **Linter versions are pinned.** `ruff` and `ty` are pinned to exact versions in the
56+
> `pyproject.toml` `[dependency-groups] dev` section (and locked in `uv.lock`) for consistent runs across local and CI.
57+
5558
## Pre-commit Hooks (Recommended)
5659

5760
Automatically run quality checks before each commit using pre-commit hooks.

pyproject.toml

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "sap-cloud-sdk"
3-
version = "0.40.0"
3+
version = "0.44.0"
44
description = "SAP Cloud SDK for Python"
55
readme = "README.md"
66
license = "Apache-2.0"
@@ -10,34 +10,35 @@ authors = [
1010
requires-python = ">=3.11"
1111
dependencies = [
1212
"minio~=7.2.16",
13-
"setuptools~=80.9.0",
13+
"setuptools>=83.0",
1414
"requests>=2.33.0",
1515
"requests-oauthlib~=2.0.0",
1616
"pydantic~=2.12.3",
1717
"hatchling~=1.27.0",
18-
"opentelemetry-exporter-otlp-proto-grpc~=1.42.1",
19-
"opentelemetry-exporter-otlp-proto-http~=1.42.1",
18+
"opentelemetry-exporter-otlp-proto-grpc~=1.43.0",
19+
"opentelemetry-exporter-otlp-proto-http~=1.43.0",
2020
"traceloop-sdk~=0.61.0",
2121
"opentelemetry-instrumentation-langchain>=0.61.0",
2222
"httpx>=0.27.0",
2323
"PyJWT>=2.13.0",
24-
"protobuf>=4.25.0",
24+
"protobuf>=7.0.0",
2525
"protovalidate>=0.13.0",
2626
"grpcio>=1.60.0",
27-
"opentelemetry-api>=1.42.1",
28-
"opentelemetry-sdk>=1.42.1",
29-
"opentelemetry-instrumentation-httpx~=0.63b1",
30-
"opentelemetry-instrumentation-requests~=0.63b1",
31-
"opentelemetry-instrumentation-grpc~=0.63b1",
32-
"opentelemetry-instrumentation-logging~=0.63b1",
33-
"opentelemetry-instrumentation-starlette~=0.63b1",
34-
"opentelemetry-instrumentation-fastapi~=0.63b1",
35-
"opentelemetry-instrumentation-aiohttp-client~=0.63b1",
36-
"opentelemetry-instrumentation-sqlalchemy~=0.63b1",
37-
"opentelemetry-instrumentation-django~=0.63b1",
38-
"opentelemetry-instrumentation-flask~=0.63b1",
27+
"opentelemetry-api>=1.43.0",
28+
"opentelemetry-sdk>=1.43.0",
29+
"opentelemetry-instrumentation-httpx~=0.64b0",
30+
"opentelemetry-instrumentation-requests~=0.64b0",
31+
"opentelemetry-instrumentation-grpc~=0.64b0",
32+
"opentelemetry-instrumentation-logging~=0.64b0",
33+
"opentelemetry-instrumentation-starlette~=0.64b0",
34+
"opentelemetry-instrumentation-fastapi~=0.64b0",
35+
"opentelemetry-instrumentation-aiohttp-client~=0.64b0",
36+
"opentelemetry-instrumentation-sqlalchemy~=0.64b0",
37+
"opentelemetry-instrumentation-django~=0.64b0",
38+
"opentelemetry-instrumentation-flask~=0.64b0",
3939
"mcp>=1.1.0",
4040
"python-odata>=0.7.0",
41+
"cryptography>=46.0.3",
4142
]
4243

4344
[project.optional-dependencies]
@@ -64,9 +65,9 @@ dev = [
6465
"pytest-asyncio>=1.0.0",
6566
"pytest-bdd>=7.2.0",
6667
"python-dotenv>=1.0.0",
67-
"ty>=0.0.21",
68+
"ty==0.0.64",
6869
"cryptography>=46.0.3",
69-
"ruff>=0.8.0",
70+
"ruff==0.16.0",
7071
"starlette>=0.40.0",
7172
"anyio>=3.6.2",
7273
"httpx>=0.27.0",

src/sap_cloud_sdk/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# SAP Cloud SDK for Python
22

3-
from sap_cloud_sdk.core.bootstrap import bootstrap
3+
from sap_cloud_sdk.core.bootstrap import bootstrap, TelemetryConfig
44

5-
__all__ = ["bootstrap"]
5+
__all__ = ["bootstrap", "TelemetryConfig"]

src/sap_cloud_sdk/adms/user-guide.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ relation = client.relations.create(
4545

4646
# Upload bytes to the presigned URL (outside SDK)
4747
import requests
48+
4849
upload_url = relation.document.document_content_upload_urls[0]
4950
requests.put(upload_url, data=open("Invoice.pdf", "rb"))
5051
```
@@ -82,6 +83,7 @@ client = create_client(user_jwt=request.headers["Authorization"].split()[1])
8283
```python
8384
from sap_cloud_sdk.adms import create_client, TokenCache
8485

86+
8587
# By default tokens are cached in-process via InMemoryTokenCache.
8688
# For multi-instance deployments (Kyma replicas > 1, CF instances > 1),
8789
# implement your own TokenCache subclass backed by the shared cache your
@@ -91,13 +93,21 @@ class MySharedCache(TokenCache):
9193
def set(self, key, token, ttl_seconds): ...
9294
def delete(self, key): ...
9395

96+
9497
client = create_client(token_cache=MySharedCache())
9598
```
9699

97100
## Async Client
98101

99102
```python
100-
from sap_cloud_sdk.adms import create_async_client, BaseType, CreateDocumentInput, CreateDocumentRelationInput, RelationQueryOptions
103+
from sap_cloud_sdk.adms import (
104+
create_async_client,
105+
BaseType,
106+
CreateDocumentInput,
107+
CreateDocumentRelationInput,
108+
RelationQueryOptions,
109+
)
110+
101111

102112
async def main():
103113
async with create_async_client() as client:
@@ -203,6 +213,7 @@ job = client.jobs.start_zip_download(params)
203213

204214
# Poll until terminal state
205215
import time
216+
206217
while not job.job_status or not job.job_status.is_terminal():
207218
time.sleep(2)
208219
job = client.jobs.get_status(job.job_id)

src/sap_cloud_sdk/agent_decorators/user-guide.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ from sap_cloud_sdk.agent_decorators import (
2121
```python
2222
from sap_cloud_sdk.agent_decorators import prompt_section, agent_model
2323

24+
2425
# Define a prompt with a coded default
2526
@prompt_section(
2627
key="prompts.system",
@@ -30,6 +31,7 @@ from sap_cloud_sdk.agent_decorators import prompt_section, agent_model
3031
def system_prompt() -> str:
3132
return "You are a helpful assistant."
3233

34+
3335
# Define the model selection
3436
@agent_model(key="config.model", label="LLM Model")
3537
def model_name() -> str:
@@ -45,6 +47,7 @@ Expose a prompt section for editing.
4547
```python
4648
from sap_cloud_sdk.agent_decorators import prompt_section
4749

50+
4851
@prompt_section(
4952
key="prompts.identity",
5053
label="Agent Identity",
@@ -62,6 +65,7 @@ Expose a configuration value for editing.
6265
```python
6366
from sap_cloud_sdk.agent_decorators import agent_config
6467

68+
6569
@agent_config(
6670
key="config.temperature",
6771
label="Temperature",
@@ -78,6 +82,7 @@ Expose a model selection. The `description` parameter is optional.
7882
```python
7983
from sap_cloud_sdk.agent_decorators import agent_model
8084

85+
8186
@agent_model(
8287
key="config.model",
8388
label="Default Model",
@@ -93,6 +98,7 @@ def default_model() -> str:
9398
from sap_cloud_sdk.agent_decorators.exceptions import AgentDecoratorError
9499

95100
try:
101+
96102
@prompt_section(key="", label="L", description="D")
97103
def bad():
98104
return ""

src/sap_cloud_sdk/agent_memory/user-guide.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,7 @@ elif total <= PAGE_SIZE:
714714
agent_id="my-agent", invoker_id="user-123", limit=total
715715
)
716716
else:
717+
717718
def iter_all_memories(client, agent_id, invoker_id, page_size=PAGE_SIZE):
718719
offset = 0
719720
while True:
@@ -781,7 +782,8 @@ The default `threshold` of `0.6` may be too strict for your data. Try a lower va
781782
782783
```python
783784
results = client.search_memories(
784-
agent_id="my-agent", invoker_id="user-123",
785+
agent_id="my-agent",
786+
invoker_id="user-123",
785787
query="user display preferences",
786788
threshold=0.3,
787789
)
@@ -933,10 +935,10 @@ from sap_cloud_sdk.agent_memory.factory.langgraph_checkpoint import create_check
933935
key="config.checkpointer.ttl_seconds",
934936
label="Thread TTL (seconds)",
935937
description="Evict inactive conversation threads after this period of "
936-
"inactivity. Set to 0 to disable eviction.",
938+
"inactivity. Set to 0 to disable eviction.",
937939
)
938940
def thread_ttl_seconds() -> int:
939-
return 3600 # 1 hour
941+
return 3600 # 1 hour
940942
941943
942944
class MyAgent:

src/sap_cloud_sdk/agentgateway/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
from sap_cloud_sdk.agentgateway._models import (
5656
AuthResult,
5757
MCPTool,
58+
MCPToolFilter,
5859
Agent,
5960
AgentCard,
6061
AgentCardFilter,
@@ -78,6 +79,7 @@
7879
# Data models
7980
"AuthResult",
8081
"MCPTool",
82+
"MCPToolFilter",
8183
"Agent",
8284
"AgentCard",
8385
"AgentCardFilter",

src/sap_cloud_sdk/agentgateway/_customer.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
CustomerCredentials,
3333
IntegrationDependency,
3434
MCPTool,
35+
MCPToolFilter,
3536
)
3637
from sap_cloud_sdk.agentgateway._token_cache import _TokenCache
3738
from sap_cloud_sdk.agentgateway.exceptions import AgentGatewaySDKError
@@ -642,7 +643,7 @@ async def _list_server_tools(
642643
async with streamable_http_client(url, http_client=http_client) as (
643644
read,
644645
write,
645-
_,
646+
*_,
646647
):
647648
async with ClientSession(read, write) as session:
648649
init_result = await session.initialize()
@@ -672,10 +673,30 @@ async def _list_server_tools(
672673
]
673674

674675

676+
def _log_mcp_server_error(ord_id: str, exc: BaseException) -> None:
677+
# Unwrap ExceptionGroup from anyio to surface the real HTTP error body
678+
if isinstance(exc, BaseExceptionGroup):
679+
for inner in exc.exceptions:
680+
_log_mcp_server_error(ord_id, inner)
681+
return
682+
if isinstance(exc, httpx.HTTPStatusError):
683+
logger.error(
684+
"Failed to load tools from %s (HTTP %d): %s",
685+
ord_id,
686+
exc.response.status_code,
687+
exc.response.text[:500],
688+
)
689+
else:
690+
logger.exception(
691+
"Failed to load tools from %s — skipping", ord_id, exc_info=exc
692+
)
693+
694+
675695
async def get_mcp_tools_customer(
676696
credentials: CustomerCredentials,
677697
system_token: str,
678698
timeout: float,
699+
filter: MCPToolFilter | None = None,
679700
) -> list[MCPTool]:
680701
"""List all MCP tools from servers defined in credentials.
681702
@@ -686,19 +707,24 @@ async def get_mcp_tools_customer(
686707
credentials: Customer credentials with integrationDependencies.
687708
system_token: Pre-fetched raw system token for authentication.
688709
timeout: HTTP timeout in seconds for MCP server calls.
710+
filter: Optional MCPToolFilter narrowing results by tool name or ORD ID.
711+
If None or empty, all tools are included.
689712
690713
Returns:
691714
List of MCPTool objects from all servers.
692-
693-
Raises:
694-
AgentGatewaySDKError: If integrationDependencies is empty.
695715
"""
716+
f = filter or MCPToolFilter()
696717
dependencies = credentials.integration_dependencies
697718

698719
if not dependencies:
699-
raise AgentGatewaySDKError(
720+
logger.warning(
700721
"integrationDependencies is empty in credentials — no MCP servers configured."
701722
)
723+
return []
724+
725+
if f.ord_ids:
726+
ord_ids_set = set(f.ord_ids)
727+
dependencies = [d for d in dependencies if d.ord_id in ord_ids_set]
702728

703729
logger.info("Discovering tools from %d MCP server(s)", len(dependencies))
704730

@@ -717,8 +743,13 @@ async def get_mcp_tools_customer(
717743
server_tools = await _list_server_tools(url, system_token, timeout)
718744
tools.extend(server_tools)
719745
logger.debug("Loaded %d tool(s) from %s", len(server_tools), dep.ord_id)
720-
except Exception:
721-
logger.exception("Failed to load tools from %s — skipping", dep.ord_id)
746+
except Exception as exc:
747+
_log_mcp_server_error(dep.ord_id, exc)
748+
749+
# Post-fetch filter: tool names are only known after fetching
750+
if f.names:
751+
names_set = set(f.names)
752+
tools = [t for t in tools if t.name in names_set]
722753

723754
logger.info(
724755
"Loaded %d MCP tool(s) from %d server(s)", len(tools), len(dependencies)
@@ -758,7 +789,7 @@ async def call_mcp_tool_customer(
758789
async with streamable_http_client(tool.url, http_client=http_client) as (
759790
read,
760791
write,
761-
_,
792+
*_,
762793
):
763794
async with ClientSession(read, write) as session:
764795
await session.initialize()
@@ -769,4 +800,9 @@ async def call_mcp_tool_customer(
769800
return ""
770801

771802
first = result.content[0]
772-
return str(getattr(first, "text", ""))
803+
text = str(getattr(first, "text", ""))
804+
805+
if result.isError:
806+
logger.error("Tool '%s' returned an error: %s", tool.name, text)
807+
808+
return text

0 commit comments

Comments
 (0)