"""Common s3 functions.""" import boto3 from botocore import exceptions from lambdacommon.common_config import logger from sentry_sdk import capture_exception from src.constants.constants import Error def copy_object(bucket: str, source_key: str, dest_key: str) -> bool: """Copy source_ky object to dest_ky inside of S3 bucket. Args: bucket (str): S3 bucket name source_key (str): source object key dest_key (str): destination object key Return: bool: True if copy operation was successful, False otherwise """ s3 = boto3.client('s3') copy_source = {'Bucket': bucket, 'Key': source_key} try: resp = s3.copy_object(Bucket=bucket, CopySource=copy_source, Key=dest_key) except (exceptions.ClientError, exceptions.ParamValidationError) as e: msg = Error.S3_COPY_ERROR_TEMPLATE.format(src=source_key, dst=dest_key) logger.error(msg) logger.exception(str(e)) capture_exception(e) return False if resp['ResponseMetadata']['HTTPStatusCode'] != 200: msg = Error.S3_COPY_ERROR_TEMPLATE.format(src=source_key, dst=dest_key) logger.error(msg) return False return True def delete_object(bucket: str, key: str) -> bool: """Delete objects from s3 bucket. Args: bucket (str): S3 bucket name key (str): object key Return: bool: True if delete operation was successful, False otherwise """ s3 = boto3.client('s3') try: resp = s3.delete_object(Bucket=bucket, Key=key) except (exceptions.ClientError, exceptions.ParamValidationError) as e: msg = Error.S3_DELETE_ERROR_TEMPLATE.format(src=key) logger.error(msg) logger.exception(str(e)) return False if resp['ResponseMetadata']['HTTPStatusCode'] not in (200, 204): msg = Error.S3_DELETE_ERROR_TEMPLATE.format(src=key) logger.error(msg) return False return True def delete_objects(bucket: str, keys: list[str], quiet: bool = True) -> dict: """Delete objects from s3 bucket. Args: bucket (str): S3 bucket keys (list): List of object keys to delete quiet (bool): enable/disable quiet mode for the request Return: dict: S3 response """ s3 = boto3.client('s3') resp = s3.delete_objects( Bucket=bucket, Delete={ 'Objects': [{'Key': key} for key in keys], 'Quiet': quiet, }, ) return resp def move_object(bucket: str, source_key: str, dest_key: str) -> bool: """Move S3 object from one location to another inside of the same bucket. If copy operation has failed - consider move failed, if delete operation has failed - consider move operation successful. This will guarantee that valid attachments are in correct directory. Args: bucket (str): bucket name source_key (str): source object key dest_key (str): destination object key Returns: bool: True if move was successful, False otherwise """ msg = 'Moving {src} to {dst}'.format(src=source_key, dst=dest_key) logger.info(msg) copy_result = copy_object(bucket, source_key, dest_key) if not copy_result: return False delete_object(bucket, source_key) return True