UrbanLens / Backend /utils /storage.py
0xarchit's picture
Initial Hugging Face Space - Backend deployment
42d88ae
import aiofiles
import aiohttp
from pathlib import Path
from uuid import uuid4
from typing import Optional
from fastapi import UploadFile
from Backend.core.config import settings
from Backend.core.logging import get_logger
logger = get_logger(__name__)
def generate_filename(original_filename: str) -> str:
ext = Path(original_filename).suffix.lower()
if not ext:
ext = ".jpg"
return f"{uuid4().hex}{ext}"
def get_supabase_public_url(file_path: str) -> str:
return f"{settings.supabase_url}/storage/v1/object/public/{settings.supabase_bucket}/{file_path}"
async def upload_to_supabase(file_data: bytes, remote_path: str, content_type: str = "image/jpeg") -> str:
url = f"{settings.supabase_url}/storage/v1/object/{settings.supabase_bucket}/{remote_path}"
headers = {
"Authorization": f"Bearer {settings.supabase_key}",
"Content-Type": content_type,
"x-upsert": "true",
}
async with aiohttp.ClientSession() as session:
async with session.post(url, data=file_data, headers=headers) as response:
if response.status not in (200, 201):
error_text = await response.text()
logger.error(f"Supabase upload failed: {response.status} - {error_text}")
raise Exception(f"Failed to upload to Supabase: {error_text}")
logger.info(f"Uploaded to Supabase: {remote_path}")
return get_supabase_public_url(remote_path)
async def save_upload(file: UploadFile, subfolder: str = "") -> str:
filename = generate_filename(file.filename or "image.jpg")
if subfolder:
remote_path = f"{subfolder}/{filename}"
else:
remote_path = filename
content = await file.read()
await file.seek(0)
content_type = file.content_type or "image/jpeg"
public_url = await upload_to_supabase(content, remote_path, content_type)
return remote_path
async def save_bytes(data: bytes, filename: str, subfolder: str = "", content_type: str = "image/jpeg") -> str:
if subfolder:
remote_path = f"{subfolder}/{filename}"
else:
remote_path = filename
public_url = await upload_to_supabase(data, remote_path, content_type)
return remote_path
async def save_local_temp(data: bytes, filename: str) -> str:
temp_dir = settings.local_temp_dir
temp_dir.mkdir(parents=True, exist_ok=True)
file_path = temp_dir / filename
async with aiofiles.open(file_path, "wb") as f:
await f.write(data)
return str(file_path)
async def download_from_supabase(remote_path: str) -> bytes:
url = get_supabase_public_url(remote_path)
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status != 200:
raise Exception(f"Failed to download from Supabase: {response.status}")
return await response.read()
def get_upload_url(file_path: str) -> str:
if file_path.startswith("http"):
return file_path
return get_supabase_public_url(file_path)
def validate_file_extension(filename: str) -> bool:
ext = Path(filename).suffix.lower().lstrip(".")
return ext in settings.allowed_extensions
def validate_file_size(size: int) -> bool:
max_bytes = settings.max_upload_size_mb * 1024 * 1024
return size <= max_bytes