Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
833312e
feat(adms): add SAP ADMS module with sync/async clients and BDD tests
adwitiyasushant May 26, 2026
13e5a5d
chore: bump version to 0.20.0 for ADMS module
adwitiyasushant May 26, 2026
197ed7b
chore(adms): scope pytest config to ADMS and extract mount-path constant
adwitiyasushant May 26, 2026
74a9909
chore: bump version to 0.20.1 after rebase onto upstream
adwitiyasushant May 26, 2026
9d9aba2
fix(adms/tests): mark async http client tests with pytest.mark.asyncio
adwitiyasushant May 26, 2026
d18d875
docs(adms): replace stale "DMS" references with "ADMS" in module docs…
adwitiyasushant May 27, 2026
d66078f
docs(adms): document integration test env vars and fix wrong field names
adwitiyasushant May 27, 2026
6d85c69
refactor(adms): address PR review feedback
adwitiyasushant May 29, 2026
b7d37e5
docs(adms): merge ADMS integration test docs into canonical guide
adwitiyasushant May 29, 2026
c06d43d
chore(adms): drop scripts/adms_cli.py from repo
adwitiyasushant May 29, 2026
5e43295
refactor(adms): align integration tests with standard CLOUD_SDK_CFG_*…
adwitiyasushant May 29, 2026
27a9ccc
fix(adms/tests): build concurrent coroutines inside the run_async loop
adwitiyasushant May 29, 2026
75f7736
docs(adms): replace personal name with generic placeholder in config …
adwitiyasushant May 29, 2026
5c0fa30
docs(adms): replace internal provisioning references in module docstring
adwitiyasushant May 29, 2026
7ad3b54
refactor(adms): properly type IasTokenFetcher.config parameter
adwitiyasushant May 29, 2026
a25bb0c
refactor(adms): remove duplicate HttpError import in _http.py
adwitiyasushant May 29, 2026
f32b705
style(adms): sort ScanStatus enum members alphabetically
adwitiyasushant May 29, 2026
3ee3da3
style(adms): sort JobType and JobStatus enum members alphabetically
adwitiyasushant May 29, 2026
76015c2
refactor(adms): import TokenCache from public core.auth path
adwitiyasushant May 29, 2026
d445fb9
docs(adms): minor wording cleanups in _models and user-guide
adwitiyasushant May 29, 2026
02a3df7
refactor(core/auth): rename mTLS* classes and drop unused client param
adwitiyasushant May 29, 2026
f13b833
refactor(core/auth): remove private constants from public __all__
adwitiyasushant May 29, 2026
ac41b57
Merge remote-tracking branch 'upstream/main' into contrib/adms
adwitiyasushant May 29, 2026
a2a9446
fix(ci): narrow async closure types and bump version to 0.22.0
adwitiyasushant May 29, 2026
042dde7
docs(adms): add docstrings to async API methods and align module naming
adwitiyasushant May 29, 2026
af71018
docs(core/auth): generalize mTLS module docstring wording
adwitiyasushant May 29, 2026
5e373dc
docs(adms): fix AdmsConfig parameter names in user-guide example
adwitiyasushant May 29, 2026
52e870f
Merge branch 'main' into contrib/adms
adwitiyasushant May 29, 2026
ac2794d
chore: bump version to 0.23.0
adwitiyasushant May 29, 2026
886b5f5
refactor(adms): drop redundant TokenCache re-export
adwitiyasushant May 29, 2026
b0e1089
chore(core/auth): add py.typed marker for PEP 561
adwitiyasushant Jun 2, 2026
16a6ec8
fix(adms): retry once on 403 with fresh CSRF token
adwitiyasushant Jun 2, 2026
12baf5c
fix(adms): share httpx client in with_user_jwt to prevent connection …
adwitiyasushant Jun 2, 2026
8c13585
refactor(core/auth): extract IAS token request timeout to named constant
adwitiyasushant Jun 2, 2026
ec9a502
refactor(adms): tidy __all__ ordering and drop defensive getattr
adwitiyasushant Jun 2, 2026
da70d1c
fix(adms): add use_admin_service parameter to async get_status
adwitiyasushant Jun 2, 2026
49e17e6
fix(adms): escape OData V4 string keys to prevent injection
adwitiyasushant Jun 2, 2026
0aa92bc
fix(adms): guard CSRF token cache for thread/coroutine safety
adwitiyasushant Jun 2, 2026
d31d1ea
fix(core/adms): harden error handling in IAS, async HTTP, and CSRF paths
adwitiyasushant Jun 2, 2026
43b4271
fix(core/auth): log Redis cache failures at WARNING
adwitiyasushant Jun 2, 2026
7a2ed1d
docs(adms/models): add docstrings to public to_odata_dict methods
adwitiyasushant Jun 2, 2026
61fe629
refactor(adms): drop broad except in factories, simplify _BindingData
adwitiyasushant Jun 2, 2026
9d7b5ca
refactor(core/auth): add explicit close() and context manager to MTLS…
adwitiyasushant Jun 2, 2026
d3db579
test(core,adms): cover aclose idempotency, exception-path cleanup, OB…
adwitiyasushant Jun 2, 2026
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
8 changes: 8 additions & 0 deletions .env_integration_tests.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ CLOUD_SDK_CFG_SDM_DEFAULT_UAA='{"url":"https://your-auth-url","clientid":"your-c
CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_APPLICATION_URL=https://your-agent-memory-api-url-here
CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_UAA='{"url":"https://your-auth-url","clientid":"your-client-id","clientsecret":"your-client-secret"}'

