"""MinIO storage client utilities.""" import logging from io import BytesIO from typing import BinaryIO import boto3 from botocore.client import Config from botocore.exceptions import ClientError from app.core.config import settings logger = logging.getLogger(__name__) class StorageClient: """MinIO storage client wrapper.""" def __init__(self): """Initialize MinIO client.""" self.client = boto3.client( "s3", endpoint_url=f"{'https' if settings.MINIO_SECURE else 'http'}://{settings.MINIO_ENDPOINT}", aws_access_key_id=settings.MINIO_ACCESS_KEY, aws_secret_access_key=settings.MINIO_SECRET_KEY, config=Config(signature_version="s3v4"), ) self.bucket = settings.MINIO_BUCKET self._ensure_bucket_exists() def _ensure_bucket_exists(self) -> None: """Create bucket if it doesn't exist.""" try: self.client.head_bucket(Bucket=self.bucket) except ClientError: logger.info(f"Creating bucket: {self.bucket}") self.client.create_bucket(Bucket=self.bucket) def upload_file(self, file_data: BinaryIO, object_name: str, content_type: str) -> str: """Upload file to MinIO. Args: file_data: File data to upload object_name: S3 object name (path) content_type: MIME type of the file Returns: str: Object URL Raises: Exception: If upload fails """ try: self.client.upload_fileobj( file_data, self.bucket, object_name, ExtraArgs={"ContentType": content_type}, ) return f"{settings.MINIO_ENDPOINT}/{self.bucket}/{object_name}" except ClientError as e: logger.error(f"Failed to upload file {object_name}: {e}") raise def download_file(self, object_name: str) -> BytesIO: """Download file from MinIO. Args: object_name: S3 object name (path) Returns: BytesIO: File data Raises: Exception: If download fails """ try: file_data = BytesIO() self.client.download_fileobj(self.bucket, object_name, file_data) file_data.seek(0) return file_data except ClientError as e: logger.error(f"Failed to download file {object_name}: {e}") raise def delete_file(self, object_name: str) -> None: """Delete file from MinIO. Args: object_name: S3 object name (path) Raises: Exception: If deletion fails """ try: self.client.delete_object(Bucket=self.bucket, Key=object_name) except ClientError as e: logger.error(f"Failed to delete file {object_name}: {e}") raise def file_exists(self, object_name: str) -> bool: """Check if file exists in MinIO. Args: object_name: S3 object name (path) Returns: bool: True if file exists, False otherwise """ try: self.client.head_object(Bucket=self.bucket, Key=object_name) return True except ClientError: return False # Global storage client instance storage_client = StorageClient()