Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8b95146
init
JagnathReddy Mar 17, 2026
cbf7209
Merge branch 'SAP:main' into dms-integration
JagnathReddy Mar 17, 2026
33d1e3e
added admin service
JagnathReddy Mar 17, 2026
0ba96b5
clean up
JagnathReddy Mar 17, 2026
11c33af
clean up - minor
JagnathReddy Mar 17, 2026
06677c6
Merge branch 'SAP:main' into dms-integration
JagnathReddy Mar 17, 2026
9a0723b
adding union import
JagnathReddy Mar 17, 2026
ba7c708
deleting config
JagnathReddy Mar 17, 2026
64c4a5c
redesing
JagnathReddy Mar 23, 2026
f809559
created overriden headers and CONSTAnTs
JagnathReddy Mar 24, 2026
f2a3881
added support for ecmuser and principals headers
JagnathReddy Mar 24, 2026
443dd53
added telemetry with new admin apis and models
JagnathReddy Mar 25, 2026
38c3194
Merge branch 'SAP:main' into dms-integration
JagnathReddy Mar 25, 2026
37ad206
Merge pull request #1 from JagnathReddy/dms-integration
JagnathReddy Mar 26, 2026
7238b87
Add CMIS retrieval and user APIs for DMS module
I559656 Mar 26, 2026
a3b5526
Merge branch 'cmis-retrieval-apis' into cmis-retrieval-apis
JagnathReddy Mar 26, 2026
783ac64
removed aces from create doc and folder and added the unit test for a…
I559656 Mar 31, 2026
d4b9ccf
fix the issues with ruff and test imports
I559656 Mar 31, 2026
63f9491
applied the fix ruff, import collision in tests
I559656 Mar 31, 2026
07c434f
fix(dms): add ty type checker ignore comments for test files
I559656 Mar 31, 2026
1a009b5
fix(dms): fix parent_folder_id description in user guide
I559656 Apr 1, 2026
7de09bb
Merge pull request #2 from I559656/cmis-retrieval-apis
JagnathReddy Apr 1, 2026
b1904eb
todo complete
JagnathReddy Apr 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
1 change: 1 addition & 0 deletions src/sap_cloud_sdk/core/telemetry/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Module(str, Enum):
AUDITLOG = "auditlog"
DESTINATION = "destination"
OBJECTSTORE = "objectstore"
DMS = "dms"

def __str__(self) -> str:
return self.value
23 changes: 23 additions & 0 deletions src/sap_cloud_sdk/core/telemetry/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,28 @@ class Operation(str, Enum):
AICORE_SET_CONFIG = "set_aicore_config"
AICORE_AUTO_INSTRUMENT = "auto_instrument"

# DMS Operations
DMS_ONBOARD_REPOSITORY = "onboard_repository"
DMS_GET_REPOSITORY = "get_repository"
DMS_GET_ALL_REPOSITORIES = "get_all_repositories"
DMS_UPDATE_REPOSITORY = "update_repository"
DMS_DELETE_REPOSITORY = "delete_repository"
DMS_CREATE_CONFIG = "create_config"
DMS_GET_CONFIGS = "get_configs"
DMS_UPDATE_CONFIG = "update_config"
DMS_DELETE_CONFIG = "delete_config"

# DMS CMIS Operations
DMS_CREATE_FOLDER = "create_folder"
DMS_CREATE_DOCUMENT = "create_document"
DMS_CHECK_OUT = "check_out"
DMS_CHECK_IN = "check_in"
DMS_CANCEL_CHECK_OUT = "cancel_check_out"
DMS_APPLY_ACL = "apply_acl"
DMS_GET_OBJECT = "get_object"
DMS_GET_CONTENT = "get_content"
DMS_UPDATE_PROPERTIES = "update_properties"
DMS_GET_CHILDREN = "get_children"

def __str__(self) -> str:
return self.value
20 changes: 20 additions & 0 deletions src/sap_cloud_sdk/dms/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import Optional
from sap_cloud_sdk.dms.model import DMSCredentials
from sap_cloud_sdk.dms.client import DMSClient
from sap_cloud_sdk.dms.config import load_sdm_config_from_env_or_mount