# ADMS (Advanced Document Management Service) — integration tests against
# a deployed ADM instance. Tests are skipped when any of these are missing.
CLOUD_SDK_CFG_ADMS_DEFAULT_CLIENTID=your-adms-client-id-here
CLOUD_SDK_CFG_ADMS_DEFAULT_CLIENTSECRET=your-adms-client-secret-here
CLOUD_SDK_CFG_ADMS_DEFAULT_URL=https://your-tenant.accounts.ondemand.com
CLOUD_SDK_CFG_ADMS_DEFAULT_URI=https://your-adm-host.cfapps.eu20.hana.ondemand.com
CLOUD_SDK_CFG_ADMS_DEFAULT_RESOURCE=urn:sap:identity:application:provider:name:your-adm-app-name

APPFND_CONHOS_LANDSCAPE=your-landscape-here
TENANT_SUBDOMAIN=your-tenant-subdomain-here
AGW_USER_TOKEN=your-user-jwt-here
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,13 @@ mocks/

# Generated files
PULL_REQUEST.md
RELEASE.md

# macOS metadata
.DS_Store

# UCL provisioning artefacts (separate repo concern)
.ucl-provision/
src/sap_cloud_sdk/adms/ucl/
RELEASE.md
.env.adms
scripts/adms_cli.py
16 changes: 16 additions & 0 deletions docs/INTEGRATION_TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,21 @@ CLOUD_SDK_CFG_DATA_ANONYMIZATION_DEFAULT_DESTINATION_NAME=your-client-certificat

The destination must be configured with `ClientCertificateAuthentication` and reference a certificate bundle containing the client certificate and private key.

### ADMS Integration Tests

For ADMS (Advanced Document Management Service) integration tests, configure the following variables in `.env_integration_tests`:

```bash
# ADMS Configuration
CLOUD_SDK_CFG_ADMS_DEFAULT_URL=https://your-tenant.accounts.ondemand.com
CLOUD_SDK_CFG_ADMS_DEFAULT_URI=https://your-adm-instance.cfapps.eu20.hana.ondemand.com
CLOUD_SDK_CFG_ADMS_DEFAULT_CLIENTID=your-ias-client-id
CLOUD_SDK_CFG_ADMS_DEFAULT_CLIENTSECRET=your-ias-client-secret
CLOUD_SDK_CFG_ADMS_DEFAULT_RESOURCE=urn:sap:identity:application:provider:name:your-app
```

`CLOUD_SDK_CFG_ADMS_DEFAULT_URI` points the tests at the target ADM service. The other `CLOUD_SDK_CFG_ADMS_DEFAULT_*` variables hold the IAS service-binding credentials used by the SDK to fetch Bearer tokens. Tests are skipped automatically when any of these are missing.

### Agent Gateway Integration Tests

