"""MinIO client wrapper for object storage operations.""" import io import logging from typing import Optional from minio import Minio from minio.error import S3Error from config import settings logger = logging.getLogger(__name__) BUCKET_NAME = "seg-media" _minio_client: Optional[Minio] = None def get_minio_client() -> Minio: """Return a singleton MinIO client instance.""" global _minio_client if _minio_client is None: _minio_client = Minio( settings.minio_endpoint, access_key=settings.minio_access_key, secret_key=settings.minio_secret_key, secure=settings.minio_secure, ) return _minio_client def ensure_bucket_exists() -> None: """Create the bucket if it does not already exist.""" client = get_minio_client() try: if not client.bucket_exists(BUCKET_NAME): client.make_bucket(BUCKET_NAME) logger.info("Created MinIO bucket: %s", BUCKET_NAME) else: logger.info("MinIO bucket %s already exists", BUCKET_NAME) except S3Error as exc: logger.error("MinIO bucket check/creation failed: %s", exc) raise def upload_file( object_name: str, data: bytes, content_type: str = "application/octet-stream", length: int = -1, ) -> str: """Upload bytes to MinIO and return the object name. Args: object_name: Destination path inside the bucket. data: Raw bytes or a file-like object. content_type: MIME type of the object. length: Object size; -1 for unknown (uses chunked upload). Returns: The object name (same as input). """ client = get_minio_client() if isinstance(data, bytes): data = io.BytesIO(data) length = len(data.getvalue()) try: client.put_object( BUCKET_NAME, object_name, data, length=length, content_type=content_type, ) logger.info("Uploaded to MinIO: %s", object_name) return object_name except S3Error as exc: logger.error("MinIO upload failed: %s", exc) raise from datetime import timedelta def get_presigned_url( object_name: str, expires: int = 3600, method: str = "GET", ) -> str: """Generate a presigned URL for an object. Args: object_name: Path inside the bucket. expires: Expiration time in seconds (default 1 hour). method: HTTP method (GET or PUT). Returns: Presigned URL string. """ client = get_minio_client() try: url = client.get_presigned_url(method, BUCKET_NAME, object_name, expires=timedelta(seconds=expires)) return url except S3Error as exc: logger.error("MinIO presigned URL failed: %s", exc) raise def download_file(object_name: str) -> bytes: """Download an object from MinIO and return its bytes. Args: object_name: Path inside the bucket. Returns: Raw bytes of the object. """ client = get_minio_client() try: response = client.get_object(BUCKET_NAME, object_name) data = response.read() response.close() response.release_conn() return data except S3Error as exc: logger.error("MinIO download failed: %s", exc) raise