From 01ba7f48fa4d91a8ccfc298c7b69794411bba4bf Mon Sep 17 00:00:00 2001 From: Arthur Tonial Date: Tue, 31 Mar 2026 11:01:53 -0300 Subject: [PATCH 1/6] fix: remove unnecessary blank lines in user guide and sync workflow --- .github/workflows/sync.yaml | 2 +- src/sap_cloud_sdk/aicore/user-guide.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sync.yaml b/.github/workflows/sync.yaml index 4d99760..e2e4047 100644 --- a/.github/workflows/sync.yaml +++ b/.github/workflows/sync.yaml @@ -46,4 +46,4 @@ jobs: echo "External repository **main** branch has been successfully mirrored to internal." >> $GITHUB_STEP_SUMMARY echo "- **External**: https://github.com/SAP/cloud-sdk-python" >> $GITHUB_STEP_SUMMARY echo "- **Branch**: main" >> $GITHUB_STEP_SUMMARY - echo "- **Time**: $(date -u)" >> $GITHUB_STEP_SUMMARY \ No newline at end of file + echo "- **Time**: $(date -u)" >> $GITHUB_STEP_SUMMARY diff --git a/src/sap_cloud_sdk/aicore/user-guide.md b/src/sap_cloud_sdk/aicore/user-guide.md index 6339b1d..ad2f5e3 100644 --- a/src/sap_cloud_sdk/aicore/user-guide.md +++ b/src/sap_cloud_sdk/aicore/user-guide.md @@ -235,9 +235,9 @@ try: temperature=0.7, max_tokens=100 ) - + print(f"Response: {response.choices[0].message.content}") - + except Exception as e: print(f"AI Core request failed: {e}") ``` @@ -370,4 +370,4 @@ set_aicore_config() - The `set_aicore_config()` function sets environment variables that persist for the lifetime of the Python process - If you need to switch between multiple AI Core instances at runtime, call `set_aicore_config()` with different instance names - The function is safe to call multiple times; subsequent calls will overwrite the environment variables -- Resource group defaults to "default" if not specified in secrets or environment variables \ No newline at end of file +- Resource group defaults to "default" if not specified in secrets or environment variables From ab073a123dfd6a0387d0aa55477a71080f46eeb8 Mon Sep 17 00:00:00 2001 From: Arthur Tonial Date: Tue, 31 Mar 2026 11:02:05 -0300 Subject: [PATCH 2/6] refactor: remove implicit audit log functionality from ObjectStoreClient --- src/sap_cloud_sdk/objectstore/_s3.py | 118 --------------------------- 1 file changed, 118 deletions(-) diff --git a/src/sap_cloud_sdk/objectstore/_s3.py b/src/sap_cloud_sdk/objectstore/_s3.py index 3c6be68..cfc8168 100644 --- a/src/sap_cloud_sdk/objectstore/_s3.py +++ b/src/sap_cloud_sdk/objectstore/_s3.py @@ -2,7 +2,6 @@ import io import os -import logging from datetime import datetime from http.client import HTTPResponse from typing import BinaryIO, List, cast @@ -19,16 +18,6 @@ ListObjectsError, ) from sap_cloud_sdk.objectstore._models import ObjectStoreBindingData, ObjectMetadata -from sap_cloud_sdk.core.auditlog import ( - create_client as create_auditlog_client, - DataAccessEvent, - DataModificationEvent, - DataDeletionEvent, - DataAccessAttribute, - ChangeAttribute, - DeletedAttribute, - Tenant, -) from sap_cloud_sdk.objectstore.utils import _normalize_host # Validation error message constants @@ -64,10 +53,6 @@ def __init__( self._creds_config = creds_config self._disable_ssl = disable_ssl self._minio_client = self._create_minio_client() - # Pass Module.OBJECTSTORE as source when creating auditlog client - self._audit_client = create_auditlog_client( - _telemetry_source=Module.OBJECTSTORE - ) def _create_minio_client(self) -> Minio: """Create MinIO client with proper configuration.""" @@ -82,46 +67,6 @@ def _create_minio_client(self) -> Minio: except Exception as e: raise ClientCreationError(f"Failed to create MinIO client: {e}") from e - def _build_data_access_event( - self, object_name: str, success: bool - ) -> DataAccessEvent: - """Build a data access audit event for read operations.""" - return DataAccessEvent( - object_type="s3-object", - object_id={"bucket": self._creds_config.bucket, "key": object_name}, - subject_type="application", - subject_id={"type": "automation"}, - subject_role="app-foundation-sdk-python", - attributes=[DataAccessAttribute(name=object_name, successful=success)], - user="app-foundation-sdk-python", - tenant=Tenant.PROVIDER, - ) - - def _build_data_modification_event(self, object_name: str) -> DataModificationEvent: - return DataModificationEvent( - object_type="s3-object", - object_id={"bucket": self._creds_config.bucket, "key": object_name}, - subject_type="application", - subject_id={"type": "automation"}, - subject_role="app-foundation-sdk-python", - attributes=[ChangeAttribute(name=object_name, new="", old="")], - user="app-foundation-sdk-python", - tenant=Tenant.PROVIDER, - ) - - def _build_data_deletion_event(self, object_name: str) -> DataDeletionEvent: - """Build a data deletion audit event for delete operations.""" - return DataDeletionEvent( - object_type="s3-object", - object_id={"bucket": self._creds_config.bucket, "key": object_name}, - subject_type="application", - subject_id={"type": "automation"}, - subject_role="app-foundation-sdk-python", - attributes=[DeletedAttribute(name=object_name, old="")], - user="app-foundation-sdk-python", - tenant=Tenant.PROVIDER, - ) - @record_metrics(Module.OBJECTSTORE, Operation.OBJECTSTORE_PUT_OBJECT_FROM_BYTES) def put_object_from_bytes(self, name: str, data: bytes, content_type: str) -> None: """Upload an object from bytes. @@ -150,12 +95,6 @@ def put_object_from_bytes(self, name: str, data: bytes, content_type: str) -> No length=len(data), content_type=content_type, ) - - try: - self._audit_client.log(self._build_data_modification_event(name)) - except Exception as e: - logging.error(f"audit log failed for PutObjectFromBytes operation: {e}") - except S3Error as e: raise ObjectOperationError( f"Failed to upload object '{name}': {e.code} - {e.message}" @@ -196,12 +135,6 @@ def put_object( length=size, content_type=content_type, ) - - try: - self._audit_client.log(self._build_data_modification_event(name)) - except Exception as e: - logging.error(f"audit log failed for PutObject operation: {e}") - except S3Error as e: raise ObjectOperationError( f"Failed to upload object '{name}': {e.code} - {e.message}" @@ -238,7 +171,6 @@ def put_object_from_file( file_size = os.path.getsize(file_path) - # Single file operation - no content reading for audit with open(file_path, "rb") as file_stream: self._minio_client.put_object( bucket_name=self._creds_config.bucket, @@ -247,12 +179,6 @@ def put_object_from_file( length=file_size, content_type=content_type, ) - - try: - self._audit_client.log(self._build_data_modification_event(name)) - except Exception as e: - logging.error(f"audit log failed for PutObjectFromFile operation: {e}") - except S3Error as e: raise ObjectOperationError( f"Failed to upload object '{name}': {e.code} - {e.message}" @@ -278,7 +204,6 @@ def get_object(self, name: str) -> HTTPResponse: if not name: raise ValueError(EMPTY_NAME_ERROR) - get_err = None try: response = cast( HTTPResponse, @@ -288,25 +213,15 @@ def get_object(self, name: str) -> HTTPResponse: ) return response except S3Error as e: - get_err = e if e.code == "NoSuchKey": raise ObjectNotFoundError(f"Object '{name}' not found") from e raise ObjectOperationError( f"Failed to download object '{name}': {e.code} - {e.message}" ) from e except Exception as e: - get_err = e raise ObjectOperationError( f"Failed to download object '{name}': {e}" ) from e - finally: - # Log audit event - try: - self._audit_client.log( - self._build_data_access_event(name, get_err is None) - ) - except Exception as e: - logging.error(f"audit log failed for GetObject operation: {e}") @record_metrics(Module.OBJECTSTORE, Operation.OBJECTSTORE_DELETE_OBJECT) def delete_object(self, name: str) -> None: @@ -326,22 +241,12 @@ def delete_object(self, name: str) -> None: self._minio_client.remove_object( bucket_name=self._creds_config.bucket, object_name=name ) - - try: - self._audit_client.log(self._build_data_deletion_event(name)) - except Exception as e: - logging.error(f"audit log failed for DeleteObject operation: {e}") - except S3Error as e: if e.code != "NoSuchKey": raise ObjectOperationError( f"Failed to delete object '{name}': {e.code} - {e.message}" ) from e # For NoSuchKey, we still consider it successful (idempotent delete) - try: - self._audit_client.log(self._build_data_deletion_event(name)) - except Exception as audit_e: - logging.error(f"audit log failed for DeleteObject operation: {audit_e}") except Exception as e: raise ObjectOperationError(f"Failed to delete object '{name}': {e}") from e @@ -362,7 +267,6 @@ def list_objects(self, prefix: str) -> List[ObjectMetadata]: if not isinstance(prefix, str): raise ValueError(INVALID_PREFIX_TYPE_ERROR) - list_err = None result = [] try: objects = self._minio_client.list_objects( @@ -382,25 +286,13 @@ def list_objects(self, prefix: str) -> List[ObjectMetadata]: return result except S3Error as e: - list_err = e raise ListObjectsError( f"Failed to list objects with prefix '{prefix}': {e.code} - {e.message}" ) from e except Exception as e: - list_err = e raise ListObjectsError( f"Failed to list objects with prefix '{prefix}': {e}" ) from e - finally: - if prefix is None or prefix.strip() == "": - # Audit log name can't be empty - prefix = "/" - try: - self._audit_client.log( - self._build_data_access_event(prefix, list_err is None) - ) - except Exception as e: - logging.error(f"audit log failed for ListObjects operation: {e}") @record_metrics(Module.OBJECTSTORE, Operation.OBJECTSTORE_HEAD_OBJECT) def head_object(self, name: str) -> ObjectMetadata: @@ -420,7 +312,6 @@ def head_object(self, name: str) -> ObjectMetadata: if not name: raise ValueError(EMPTY_NAME_ERROR) - head_err = None try: stat: minio.datatypes.Object = self._minio_client.stat_object( bucket_name=self._creds_config.bucket, object_name=name @@ -435,24 +326,15 @@ def head_object(self, name: str) -> ObjectMetadata: owner=None, # stat_object doesn't provide owner ) except S3Error as e: - head_err = e if e.code == "NoSuchKey": raise ObjectNotFoundError(f"Object '{name}' not found") from e raise ObjectOperationError( f"Failed to get metadata for object '{name}': {e.code} - {e.message}" ) from e except Exception as e: - head_err = e raise ObjectOperationError( f"Failed to get metadata for object '{name}': {e}" ) from e - finally: - try: - self._audit_client.log( - self._build_data_access_event(name, head_err is None) - ) - except Exception as e: - logging.error(f"audit log failed for HeadObject operation: {e}") @record_metrics(Module.OBJECTSTORE, Operation.OBJECTSTORE_OBJECT_EXISTS) def object_exists(self, name: str) -> bool: From b705bcd45ea507dec1efbd0debfb5d11783d4d17 Mon Sep 17 00:00:00 2001 From: Arthur Tonial Date: Tue, 31 Mar 2026 11:03:25 -0300 Subject: [PATCH 3/6] refactor: remove audit log client from ObjectStoreClient tests --- tests/objectstore/unit/test_s3_client.py | 76 ++++++++---------------- 1 file changed, 25 insertions(+), 51 deletions(-) diff --git a/tests/objectstore/unit/test_s3_client.py b/tests/objectstore/unit/test_s3_client.py index 80d9cfa..fec111f 100644 --- a/tests/objectstore/unit/test_s3_client.py +++ b/tests/objectstore/unit/test_s3_client.py @@ -15,7 +15,6 @@ ) -@patch('sap_cloud_sdk.objectstore._s3.create_auditlog_client') class TestObjectStoreClient: def setup_method(self): @@ -27,10 +26,9 @@ def setup_method(self): ) @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_client_creation_ssl_enabled(self, mock_minio_class, mock_audit_client): + def test_client_creation_ssl_enabled(self, mock_minio_class): mock_minio = Mock() mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds, disable_ssl=False) @@ -43,10 +41,9 @@ def test_client_creation_ssl_enabled(self, mock_minio_class, mock_audit_client): assert client._minio_client == mock_minio @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_client_creation_ssl_disabled(self, mock_minio_class, mock_audit_client): + def test_client_creation_ssl_disabled(self, mock_minio_class): mock_minio = Mock() mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds, disable_ssl=True) @@ -58,18 +55,16 @@ def test_client_creation_ssl_disabled(self, mock_minio_class, mock_audit_client) ) @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_client_creation_failure(self, mock_minio_class, mock_audit_client): + def test_client_creation_failure(self, mock_minio_class): mock_minio_class.side_effect = Exception("Connection failed") - mock_audit_client.return_value = Mock() with pytest.raises(ClientCreationError, match="Failed to create MinIO client"): ObjectStoreClient(self.creds) @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_put_object_from_bytes_success(self, mock_minio_class, mock_audit_client): + def test_put_object_from_bytes_success(self, mock_minio_class): mock_minio = Mock() mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) test_data = b"Hello, World!" @@ -85,9 +80,8 @@ def test_put_object_from_bytes_success(self, mock_minio_class, mock_audit_client assert isinstance(call_args.kwargs['data'], io.BytesIO) @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_put_object_from_bytes_validation(self, mock_minio_class, mock_audit_client): + def test_put_object_from_bytes_validation(self, mock_minio_class): mock_minio_class.return_value = Mock() - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) with pytest.raises(ValueError, match="name must be a non-empty string"): @@ -100,12 +94,11 @@ def test_put_object_from_bytes_validation(self, mock_minio_class, mock_audit_cli client.put_object_from_bytes("test.txt", b"data", "") @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_put_object_from_bytes_s3_error(self, mock_minio_class, mock_audit_client): + def test_put_object_from_bytes_s3_error(self, mock_minio_class): mock_minio = Mock() s3_error = S3Error("AccessDenied", "Access denied", "test.txt", "123", "456", Mock()) mock_minio.put_object.side_effect = s3_error mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) @@ -113,10 +106,9 @@ def test_put_object_from_bytes_s3_error(self, mock_minio_class, mock_audit_clien client.put_object_from_bytes("test.txt", b"data", "text/plain") @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_put_object_from_stream_success(self, mock_minio_class, mock_audit_client): + def test_put_object_from_stream_success(self, mock_minio_class): mock_minio = Mock() mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) stream = io.BytesIO(b"stream data") @@ -132,9 +124,8 @@ def test_put_object_from_stream_success(self, mock_minio_class, mock_audit_clien assert call_args.kwargs['content_type'] == 'text/plain' @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_put_object_validation(self, mock_minio_class, mock_audit_client): + def test_put_object_validation(self, mock_minio_class): mock_minio_class.return_value = Mock() - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) with pytest.raises(ValueError, match="size must be non-negative"): @@ -144,10 +135,9 @@ def test_put_object_validation(self, mock_minio_class, mock_audit_client): @patch('builtins.open', new_callable=mock_open, read_data=b"file content") @patch('os.path.isfile', return_value=True) @patch('os.path.getsize', return_value=12) - def test_put_object_from_file_success(self, mock_getsize, mock_isfile, mock_file, mock_minio_class, mock_audit_client): + def test_put_object_from_file_success(self, mock_getsize, mock_isfile, mock_file, mock_minio_class): mock_minio = Mock() mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) client.put_object_from_file("test.txt", "/path/to/file.txt", "text/plain") @@ -159,21 +149,19 @@ def test_put_object_from_file_success(self, mock_getsize, mock_isfile, mock_file @patch('sap_cloud_sdk.objectstore._s3.Minio') @patch('builtins.open', new_callable=mock_open, read_data=b"file content") @patch('os.path.isfile', return_value=False) - def test_put_object_from_file_not_found(self, mock_isfile, mock_file, mock_minio_class, mock_audit_client): + def test_put_object_from_file_not_found(self, mock_isfile, mock_file, mock_minio_class): mock_minio_class.return_value = Mock() - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) with pytest.raises(ObjectOperationError, match="File not found"): client.put_object_from_file("test.txt", "/nonexistent.txt", "text/plain") @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_get_object_success(self, mock_minio_class, mock_audit_client): + def test_get_object_success(self, mock_minio_class): mock_minio = Mock() mock_response = Mock(spec=HTTPResponse) mock_minio.get_object.return_value = mock_response mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) result = client.get_object("test.txt") @@ -185,12 +173,11 @@ def test_get_object_success(self, mock_minio_class, mock_audit_client): assert result == mock_response @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_get_object_not_found(self, mock_minio_class, mock_audit_client): + def test_get_object_not_found(self, mock_minio_class): mock_minio = Mock() s3_error = S3Error("NoSuchKey", "Key not found", "test.txt", "123", "456", Mock()) mock_minio.get_object.side_effect = s3_error mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) @@ -198,10 +185,9 @@ def test_get_object_not_found(self, mock_minio_class, mock_audit_client): client.get_object("test.txt") @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_delete_object_success(self, mock_minio_class, mock_audit_client): + def test_delete_object_success(self, mock_minio_class): mock_minio = Mock() mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) client.delete_object("test.txt") @@ -212,18 +198,17 @@ def test_delete_object_success(self, mock_minio_class, mock_audit_client): ) @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_delete_object_not_found_ignored(self, mock_minio_class, mock_audit_client): + def test_delete_object_not_found_ignored(self, mock_minio_class): mock_minio = Mock() s3_error = S3Error("NoSuchKey", "Key not found", "test.txt", "123", "456", Mock()) mock_minio.remove_object.side_effect = s3_error mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) client.delete_object("test.txt") @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_list_objects_success(self, mock_minio_class, mock_audit_client): + def test_list_objects_success(self, mock_minio_class): mock_minio = Mock() mock_obj1 = Mock() @@ -236,7 +221,6 @@ def test_list_objects_success(self, mock_minio_class, mock_audit_client): mock_minio.list_objects.return_value = [mock_obj1] mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) result = client.list_objects("prefix/") @@ -251,12 +235,11 @@ def test_list_objects_success(self, mock_minio_class, mock_audit_client): assert result[0].etag == '"abc123"' @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_list_objects_s3_error(self, mock_minio_class, mock_audit_client): + def test_list_objects_s3_error(self, mock_minio_class): mock_minio = Mock() s3_error = S3Error("AccessDenied", "Access denied", "", "123", "456", Mock()) mock_minio.list_objects.side_effect = s3_error mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) @@ -264,7 +247,7 @@ def test_list_objects_s3_error(self, mock_minio_class, mock_audit_client): client.list_objects("prefix/") @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_head_object_success(self, mock_minio_class, mock_audit_client): + def test_head_object_success(self, mock_minio_class): mock_minio = Mock() mock_stat = Mock() @@ -274,7 +257,6 @@ def test_head_object_success(self, mock_minio_class, mock_audit_client): mock_minio.stat_object.return_value = mock_stat mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) result = client.head_object("test.txt") @@ -289,12 +271,11 @@ def test_head_object_success(self, mock_minio_class, mock_audit_client): assert result.size == 100 @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_head_object_not_found(self, mock_minio_class, mock_audit_client): + def test_head_object_not_found(self, mock_minio_class): mock_minio = Mock() s3_error = S3Error("NoSuchKey", "Key not found", "test.txt", "123", "456", Mock()) mock_minio.stat_object.side_effect = s3_error mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) @@ -302,11 +283,10 @@ def test_head_object_not_found(self, mock_minio_class, mock_audit_client): client.head_object("test.txt") @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_object_exists_true(self, mock_minio_class, mock_audit_client): + def test_object_exists_true(self, mock_minio_class): mock_minio = Mock() mock_minio.stat_object.return_value = Mock() mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) result = client.object_exists("test.txt") @@ -314,12 +294,11 @@ def test_object_exists_true(self, mock_minio_class, mock_audit_client): assert result is True @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_object_exists_false(self, mock_minio_class, mock_audit_client): + def test_object_exists_false(self, mock_minio_class): mock_minio = Mock() s3_error = S3Error("NoSuchKey", "Key not found", "test.txt", "123", "456", Mock()) mock_minio.stat_object.side_effect = s3_error mock_minio_class.return_value = mock_minio - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) result = client.object_exists("test.txt") @@ -327,45 +306,40 @@ def test_object_exists_false(self, mock_minio_class, mock_audit_client): assert result is False @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_get_object_empty_name_validation(self, mock_minio_class, mock_audit_client): + def test_get_object_empty_name_validation(self, mock_minio_class): mock_minio_class.return_value = Mock() - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) with pytest.raises(ValueError, match="name must be a non-empty string"): client.get_object("") @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_delete_object_empty_name_validation(self, mock_minio_class, mock_audit_client): + def test_delete_object_empty_name_validation(self, mock_minio_class): mock_minio_class.return_value = Mock() - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) with pytest.raises(ValueError, match="name must be a non-empty string"): client.delete_object("") @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_head_object_empty_name_validation(self, mock_minio_class, mock_audit_client): + def test_head_object_empty_name_validation(self, mock_minio_class): mock_minio_class.return_value = Mock() - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) with pytest.raises(ValueError, match="name must be a non-empty string"): client.head_object("") @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_object_exists_empty_name_validation(self, mock_minio_class, mock_audit_client): + def test_object_exists_empty_name_validation(self, mock_minio_class): mock_minio_class.return_value = Mock() - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) with pytest.raises(ValueError, match="name must be a non-empty string"): client.object_exists("") @patch('sap_cloud_sdk.objectstore._s3.Minio') - def test_list_objects_prefix_validation(self, mock_minio_class, mock_audit_client): + def test_list_objects_prefix_validation(self, mock_minio_class): mock_minio_class.return_value = Mock() - mock_audit_client.return_value = Mock() client = ObjectStoreClient(self.creds) with pytest.raises(ValueError, match="prefix must be a string"): From 7d4841b19093f1792b462fa72f073c8e231e37e3 Mon Sep 17 00:00:00 2001 From: Arthur Tonial Date: Tue, 31 Mar 2026 11:10:06 -0300 Subject: [PATCH 4/6] docs: update user guide to reflect removal of implicit audit logging --- src/sap_cloud_sdk/objectstore/user-guide.md | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/sap_cloud_sdk/objectstore/user-guide.md b/src/sap_cloud_sdk/objectstore/user-guide.md index 5b6a7f5..c355dca 100644 --- a/src/sap_cloud_sdk/objectstore/user-guide.md +++ b/src/sap_cloud_sdk/objectstore/user-guide.md @@ -4,6 +4,30 @@ This module provides a simple API for interacting with S3-compatible object stor Provides a simple and unified way to connect to Object Store services. It abstracts configuration, authentication, and transport, making it easy to upload and download files without dealing with provider-specific details. +> **Breaking Change Notice** +> +> **Implicit audit logging has been removed** from the ObjectStore module. Previously, all object store operations (upload, download, delete, list, etc.) automatically logged audit events. This implicit behavior has been removed to provide a cleaner, more focused API. +> +> **Migration Required:** If your application requires audit logging for object store operations, you must now implement it explicitly in your application code using the `sap_cloud_sdk.core.auditlog` module. This gives you full control over what, when, and how audit events are logged. +> +> Example of explicit audit logging: +> ```python +> from sap_cloud_sdk.objectstore import create_client +> from sap_cloud_sdk.core.auditlog import create_client as create_audit_client, DataModificationEvent +> +> # Create clients +> store_client = create_client("my-instance") +> audit_client = create_audit_client() +> +> # Upload with explicit audit logging +> store_client.put_object_from_bytes("file.txt", b"data", "text/plain") +> audit_client.log(DataModificationEvent( +> object_type="s3-object", +> object_id={"bucket": "my-bucket", "key": "file.txt"}, +> # ... other audit event details +> )) +> ``` + ## Installation ```bash From 1f92c3351b4695ea21572903b6c3d98109a30747 Mon Sep 17 00:00:00 2001 From: Arthur Tonial Date: Tue, 31 Mar 2026 11:42:27 -0300 Subject: [PATCH 5/6] docs: remove implicit audit logging notice and migration instructions from user guide --- src/sap_cloud_sdk/objectstore/user-guide.md | 24 --------------------- 1 file changed, 24 deletions(-) diff --git a/src/sap_cloud_sdk/objectstore/user-guide.md b/src/sap_cloud_sdk/objectstore/user-guide.md index c355dca..5b6a7f5 100644 --- a/src/sap_cloud_sdk/objectstore/user-guide.md +++ b/src/sap_cloud_sdk/objectstore/user-guide.md @@ -4,30 +4,6 @@ This module provides a simple API for interacting with S3-compatible object stor Provides a simple and unified way to connect to Object Store services. It abstracts configuration, authentication, and transport, making it easy to upload and download files without dealing with provider-specific details. -> **Breaking Change Notice** -> -> **Implicit audit logging has been removed** from the ObjectStore module. Previously, all object store operations (upload, download, delete, list, etc.) automatically logged audit events. This implicit behavior has been removed to provide a cleaner, more focused API. -> -> **Migration Required:** If your application requires audit logging for object store operations, you must now implement it explicitly in your application code using the `sap_cloud_sdk.core.auditlog` module. This gives you full control over what, when, and how audit events are logged. -> -> Example of explicit audit logging: -> ```python -> from sap_cloud_sdk.objectstore import create_client -> from sap_cloud_sdk.core.auditlog import create_client as create_audit_client, DataModificationEvent -> -> # Create clients -> store_client = create_client("my-instance") -> audit_client = create_audit_client() -> -> # Upload with explicit audit logging -> store_client.put_object_from_bytes("file.txt", b"data", "text/plain") -> audit_client.log(DataModificationEvent( -> object_type="s3-object", -> object_id={"bucket": "my-bucket", "key": "file.txt"}, -> # ... other audit event details -> )) -> ``` - ## Installation ```bash From 12c050ea0fb26e097fc9f1f3144856a338bbc6a0 Mon Sep 17 00:00:00 2001 From: Arthur Tonial Date: Tue, 31 Mar 2026 12:43:54 -0300 Subject: [PATCH 6/6] chore: update version to 0.4.0 in pyproject.toml and uv.lock --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b4e611b..9334cbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sap-cloud-sdk" -version = "0.3.2" +version = "0.4.0" description = "SAP Cloud SDK for Python" readme = "README.md" license = "Apache-2.0" diff --git a/uv.lock b/uv.lock index cc167f8..023df00 100644 --- a/uv.lock +++ b/uv.lock @@ -2415,7 +2415,7 @@ wheels = [ [[package]] name = "sap-cloud-sdk" -version = "0.3.2" +version = "0.4.0" source = { editable = "." } dependencies = [ { name = "hatchling" },