Agent Gateway integration tests use the LoB agent flow via the Destination Service. Configure the following variables in `.env_integration_tests`:
Expand Down Expand Up @@ -131,6 +146,7 @@ uv run pytest tests/core/integration/data_anonymization -v
uv run pytest tests/objectstore/integration/ -v
uv run pytest tests/destination/integration/ -v
uv run pytest tests/agent_memory/integration/ -v
uv run pytest tests/adms/integration/ -v
uv run pytest tests/agentgateway/integration/ -v
```

Expand Down
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.22.0"
version = "0.23.0"
description = "SAP Cloud SDK for Python"
readme = "README.md"
license = "Apache-2.0"
Expand Down
131 changes: 131 additions & 0 deletions src/sap_cloud_sdk/adms/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""SAP Cloud SDK for Python — ADMS (Advanced Document Management Service) module.

Provides a typed, high-level Python client for the SAP ADM OData V4 service.

ADM is a **BTP Shared SaaS Application** (IAS-based multi-tenant service).
It must be provisioned as a BTP service instance before use.

Quick start::

from sap_cloud_sdk.adms import (
create_client,
BaseType,
CreateDocumentInput,
CreateDocumentRelationInput,
)

# Reads binding from /etc/secrets/appfnd/adms/default/ or env vars
client = create_client("default")

# Link a document to a business object
relation = client.relations.create(
CreateDocumentRelationInput(
business_object_node_type_unique_id="PurchaseOrder",
host_business_object_node_id="PO-4500012345",
document=CreateDocumentInput(
document_name="Invoice.pdf",
document_base_type=BaseType.DOCUMENT,
document_type_id="INVOICE",
),
is_active_entity=False,
)
)
# Upload bytes to presigned URL (outside SDK)
import requests
requests.put(relation.document.document_content_upload_urls[0], data=open("f.pdf","rb"))
"""

from __future__ import annotations

from sap_cloud_sdk.adms.client import (
AdmsClient,
AsyncAdmsClient,
create_client,
create_async_client,
)
from sap_cloud_sdk.adms.config import AdmsConfig
from sap_cloud_sdk.adms.exceptions import (
AuthError,
ClientCreationError,
ConfigError,
AdmsError,
AdmsOperationError,
DocumentNotFoundError,
HttpError,
ScanNotCleanError,
)
from sap_cloud_sdk.adms._models import (
AllowedDomain,
BaseType,
BusinessObjectNodeType,
CreateAllowedDomainInput,
CreateBusinessObjectNodeTypeInput,
CreateDocumentTypeBoTypeMapInput,
CreateDocumentInput,
CreateDocumentRelationInput,
CreateDocumentTypeInput,
DeleteUserDataJobParameters,
Document,
DocumentContentVersion,
DocumentRelation,
DocumentType,
DocumentTypeBusinessObjectTypeMap,
DocumentTypeText,
DraftActivateInput,
DraftInput,
JobInput,
JobOutput,
JobStatus,
JobType,
ScanStatus,
UpdateDocumentInput,
ZipDownloadJobParameters,
)


__all__ = [
# factories
"create_client",
"create_async_client",
# clients
"AdmsClient",
"AsyncAdmsClient",
# config
"AdmsConfig",
# exceptions
"AdmsError",
"AdmsOperationError",
"AuthError",
"ClientCreationError",
"ConfigError",
"DocumentNotFoundError",
"HttpError",
"ScanNotCleanError",
# models — core
"BaseType",
"CreateDocumentInput",
"CreateDocumentRelationInput",
"DeleteUserDataJobParameters",
"Document",
"DocumentContentVersion",
"DocumentRelation",
"DraftActivateInput",
"DraftInput",
"JobInput",
"JobOutput",
"JobStatus",
"JobType",
"ScanStatus",
"UpdateDocumentInput",
"ZipDownloadJobParameters",
# models — config
"AllowedDomain",
"BusinessObjectNodeType",
"CreateAllowedDomainInput",
"CreateBusinessObjectNodeTypeInput",
"CreateDocumentTypeBoTypeMapInput",
"CreateDocumentTypeInput",
"DocumentType",
"DocumentTypeBusinessObjectTypeMap",
"DocumentTypeText",
]
75 changes: 75 additions & 0 deletions src/sap_cloud_sdk/adms/_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""IAS token management for the ADMS module — thin ADMS adapter over core auth.

All token-fetching logic lives in :mod:`sap_cloud_sdk.core.auth._ias_fetcher`.
This module provides ADMS-specific wrappers that:

* Accept :class:`~sap_cloud_sdk.adms.config.AdmsConfig` instead of raw URL/credentials.
* Re-raise :class:`~sap_cloud_sdk.core.auth.AuthError` as ADMS's own
:class:`~sap_cloud_sdk.adms.exceptions.AuthError` (a subclass of
``AdmsError``) so that callers using ``except AdmsError`` still catch auth failures.

The public symbols exported here match what the existing ADMS unit-tests import,
so no test changes are required.
"""

from __future__ import annotations

import requests

# Core implementations — real logic lives here
from sap_cloud_sdk.core.auth import (
IasTokenFetcher as _CoreIasTokenFetcher,
AuthError as _CoreAuthError,
TokenCache,
)
from sap_cloud_sdk.adms.config import AdmsConfig
from sap_cloud_sdk.adms.exceptions import AuthError

__all__ = [
"IasTokenFetcher",
]


class IasTokenFetcher(_CoreIasTokenFetcher):
"""ADMS-flavoured IAS token fetcher that accepts :class:`AdmsConfig`.

Inherits all caching / fetching logic from the core layer. Converts
:class:`~sap_cloud_sdk.core.auth.AuthError` to
:class:`~sap_cloud_sdk.adms.exceptions.AuthError` (a ``AdmsError`` subclass)
so existing ``except AdmsError / AuthError`` handlers are unaffected.

Args:
config: :class:`~sap_cloud_sdk.adms.config.AdmsConfig` with IAS credentials.
session: Optional ``requests.Session`` to reuse (useful for testing).
cache: Pluggable :class:`~sap_cloud_sdk.core.auth.TokenCache`.
Defaults to :class:`~sap_cloud_sdk.core.auth.InMemoryTokenCache`.
Pass a :class:`~sap_cloud_sdk.core.auth.RedisTokenCache` for
multi-instance deployments.
"""

def __init__(
self,
config: AdmsConfig,
session: requests.Session | None = None,
cache: TokenCache | None = None,
) -> None:
super().__init__(
ias_url=config.ias_url,
client_id=config.client_id,
client_secret=config.client_secret,
session=session,
cache=cache,
resource=config.resource,
)

def get_token(self) -> str:
try:
return super().get_token()
except _CoreAuthError as exc:
raise AuthError(str(exc)) from exc

def exchange_token(self, user_jwt: str) -> str:
try:
return super().exchange_token(user_jwt)
except _CoreAuthError as exc:
raise AuthError(str(exc)) from exc
Loading
Loading