CoolFace
Apppublic

kussssh/IPO-Analyzer

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
supabase_storage.py114 linesDownload Raw Back to backend
1"""2Supabase-backed durable file storage helpers.3 4When RUNTIME_STORAGE_BACKEND=supabase the backend keeps only temporary scratch5files on the local machine and stores PDFs / generated artifacts in Supabase6Storage.7"""8from __future__ import annotations9 10import json11import re12import tempfile13from functools import lru_cache14from pathlib import Path15from typing import Any, Optional16 17from supabase import create_client18from supabase.lib.client_options import SyncClientOptions19 20from backend.config import (21    RUNTIME_ROOT,22    SUPABASE_SERVICE_ROLE_KEY,23    SUPABASE_URL,24    USE_SUPABASE_RUNTIME,25)26 27 28SCRATCH_DIR = RUNTIME_ROOT / "scratch"29SCRATCH_DIR.mkdir(parents=True, exist_ok=True)30 31 32def _safe_slug(value: str, fallback: str = "file") -> str:33    cleaned = re.sub(r"[^\w\-.]+", "_", (value or "").strip()).strip("_")34    return cleaned or fallback35 36 37@lru_cache(maxsize=1)38def get_storage_client():39    if not USE_SUPABASE_RUNTIME:40        raise RuntimeError("Supabase storage client requested while runtime backend is local")41    if not SUPABASE_URL or not SUPABASE_SERVICE_ROLE_KEY:42        raise RuntimeError("Supabase storage is not configured")43    return create_client(44        SUPABASE_URL,45        SUPABASE_SERVICE_ROLE_KEY,46        options=SyncClientOptions(auto_refresh_token=False, persist_session=False),47    )48 49 50def build_document_storage_key(51    company_id: int,52    doc_type: str,53    pdf_sha256: str,54    filename: Optional[str] = None,55) -> str:56    base_name = _safe_slug(Path(filename or f"{doc_type}.pdf").name, fallback=f"{doc_type}.pdf")57    return f"documents/{company_id}/{doc_type}/{pdf_sha256}/{base_name}"58 59 60def build_result_storage_key(company_id: int, doc_type: str, company_name: str) -> str:61    safe_company = _safe_slug(company_name, fallback=f"company_{company_id}")62    return f"results/{company_id}/{doc_type}/{safe_company}_{doc_type}.json"63 64 65def upload_file(bucket: str, storage_key: str, local_path: str | Path, content_type: str) -> str:66    client = get_storage_client()67    with open(local_path, "rb") as handle:68        client.storage.from_(bucket).upload(69            path=storage_key,70            file=handle,71            file_options={"content-type": content_type, "upsert": "true"},72        )73    return storage_key74 75 76def upload_json(bucket: str, storage_key: str, payload: Any) -> str:77    client = get_storage_client()78    data = json.dumps(payload, indent=2, default=str).encode("utf-8")79    client.storage.from_(bucket).upload(80        path=storage_key,81        file=data,82        file_options={"content-type": "application/json", "upsert": "true"},83    )84    return storage_key85 86 87def download_to_temp(bucket: str, storage_key: str, *, suffix: str = "") -> str:88    client = get_storage_client()89    data = client.storage.from_(bucket).download(storage_key)90    tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix, dir=SCRATCH_DIR)91    try:92        tmp.write(data)93        tmp.flush()94        return tmp.name95    finally:96        tmp.close()97 98 99def stage_remote_pdf(storage_bucket: Optional[str], storage_key: Optional[str]) -> Optional[str]:100    if not storage_bucket or not storage_key:101        return None102    return download_to_temp(storage_bucket, storage_key, suffix=".pdf")103 104 105def cleanup_temp_file(path: Optional[str]) -> None:106    if not path:107        return108    try:109        candidate = Path(path)110        if candidate.exists():111            candidate.unlink(missing_ok=True)112    except Exception:113        pass114