infrasynth-backend-kit/infrasynth/files/storage.py
2026-08-28 14:38:47 -05:00

162 lines
5.1 KiB
Python

from django.core.files.storage import FileSystemStorage
from infrasynth.shared.settings_utils import get_setting
class _LocalStorage:
def __init__(self, config: dict):
self._storage = FileSystemStorage()
def save(self, name: str, content) -> str:
return self._storage.save(name, content)
def open(self, name: str, mode: str = "rb"):
return self._storage.open(name, mode)
def exists(self, name: str) -> bool:
return self._storage.exists(name)
def url(self, name: str) -> str:
return self._storage.url(name)
def delete(self, name: str) -> None:
self._storage.delete(name)
def generate_signed_url(self, name: str, expiry_seconds: int = 3600) -> str:
return self._storage.url(name)
class _S3Storage:
def __init__(self, config: dict):
from storages.backends.s3boto3 import S3Boto3Storage
options = {
"access_key": config.get("ACCESS_KEY"),
"secret_key": config.get("SECRET_KEY"),
"bucket_name": config.get("BUCKET_NAME"),
"region_name": config.get("REGION", "us-east-1"),
"endpoint_url": config.get("ENDPOINT_URL"),
"querystring_auth": True,
"default_acl": None,
"file_overwrite": False,
}
options = {k: v for k, v in options.items() if v is not None}
self._storage = S3Boto3Storage(**options)
def save(self, name: str, content) -> str:
return self._storage.save(name, content)
def open(self, name: str, mode: str = "rb"):
return self._storage.open(name, mode)
def exists(self, name: str) -> bool:
return self._storage.exists(name)
def url(self, name: str) -> str:
return self._storage.url(name)
def delete(self, name: str) -> None:
self._storage.delete(name)
def generate_signed_url(self, name: str, expiry_seconds: int = 3600) -> str:
return self._storage.url(name, expire=expiry_seconds)
class _GCSStorage:
def __init__(self, config: dict):
from storages.backends.gcloud import GoogleCloudStorage
self._storage = GoogleCloudStorage(
bucket_name=config.get("BUCKET_NAME"),
project_id=config.get("PROJECT_ID"),
credentials=config.get("CREDENTIALS_PATH"),
)
def save(self, name: str, content) -> str:
return self._storage.save(name, content)
def open(self, name: str, mode: str = "rb"):
return self._storage.open(name, mode)
def exists(self, name: str) -> bool:
return self._storage.exists(name)
def url(self, name: str) -> str:
return self._storage.url(name)
def delete(self, name: str) -> None:
self._storage.delete(name)
def generate_signed_url(self, name: str, expiry_seconds: int = 3600) -> str:
return self._storage.url(name, expire=expiry_seconds)
class _CloudinaryStorage:
def __init__(self, config: dict):
import cloudinary
from cloudinary.uploader import upload
cloudinary.config(
cloud_name=config.get("CLOUD_NAME"),
api_key=config.get("API_KEY"),
api_secret=config.get("API_SECRET"),
)
self._upload = upload
def save(self, name: str, content) -> str:
result = self._upload(content.read(), public_id=name, overwrite=False)
return result.get("public_id", name)
def open(self, name: str, mode: str = "rb"):
raise NotImplementedError("Cloudinary storage does not support direct file open.")
def exists(self, name: str) -> bool:
import cloudinary.api
try:
cloudinary.api.resource(name)
return True
except Exception:
return False
def url(self, name: str) -> str:
import cloudinary.utils
return cloudinary.utils.cloudinary_url(name)[0]
def delete(self, name: str) -> None:
from cloudinary.uploader import destroy
destroy(name)
def generate_signed_url(self, name: str, expiry_seconds: int = 3600) -> str:
import cloudinary.utils
return cloudinary.utils.cloudinary_url(name, sign_url=True, expires_at=expiry_seconds)[0]
_BACKEND_CLASSES = {
"local": _LocalStorage,
"S3": _S3Storage,
"s3": _S3Storage,
"gcs": _GCSStorage,
"cloudinary": _CloudinaryStorage,
}
def get_storage_backend(backend_name: str | None = None):
"""Returns a storage backend instance by name (defaults to settings config)."""
from django.conf import settings
config = getattr(settings, "INFRASYNTH_FILES", {})
if not backend_name:
backend_name = get_setting("INFRASYNTH_FILES", "DEFAULT_STORAGE_BACKEND", "local")
backend_config = config.get("STORAGE_BACKENDS", {}).get(backend_name, {})
backend_class = _BACKEND_CLASSES.get(str(backend_name), _LocalStorage)
return backend_class(backend_config or {})
def save_file(file_obj, storage_key: str, backend: str = "local"):
"""Saves a file to the given storage backend, returns the stored key."""
storage = get_storage_backend(backend)
return storage.save(storage_key, file_obj)