"""S3 operations for uploading and downloading files""" import logging from pathlib import Path import boto3 logger = logging.getLogger(__name__) class S3Client: """Client for S3 operations""" def __init__(self, region: str = "us-east-1"): self.client = boto3.client("s3", region_name=region) def upload_file(self, local_file: Path, s3_bucket: str, s3_key: str) -> str: """Upload a local file to S3 and return the S3 key Args: local_file: Path to local file s3_key: S3 key to upload to Returns: The S3 key of the uploaded file """ logger.info(f"Uploading {local_file.name} to s3://{s3_bucket}/{s3_key}") with open(local_file, "rb") as f: self.client.upload_fileobj(f, s3_bucket, s3_key) logger.info(f"Upload complete: {s3_key}") return s3_key def download_file( self, s3_bucket: str, s3_key: str, local_path: Path, replace_existing: bool = True, ) -> Path: """Download a file from S3 to local directory Args: s3_key: S3 key to download local_path: Local path to save file Returns: Path to the downloaded file """ if local_path.exists() and not replace_existing: logger.info(f"File {local_path} already exists, skipping download") return local_path logger.info(f"Downloading s3://{s3_bucket}/{s3_key} to {local_path}") try: self.client.download_file(s3_bucket, s3_key, str(local_path)) logger.info(f"Download complete: {local_path}") except Exception as e: logger.error(f"Failed to download {s3_key}: {e}") raise return local_path def copy_file( self, s3_bucket: str, s3_key: str, s3_bucket_dest: str, s3_key_dest: str ) -> str: """Copy a file from one S3 bucket to another""" try: self.client.copy_object( Bucket=s3_bucket_dest, Key=s3_key_dest, CopySource={"Bucket": s3_bucket, "Key": s3_key}, ) except Exception as e: logger.error(f"Failed to copy {s3_key}: {e}") raise return s3_key_dest def file_exists(self, s3_bucket: str, s3_key: str) -> bool: """Check if a file exists in S3 Args: s3_key: S3 key to check Returns: True if file exists, False otherwise """ try: self.client.head_object(Bucket=s3_bucket, Key=s3_key) return True except Exception: return False