def create_client(
*, instance: Optional[str] = None, dms_cred: Optional[DMSCredentials] = None
):
if dms_cred is not None:
return DMSClient(dms_cred)
if instance is not None:
return DMSClient(load_sdm_config_from_env_or_mount(instance))

raise ValueError(
"No configuration provided. Please provide either instance name, config, or dms_cred."
)


__all__ = ["create_client"]
109 changes: 109 additions & 0 deletions src/sap_cloud_sdk/dms/_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import logging
import time
import requests
from collections import OrderedDict
from requests.exceptions import RequestException
from typing import Optional, TypedDict
from sap_cloud_sdk.dms.exceptions import (
DMSError,
DMSConnectionError,
DMSPermissionDeniedException,
)
from sap_cloud_sdk.dms.model import DMSCredentials

logger = logging.getLogger(__name__)


class _TokenResponse(TypedDict):
access_token: str
expires_in: int


class _CachedToken:
def __init__(self, token: str, expires_at: float) -> None:
self.token = token
self.expires_at = expires_at

def is_valid(self) -> bool:
return time.monotonic() < self.expires_at - 30


_MAX_CACHE_SIZE = 10


class Auth:
"""Fetches and caches OAuth2 access tokens for DMS service requests."""

def __init__(self, credentials: DMSCredentials) -> None:
self._credentials = credentials
self._cache: OrderedDict[str, _CachedToken] = OrderedDict()

def get_token(self, tenant_subdomain: Optional[str] = None) -> str:
cache_key = tenant_subdomain or "technical"

cached = self._cache.get(cache_key)
if cached and cached.is_valid():
self._cache.move_to_end(cache_key) # Mark as recently used by moving to end
logger.debug("Using cached token for key '%s'", cache_key)
return cached.token

logger.debug("Fetching new token for key '%s'", cache_key)
token_url = self._resolve_token_url(tenant_subdomain)
token = self._fetch_token(token_url)

if len(self._cache) >= _MAX_CACHE_SIZE:
evicted, _ = self._cache.popitem(last=False)
logger.debug("Cache full — evicted token for key '%s'", evicted)

self._cache[cache_key] = _CachedToken(
token=token["access_token"],
expires_at=time.monotonic() + token.get("expires_in", 3600),
)
logger.debug("Token cached for key '%s'", cache_key)
return self._cache[cache_key].token

def _resolve_token_url(self, tenant_subdomain: Optional[str]) -> str:
if not tenant_subdomain:
return self._credentials.token_url
logger.debug("Resolving token URL for tenant '%s'", tenant_subdomain)
return self._credentials.token_url.replace(
self._credentials.identityzone,
tenant_subdomain,
)

def _fetch_token(self, token_url: str) -> _TokenResponse:
try:
response = requests.post(
f"{token_url}/oauth/token",
data={
"grant_type": "client_credentials",
"client_id": self._credentials.client_id,
"client_secret": self._credentials.client_secret,
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=10,
)
response.raise_for_status()
except requests.exceptions.ConnectionError as e:
logger.error("Failed to connect to token endpoint")
raise DMSConnectionError(
"Failed to connect to the authentication server"
) from e
except requests.exceptions.HTTPError as e:
status = e.response.status_code if e.response is not None else None
logger.error("Token request failed with status %s", status)
if status in (401, 403):
raise DMSPermissionDeniedException(
"Authentication failed — invalid client credentials", status
) from e
raise DMSError("Failed to obtain access token", status) from e
except RequestException as e:
logger.error("Unexpected error during token fetch")
raise DMSConnectionError("Unexpected error during authentication") from e

payload: _TokenResponse = response.json()
if not payload.get("access_token"):
raise DMSError("Token response missing access_token")

logger.debug("Token fetched successfully")
return payload

Check failure on line 109 in src/sap_cloud_sdk/dms/_auth.py

View workflow job for this annotation

GitHub Actions / Code Quality Checks

ruff (W292)

src/sap_cloud_sdk/dms/_auth.py:109:23: W292 No newline at end of file help: Add trailing newline
2 changes: 2 additions & 0 deletions src/sap_cloud_sdk/dms/_endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
REPOSITORIES = "/rest/v2/repositories"
CONFIGS = "/rest/v2/configs"
Loading
Loading