""" Delete recordings from the S3 bucket. Requires explicit confirmation before deleting anything. Usage: python scripts/delete_s3_recordings.py # dry run, lists what would be deleted python scripts/delete_s3_recordings.py --confirm # actually delete python scripts/delete_s3_recordings.py --prefix benapi92 --confirm # delete one session only """ import argparse import os from pathlib import Path import boto3 from botocore.config import Config as BotoConfig from dotenv import load_dotenv load_dotenv() BUCKET = 'ethz-otree-whisper' REGION = 'eu-north-1' def list_keys(s3, prefix: str) -> list[str]: paginator = s3.get_paginator('list_objects_v2') keys = [] for page in paginator.paginate(Bucket=BUCKET, Prefix=prefix): for obj in page.get('Contents', []): keys.append(obj['Key']) return keys def delete(prefix: str = '', confirm: bool = False): s3 = boto3.client( 's3', aws_access_key_id=os.environ['S3_ACCESS_KEY'], aws_secret_access_key=os.environ['S3_SECRET_KEY'], region_name=REGION, config=BotoConfig(signature_version='s3v4'), ) keys = list_keys(s3, prefix) if not keys: print('No files found.') return print(f'Found {len(keys)} file(s):\n') for k in keys: print(f' {k}') if not confirm: print(f'\nDry run — nothing deleted. Re-run with --confirm to delete.') return # Safety prompt answer = input(f'\nPermanently delete {len(keys)} file(s) from s3://{BUCKET}? [yes/N] ') if answer.strip().lower() != 'yes': print('Aborted.') return # Delete in batches of 1000 (S3 limit) batch_size = 1000 deleted = 0 for i in range(0, len(keys), batch_size): batch = [{'Key': k} for k in keys[i:i + batch_size]] response = s3.delete_objects(Bucket=BUCKET, Delete={'Objects': batch}) deleted += len(response.get('Deleted', [])) for err in response.get('Errors', []): print(f' ERROR deleting {err["Key"]}: {err["Message"]}') print(f'\nDeleted {deleted} file(s).') if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--prefix', default='', help='Filter by key prefix (e.g. session code)') parser.add_argument('--confirm', action='store_true', help='Actually delete (default is dry run)') args = parser.parse_args() delete(prefix=args.prefix, confirm=args.confirm)