implement suggestions

This commit is contained in:
Simone Scarduzio
2025-09-25 17:18:19 +02:00
parent 02120a764e
commit 0c1d0373a9
3 changed files with 54 additions and 44 deletions

View File

@@ -10,6 +10,11 @@ from ..ports.metrics import MetricsPort
logger = logging.getLogger(__name__)
# Constants for byte conversions
BYTES_PER_KB = 1024
BYTES_PER_MB = 1024 * 1024
BYTES_PER_GB = 1024 * 1024 * 1024
class CloudWatchMetricsAdapter(MetricsPort):
"""CloudWatch implementation of MetricsPort for AWS-native metrics."""
@@ -160,11 +165,11 @@ class CloudWatchMetricsAdapter(MetricsPort):
# Size metrics
if any(x in name_lower for x in ["size", "bytes"]):
if value > 1024 * 1024 * 1024: # > 1GB
if value > BYTES_PER_GB: # > 1GB
return "Gigabytes"
elif value > 1024 * 1024: # > 1MB
elif value > BYTES_PER_MB: # > 1MB
return "Megabytes"
elif value > 1024: # > 1KB
elif value > BYTES_PER_KB: # > 1KB
return "Kilobytes"
return "Bytes"

View File

@@ -6,6 +6,7 @@ from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
from .adapters.storage_s3 import S3StorageAdapter
from .core import DeltaService, DeltaSpace, ObjectKey
@@ -279,8 +280,6 @@ class DeltaGliderClient:
ListObjectsResponse with objects and common prefixes
"""
# Use storage adapter's list_objects method if available
from .adapters.storage_s3 import S3StorageAdapter
if hasattr(self.service.storage, "list_objects"):
# Use list_objects method if available
result = self.service.storage.list_objects(
@@ -364,8 +363,6 @@ class DeltaGliderClient:
Returns:
Response dict with deletion details
"""
from .core.models import ObjectKey
# Use core service's delta-aware delete
object_key = ObjectKey(bucket=Bucket, key=Key)
delete_result = self.service.delete(object_key)
@@ -413,8 +410,6 @@ class DeltaGliderClient:
Returns:
Response dict with deleted objects
"""
from .core.models import ObjectKey
deleted = []
errors = []
delta_info = []
@@ -1051,6 +1046,26 @@ class DeltaGliderClient:
direct_objects=direct_count,
)
def _try_boto3_presigned_operation(self, operation: str, **kwargs: Any) -> Any | None:
"""Try to generate presigned operation using boto3 client, return None if not available."""
storage_adapter = self.service.storage
# Check if storage adapter has boto3 client
if hasattr(storage_adapter, "client"):
try:
if operation == "url":
return str(storage_adapter.client.generate_presigned_url(**kwargs))
elif operation == "post":
return dict(storage_adapter.client.generate_presigned_post(**kwargs))
except AttributeError:
# storage_adapter does not have a 'client' attribute
pass
except Exception as e:
# Fall back to manual construction if needed
self.service.logger.warning(f"Failed to generate presigned {operation}: {e}")
return None
def generate_presigned_url(
self,
ClientMethod: str,
@@ -1067,23 +1082,15 @@ class DeltaGliderClient:
Returns:
Presigned URL string
"""
# Access the underlying S3 client through storage adapter
# Note: service.storage should be an S3StorageAdapter instance
storage_adapter = self.service.storage
# Check if storage adapter has boto3 client
if hasattr(storage_adapter, "client"):
try:
# Use boto3's native presigned URL generation
url = storage_adapter.client.generate_presigned_url(
ClientMethod=ClientMethod,
Params=Params,
ExpiresIn=ExpiresIn,
)
return str(url)
except Exception as e:
# Fall back to manual URL construction if needed
self.service.logger.warning(f"Failed to generate presigned URL: {e}")
# Try boto3 first, fallback to manual construction
url = self._try_boto3_presigned_operation(
"url",
ClientMethod=ClientMethod,
Params=Params,
ExpiresIn=ExpiresIn,
)
if url is not None:
return str(url)
# Fallback: construct URL manually (less secure, for dev/testing only)
bucket = Params.get("Bucket", "")
@@ -1118,22 +1125,17 @@ class DeltaGliderClient:
Returns:
Dict with 'url' and 'fields' for form submission
"""
storage_adapter = self.service.storage
# Check if storage adapter has boto3 client
if hasattr(storage_adapter, "client"):
try:
# Use boto3's native presigned POST generation
response = storage_adapter.client.generate_presigned_post(
Bucket=Bucket,
Key=Key,
Fields=Fields,
Conditions=Conditions,
ExpiresIn=ExpiresIn,
)
return dict(response)
except Exception as e:
self.service.logger.warning(f"Failed to generate presigned POST: {e}")
# Try boto3 first, fallback to manual construction
response = self._try_boto3_presigned_operation(
"post",
Bucket=Bucket,
Key=Key,
Fields=Fields,
Conditions=Conditions,
ExpiresIn=ExpiresIn,
)
if response is not None:
return dict(response)
# Fallback: return minimal structure for compatibility
if self.endpoint_url:

View File

@@ -1,6 +1,7 @@
"""Tests for the DeltaGlider client with boto3-compatible APIs."""
import hashlib
from datetime import UTC, datetime
from pathlib import Path
import pytest
@@ -30,7 +31,7 @@ class MockStorage:
key=key,
size=obj["size"],
etag=obj.get("etag", "mock-etag"),
last_modified=obj.get("last_modified"),
last_modified=obj.get("last_modified", datetime.now(UTC)),
metadata=obj.get("metadata", {}),
)
return None
@@ -39,7 +40,9 @@ class MockStorage:
"""Mock list operation for StoragePort interface."""
for key, _obj in self.objects.items():
if key.startswith(prefix):
yield self.head(key)
obj_head = self.head(key)
if obj_head is not None:
yield obj_head
def list_objects(self, bucket, prefix="", delimiter="", max_keys=1000, start_after=None):
"""Mock list_objects operation for S3 features."""