"""MinIO/S3-compatible storage abstraction.""" import boto3 from botocore.config import Config from botocore.exceptions import ClientError from app.config import get_settings settings = get_settings() _client = None def get_client(): global _client if _client is None: _client = boto3.client( "s3", endpoint_url=settings.storage_endpoint, aws_access_key_id=settings.storage_access_key, aws_secret_access_key=settings.storage_secret_key, config=Config(signature_version="s3v4"), ) return _client def ensure_bucket() -> None: client = get_client() try: client.head_bucket(Bucket=settings.storage_bucket) except ClientError: client.create_bucket(Bucket=settings.storage_bucket) def upload_bytes(key: str, data: bytes, content_type: str = "application/octet-stream") -> str: get_client().put_object( Bucket=settings.storage_bucket, Key=key, Body=data, ContentType=content_type, ) return f"{settings.storage_endpoint}/{settings.storage_bucket}/{key}" def download_bytes(key: str) -> bytes: response = get_client().get_object(Bucket=settings.storage_bucket, Key=key) return response["Body"].read() def delete_object(key: str) -> None: get_client().delete_object(Bucket=settings.storage_bucket, Key=key) def presigned_url(key: str, expires: int = 3600) -> str: return get_client().generate_presigned_url( "get_object", Params={"Bucket": settings.storage_bucket, "Key": key}, ExpiresIn=expires, )