69 lines
2.1 KiB
Python
69 lines
2.1 KiB
Python
"""MinIO client — S3-compatible object storage for invoice / proforma attachments.
|
|
|
|
The `minio` SDK is synchronous; every call is wrapped with
|
|
``asyncio.to_thread`` so the event loop is never blocked.
|
|
"""
|
|
import asyncio
|
|
import io
|
|
import logging
|
|
from functools import lru_cache
|
|
|
|
from minio import Minio
|
|
|
|
from app.config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def get_client() -> Minio:
|
|
"""Lazily built Minio client from the PROCUREMENT_MINIO_* settings."""
|
|
return Minio(
|
|
settings.MINIO_ENDPOINT,
|
|
access_key=settings.MINIO_ACCESS_KEY,
|
|
secret_key=settings.MINIO_SECRET_KEY,
|
|
secure=settings.MINIO_SECURE,
|
|
)
|
|
|
|
|
|
async def ensure_bucket() -> None:
|
|
"""Create the attachments bucket if it does not exist (idempotent)."""
|
|
client = get_client()
|
|
try:
|
|
exists = await asyncio.to_thread(client.bucket_exists, settings.MINIO_BUCKET)
|
|
if not exists:
|
|
await asyncio.to_thread(client.make_bucket, settings.MINIO_BUCKET)
|
|
logger.info("Created MinIO bucket %r", settings.MINIO_BUCKET)
|
|
except Exception:
|
|
logger.exception("MinIO bucket check failed — is the minio service reachable?")
|
|
|
|
|
|
async def put_object(key: str, content: bytes, mime_type: str) -> None:
|
|
"""Upload a byte payload to the bucket (overwrites any existing object)."""
|
|
await asyncio.to_thread(
|
|
get_client().put_object,
|
|
settings.MINIO_BUCKET,
|
|
key,
|
|
io.BytesIO(content),
|
|
length=len(content),
|
|
content_type=mime_type,
|
|
)
|
|
|
|
|
|
async def get_object_bytes(key: str) -> bytes:
|
|
"""Download an object's bytes from the bucket."""
|
|
response = await asyncio.to_thread(get_client().get_object, settings.MINIO_BUCKET, key)
|
|
try:
|
|
return response.read()
|
|
finally:
|
|
response.close()
|
|
response.release_conn()
|
|
|
|
|
|
async def remove_object(key: str) -> None:
|
|
"""Delete an object; missing objects are ignored (best-effort)."""
|
|
try:
|
|
await asyncio.to_thread(get_client().remove_object, settings.MINIO_BUCKET, key)
|
|
except Exception:
|
|
logger.warning("Could not remove MinIO object %r — ignoring", key)
|