Files
webref/backend/app/core/storage.py
Danilo Reyes 010df31455 phase 5
2025-11-02 11:07:42 -06:00

143 lines
4.2 KiB
Python

"""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 put_object(self, bucket_name: str, object_name: str, data: BinaryIO, length: int, content_type: str):
"""MinIO-compatible put_object method."""
return self.upload_file(data, object_name, content_type)
def remove_object(self, bucket_name: str, object_name: str):
"""MinIO-compatible remove_object method."""
return self.delete_file(object_name)
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()
def get_storage_client() -> StorageClient:
"""Get the global storage client instance."""
return storage_client
# Compatibility methods for MinIO-style API
def put_object(bucket_name: str, object_name: str, data: BinaryIO, length: int, content_type: str):
"""MinIO-compatible put_object method."""
storage_client.upload_file(data, object_name, content_type)
def remove_object(bucket_name: str, object_name: str):
"""MinIO-compatible remove_object method."""
storage_client.delete_file(object_name)