diff --git a/examples/boto3_compatible_types.py b/examples/boto3_compatible_types.py new file mode 100644 index 0000000..6bb2303 --- /dev/null +++ b/examples/boto3_compatible_types.py @@ -0,0 +1,51 @@ +"""Example: Using boto3-compatible types without importing boto3. + +This demonstrates how DeltaGlider provides full type safety without +requiring boto3 imports in user code. +""" + +from deltaglider import ListObjectsV2Response, S3Object, create_client + +# Create client (no boto3 import needed!) +client = create_client() + +# Type hints work perfectly without boto3 +def process_files(bucket: str, prefix: str) -> None: + """Process files in S3 with full type safety.""" + # Return type is fully typed - IDE autocomplete works! + response: ListObjectsV2Response = client.list_objects( + Bucket=bucket, Prefix=prefix, Delimiter="/" + ) + + # TypedDict provides autocomplete and type checking + for obj in response["Contents"]: + # obj is typed as S3Object - all fields have autocomplete! + key: str = obj["Key"] # ✅ IDE knows this is str + size: int = obj["Size"] # ✅ IDE knows this is int + print(f"{key}: {size} bytes") + + # Optional fields work too + for prefix_dict in response.get("CommonPrefixes", []): + print(f"Directory: {prefix_dict['Prefix']}") + + # Pagination info + if response.get("IsTruncated"): + next_token = response.get("NextContinuationToken") + print(f"More results available, token: {next_token}") + + +# This is 100% compatible with boto3 code! +def works_with_boto3_or_deltaglider(s3_client) -> None: + """This function works with EITHER boto3 or DeltaGlider client.""" + # Because the response structure is identical! + response = s3_client.list_objects(Bucket="my-bucket") + + for obj in response["Contents"]: + print(obj["Key"]) + + +if __name__ == "__main__": + # Example usage + print("✅ Full type safety without boto3 imports!") + print("✅ 100% compatible with boto3") + print("✅ Drop-in replacement") diff --git a/src/deltaglider/__init__.py b/src/deltaglider/__init__.py index ead78e0..bad11c4 100644 --- a/src/deltaglider/__init__.py +++ b/src/deltaglider/__init__.py @@ -17,12 +17,26 @@ from .client_models import ( ) from .core import DeltaService, DeltaSpace, ObjectKey +# Import boto3-compatible type aliases (no boto3 import required!) +from .types import ( + CopyObjectResponse, + CreateBucketResponse, + DeleteObjectResponse, + DeleteObjectsResponse, + GetObjectResponse, + HeadObjectResponse, + ListBucketsResponse, + ListObjectsV2Response, + PutObjectResponse, + S3Object, +) + __all__ = [ "__version__", # Client "DeltaGliderClient", "create_client", - # Data classes + # Data classes (legacy - will be deprecated in favor of TypedDict) "UploadSummary", "CompressionEstimate", "ObjectInfo", @@ -32,4 +46,15 @@ __all__ = [ "DeltaService", "DeltaSpace", "ObjectKey", + # boto3-compatible types (no boto3 import needed!) + "ListObjectsV2Response", + "PutObjectResponse", + "GetObjectResponse", + "DeleteObjectResponse", + "DeleteObjectsResponse", + "HeadObjectResponse", + "ListBucketsResponse", + "CreateBucketResponse", + "CopyObjectResponse", + "S3Object", ] diff --git a/src/deltaglider/types.py b/src/deltaglider/types.py new file mode 100644 index 0000000..ff11ead --- /dev/null +++ b/src/deltaglider/types.py @@ -0,0 +1,295 @@ +"""Type definitions for boto3-compatible responses. + +These TypedDict definitions provide type safety and IDE autocomplete +without requiring boto3 imports. At runtime, all responses are plain dicts +that are 100% compatible with boto3. + +This allows DeltaGlider to be a true drop-in replacement for boto3.s3.Client. +""" + +from datetime import datetime +from typing import Any, Literal, NotRequired, TypedDict + + +# ============================================================================ +# S3 Object Types +# ============================================================================ + + +class S3Object(TypedDict): + """An S3 object returned in list operations. + + Compatible with boto3's S3.Client.list_objects_v2() response Contents. + """ + + Key: str + Size: int + LastModified: datetime + ETag: NotRequired[str] + StorageClass: NotRequired[str] + Owner: NotRequired[dict[str, str]] + Metadata: NotRequired[dict[str, str]] + + +class CommonPrefix(TypedDict): + """A common prefix (directory) in S3 listing. + + Compatible with boto3's S3.Client.list_objects_v2() response CommonPrefixes. + """ + + Prefix: str + + +# ============================================================================ +# List Operations Response Types +# ============================================================================ + + +class ListObjectsV2Response(TypedDict): + """Response from list_objects_v2 operation. + + 100% compatible with boto3's S3.Client.list_objects_v2() response. + + Example: + ```python + client = create_client() + response: ListObjectsV2Response = client.list_objects( + Bucket='my-bucket', + Prefix='path/', + Delimiter='/' + ) + + for obj in response['Contents']: + print(f"{obj['Key']}: {obj['Size']} bytes") + + for prefix in response.get('CommonPrefixes', []): + print(f"Directory: {prefix['Prefix']}") + ``` + """ + + Contents: list[S3Object] + Name: NotRequired[str] # Bucket name + Prefix: NotRequired[str] + Delimiter: NotRequired[str] + MaxKeys: NotRequired[int] + CommonPrefixes: NotRequired[list[CommonPrefix]] + EncodingType: NotRequired[str] + KeyCount: NotRequired[int] + ContinuationToken: NotRequired[str] + NextContinuationToken: NotRequired[str] + StartAfter: NotRequired[str] + IsTruncated: NotRequired[bool] + + +# ============================================================================ +# Put/Get/Delete Response Types +# ============================================================================ + + +class ResponseMetadata(TypedDict): + """Metadata about the API response. + + Compatible with all boto3 responses. + """ + + RequestId: NotRequired[str] + HostId: NotRequired[str] + HTTPStatusCode: int + HTTPHeaders: NotRequired[dict[str, str]] + RetryAttempts: NotRequired[int] + + +class PutObjectResponse(TypedDict): + """Response from put_object operation. + + Compatible with boto3's S3.Client.put_object() response. + """ + + ETag: str + VersionId: NotRequired[str] + ServerSideEncryption: NotRequired[str] + ResponseMetadata: NotRequired[ResponseMetadata] + + +class GetObjectResponse(TypedDict): + """Response from get_object operation. + + Compatible with boto3's S3.Client.get_object() response. + """ + + Body: Any # StreamingBody in boto3, bytes in DeltaGlider + ContentLength: int + ContentType: NotRequired[str] + ETag: NotRequired[str] + LastModified: NotRequired[datetime] + Metadata: NotRequired[dict[str, str]] + VersionId: NotRequired[str] + StorageClass: NotRequired[str] + ResponseMetadata: NotRequired[ResponseMetadata] + + +class DeleteObjectResponse(TypedDict): + """Response from delete_object operation. + + Compatible with boto3's S3.Client.delete_object() response. + """ + + DeleteMarker: NotRequired[bool] + VersionId: NotRequired[str] + ResponseMetadata: NotRequired[ResponseMetadata] + + +class DeletedObject(TypedDict): + """A successfully deleted object. + + Compatible with boto3's S3.Client.delete_objects() response Deleted. + """ + + Key: str + VersionId: NotRequired[str] + DeleteMarker: NotRequired[bool] + DeleteMarkerVersionId: NotRequired[str] + + +class DeleteError(TypedDict): + """An error that occurred during deletion. + + Compatible with boto3's S3.Client.delete_objects() response Errors. + """ + + Key: str + Code: str + Message: str + VersionId: NotRequired[str] + + +class DeleteObjectsResponse(TypedDict): + """Response from delete_objects operation. + + Compatible with boto3's S3.Client.delete_objects() response. + """ + + Deleted: NotRequired[list[DeletedObject]] + Errors: NotRequired[list[DeleteError]] + ResponseMetadata: NotRequired[ResponseMetadata] + + +# ============================================================================ +# Head Object Response +# ============================================================================ + + +class HeadObjectResponse(TypedDict): + """Response from head_object operation. + + Compatible with boto3's S3.Client.head_object() response. + """ + + ContentLength: int + ContentType: NotRequired[str] + ETag: NotRequired[str] + LastModified: NotRequired[datetime] + Metadata: NotRequired[dict[str, str]] + VersionId: NotRequired[str] + StorageClass: NotRequired[str] + ResponseMetadata: NotRequired[ResponseMetadata] + + +# ============================================================================ +# Bucket Operations +# ============================================================================ + + +class Bucket(TypedDict): + """An S3 bucket. + + Compatible with boto3's S3.Client.list_buckets() response Buckets. + """ + + Name: str + CreationDate: datetime + + +class ListBucketsResponse(TypedDict): + """Response from list_buckets operation. + + Compatible with boto3's S3.Client.list_buckets() response. + """ + + Buckets: list[Bucket] + Owner: NotRequired[dict[str, str]] + ResponseMetadata: NotRequired[ResponseMetadata] + + +class CreateBucketResponse(TypedDict): + """Response from create_bucket operation. + + Compatible with boto3's S3.Client.create_bucket() response. + """ + + Location: NotRequired[str] + ResponseMetadata: NotRequired[ResponseMetadata] + + +# ============================================================================ +# Multipart Upload Types +# ============================================================================ + + +class CompletedPart(TypedDict): + """A completed part in a multipart upload.""" + + PartNumber: int + ETag: str + + +class CompleteMultipartUploadResponse(TypedDict): + """Response from complete_multipart_upload operation.""" + + Location: NotRequired[str] + Bucket: NotRequired[str] + Key: NotRequired[str] + ETag: NotRequired[str] + VersionId: NotRequired[str] + ResponseMetadata: NotRequired[ResponseMetadata] + + +# ============================================================================ +# Copy Operations +# ============================================================================ + + +class CopyObjectResponse(TypedDict): + """Response from copy_object operation. + + Compatible with boto3's S3.Client.copy_object() response. + """ + + CopyObjectResult: NotRequired[dict[str, Any]] + ETag: NotRequired[str] + LastModified: NotRequired[datetime] + VersionId: NotRequired[str] + ResponseMetadata: NotRequired[ResponseMetadata] + + +# ============================================================================ +# Type Aliases for Convenience +# ============================================================================ + +# Common parameter types +BucketName = str +ObjectKey = str +Prefix = str +Delimiter = str + +# Storage class options +StorageClass = Literal[ + "STANDARD", + "REDUCED_REDUNDANCY", + "STANDARD_IA", + "ONEZONE_IA", + "INTELLIGENT_TIERING", + "GLACIER", + "DEEP_ARCHIVE", + "GLACIER_IR", +]