-
Notifications
You must be signed in to change notification settings - Fork 5
feat: sap dms sdk addition #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JagnathReddy
wants to merge
34
commits into
SAP:main
Choose a base branch
from
JagnathReddy:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
8b95146
init
JagnathReddy cbf7209
Merge branch 'SAP:main' into dms-integration
JagnathReddy 33d1e3e
added admin service
JagnathReddy 0ba96b5
clean up
JagnathReddy 11c33af
clean up - minor
JagnathReddy 06677c6
Merge branch 'SAP:main' into dms-integration
JagnathReddy 9a0723b
adding union import
JagnathReddy ba7c708
deleting config
JagnathReddy 64c4a5c
redesing
JagnathReddy f809559
created overriden headers and CONSTAnTs
JagnathReddy f2a3881
added support for ecmuser and principals headers
JagnathReddy 443dd53
added telemetry with new admin apis and models
JagnathReddy 38c3194
Merge branch 'SAP:main' into dms-integration
JagnathReddy 37ad206
Merge pull request #1 from JagnathReddy/dms-integration
JagnathReddy 7238b87
Add CMIS retrieval and user APIs for DMS module
I559656 a3b5526
Merge branch 'cmis-retrieval-apis' into cmis-retrieval-apis
JagnathReddy 783ac64
removed aces from create doc and folder and added the unit test for a…
I559656 d4b9ccf
fix the issues with ruff and test imports
I559656 63f9491
applied the fix ruff, import collision in tests
I559656 07c434f
fix(dms): add ty type checker ignore comments for test files
I559656 1a009b5
fix(dms): fix parent_folder_id description in user guide
I559656 7de09bb
Merge pull request #2 from I559656/cmis-retrieval-apis
JagnathReddy 0fa0f43
Merge branch 'SAP:main' into main
JagnathReddy b1904eb
todo complete
JagnathReddy a37d666
fix ruff: missing newlines at end of file
I559656 59aa82c
Merge pull request #3 from JagnathReddy/hashenum
JagnathReddy af0a684
fix(dms): refactored the pagination and added more documentations in …
I559656 80e0dff
fix(dms): move ty:ignore comments to correct lines in test_dms_bdd
I559656 3c8d970
docs(dms): add docstrings to create_client() and DMSClient.__init__()
I559656 d4e31cb
FIX: added warning for direct client creation and completed flow for …
JagnathReddy 86f0e28
Merge branch 'SAP:main' into main
JagnathReddy 1a18051
fix(dms): warning for direct client creation and completed timeout flows
JagnathReddy aa0ee55
feat(dms): add delete_object, restore_object, append_content_stream, …
I559656 578053c
fix(dms): format logger.info calls to pass ruff format check
I559656 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| """SAP Cloud SDK for Python - Document Management Service (DMS) module | ||
|
|
||
| The create_client() function loads credentials from mounts/env vars and points | ||
| to an instance in the cloud. | ||
|
|
||
| Usage: | ||
| from sap_cloud_sdk.dms import create_client | ||
|
|
||
| # Recommended: use the factory which configures OAuth/HTTP from environment | ||
| client = create_client() | ||
|
|
||
| # Or specify a named instance | ||
| client = create_client(instance="my-sdm-instance") | ||
|
|
||
| # List all onboarded repositories | ||
| repos = client.get_all_repositories() | ||
|
|
||
| # Create a folder | ||
| folder = client.create_folder(repo.id, root_folder_id, "MyFolder") | ||
|
|
||
| # Upload a document | ||
| with open("file.pdf", "rb") as f: | ||
| doc = client.create_document( | ||
| repo.id, folder.object_id, "file.pdf", f, mime_type="application/pdf" | ||
| ) | ||
| """ | ||
|
|
||
| from typing import Optional | ||
|
|
||
| from sap_cloud_sdk.core.telemetry import Module | ||
| from sap_cloud_sdk.dms.model import ( | ||
| Ace, | ||
| Acl, | ||
| ChildrenOptions, | ||
| ChildrenPage, | ||
| CmisObject, | ||
| DMSCredentials, | ||
| Document, | ||
| Folder, | ||
| QueryOptions, | ||
| QueryResultPage, | ||
| UserClaim, | ||
| ) | ||
| from sap_cloud_sdk.dms.client import DMSClient | ||
| from sap_cloud_sdk.dms.config import load_sdm_config_from_env_or_mount | ||
| from sap_cloud_sdk.dms.exceptions import DMSError | ||
|
|
||
|
|
||
| def create_client( | ||
NicoleMGomes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| *, | ||
| instance: Optional[str] = None, | ||
| dms_cred: Optional[DMSCredentials] = None, | ||
| connect_timeout: Optional[int] = None, | ||
| read_timeout: Optional[int] = None, | ||
| _telemetry_source: Optional[Module] = None, | ||
| ): | ||
| """Create a DMS client with automatic credential resolution. | ||
|
|
||
| Args: | ||
| instance: Logical instance name for secret resolution. Defaults to ``"default"``. | ||
| dms_cred: Explicit credentials. If provided, skips secret resolution. | ||
| connect_timeout: TCP connection timeout in seconds. Defaults to 10. | ||
| read_timeout: Response read timeout in seconds. Defaults to 30. | ||
| _telemetry_source: Internal telemetry source identifier. Not intended for external use. | ||
|
|
||
| Returns: | ||
| DMSClient: Configured client ready to use. | ||
|
|
||
| Raises: | ||
| DMSError: If client creation fails due to configuration or initialization issues. | ||
| """ | ||
| try: | ||
| credentials = dms_cred or load_sdm_config_from_env_or_mount(instance) | ||
| client = DMSClient( | ||
| credentials, | ||
| connect_timeout=connect_timeout, | ||
| read_timeout=read_timeout, | ||
| ) | ||
| client._telemetry_source = _telemetry_source | ||
| return client | ||
| except Exception as e: | ||
| raise DMSError(f"Failed to create DMS client: {e}") from e | ||
|
|
||
|
|
||
| __all__ = [ | ||
| "create_client", | ||
| "Ace", | ||
| "Acl", | ||
| "ChildrenOptions", | ||
| "ChildrenPage", | ||
| "CmisObject", | ||
| "DMSClient", | ||
| "DMSCredentials", | ||
| "DMSError", | ||
| "Document", | ||
| "Folder", | ||
| "QueryOptions", | ||
| "QueryResultPage", | ||
| "UserClaim", | ||
| ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to have multiple variables for it, we have faced some issues in the past
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we thought of keeping it similar to how these variables are defined in the actual mount path in kyma runtime, should we keep the structure of env variables different that the mount?
screenshot of mount below