feat: Add AWS credential parameters to create_client()

- Add aws_access_key_id, aws_secret_access_key, aws_session_token, and region_name parameters
- Pass credentials through to S3StorageAdapter and boto3.client()
- Enables multi-tenant scenarios with different AWS accounts
- Maintains backward compatibility (uses boto3 default credential chain when omitted)
- Add comprehensive tests for credential handling
- Add examples/credentials_example.py with usage examples

Fixes credential conflicts when multiple SDK instances need different credentials.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Simone Scarduzio
2025-10-06 09:07:40 +02:00
parent 74207f4ee4
commit 2efa760785
5 changed files with 217 additions and 11 deletions

View File

@@ -21,13 +21,31 @@ class S3StorageAdapter(StoragePort):
self,
client: Optional["S3Client"] = None,
endpoint_url: str | None = None,
boto3_kwargs: dict[str, Any] | None = None,
):
"""Initialize with S3 client."""
"""Initialize with S3 client.
Args:
client: Pre-configured S3 client (if None, one will be created)
endpoint_url: S3 endpoint URL override (for MinIO, LocalStack, etc.)
boto3_kwargs: Additional kwargs to pass to boto3.client() including:
- aws_access_key_id: AWS access key
- aws_secret_access_key: AWS secret key
- aws_session_token: AWS session token (for temporary credentials)
- region_name: AWS region name
"""
if client is None:
self.client = boto3.client(
"s3",
endpoint_url=endpoint_url or os.environ.get("AWS_ENDPOINT_URL"),
)
# Build boto3 client parameters
client_params: dict[str, Any] = {
"service_name": "s3",
"endpoint_url": endpoint_url or os.environ.get("AWS_ENDPOINT_URL"),
}
# Merge in any additional boto3 kwargs (credentials, region, etc.)
if boto3_kwargs:
client_params.update(boto3_kwargs)
self.client = boto3.client(**client_params)
else:
self.client = client

View File

@@ -1396,6 +1396,10 @@ def create_client(
endpoint_url: str | None = None,
log_level: str = "INFO",
cache_dir: str = "/tmp/.deltaglider/cache",
aws_access_key_id: str | None = None,
aws_secret_access_key: str | None = None,
aws_session_token: str | None = None,
region_name: str | None = None,
**kwargs: Any,
) -> DeltaGliderClient:
"""Create a DeltaGlider client with boto3-compatible APIs.
@@ -1411,18 +1415,28 @@ def create_client(
endpoint_url: Optional S3 endpoint URL (for MinIO, R2, etc.)
log_level: Logging level
cache_dir: Directory for reference cache
aws_access_key_id: AWS access key ID (None to use environment/IAM)
aws_secret_access_key: AWS secret access key (None to use environment/IAM)
aws_session_token: AWS session token for temporary credentials (None if not using)
region_name: AWS region name (None for default)
**kwargs: Additional arguments
Returns:
DeltaGliderClient instance
Examples:
>>> # Boto3-compatible usage
>>> # Boto3-compatible usage with default credentials
>>> client = create_client()
>>> client.put_object(Bucket='my-bucket', Key='file.zip', Body=b'data')
>>> response = client.get_object(Bucket='my-bucket', Key='file.zip')
>>> data = response['Body'].read()
>>> # With explicit credentials
>>> client = create_client(
... aws_access_key_id='AKIAIOSFODNN7EXAMPLE',
... aws_secret_access_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
... )
>>> # Batch operations
>>> results = client.upload_batch(['v1.zip', 'v2.zip'], 's3://bucket/releases/')
@@ -1441,9 +1455,20 @@ def create_client(
XdeltaAdapter,
)
# Build boto3 client kwargs
boto3_kwargs = {}
if aws_access_key_id is not None:
boto3_kwargs["aws_access_key_id"] = aws_access_key_id
if aws_secret_access_key is not None:
boto3_kwargs["aws_secret_access_key"] = aws_secret_access_key
if aws_session_token is not None:
boto3_kwargs["aws_session_token"] = aws_session_token
if region_name is not None:
boto3_kwargs["region_name"] = region_name
# Create adapters
hasher = Sha256Adapter()
storage = S3StorageAdapter(endpoint_url=endpoint_url)
storage = S3StorageAdapter(endpoint_url=endpoint_url, boto3_kwargs=boto3_kwargs)
diff = XdeltaAdapter()
cache = FsCacheAdapter(Path(cache_dir), hasher)
clock = UtcClockAdapter()