norhan12 commited on
Commit
0efd223
·
verified ·
1 Parent(s): 7804693

Private Hugging Face dataset storage for recordings; health reports database and storage

Browse files
Files changed (3) hide show
  1. README.md +3 -1
  2. app.py +8 -1
  3. platform_app/core.py +44 -0
README.md CHANGED
@@ -77,7 +77,7 @@ FastAPI (this Space, Docker)
77
  │ ReportLab PDF (English / right-to-left Arabic)
78
  └─ /demo standalone analyzer for a full recorded interview + single-question practice
79
  │
80
- Supabase Postgres (data) + Supabase Storage (recordings, PDFs)
81
  ```
82
 
83
  ### Configuration (Space secrets)
@@ -91,6 +91,8 @@ Supabase Postgres (data) + Supabase Storage (recordings, PDFs)
91
  | `SUPABASE_SERVICE_ROLE_KEY` | Server-side storage access |
92
  | `PINECONE_API_KEY` | Optional; a built-in vector index is used otherwise |
93
 
 
 
94
  Optional: `GEMINI_MODEL` (default `gemini-3.5-flash-lite`, with automatic fallback to other Flash models), `STORAGE_BUCKET`.
95
 
96
  ### Run locally
 
77
  │ ReportLab PDF (English / right-to-left Arabic)
78
  └─ /demo standalone analyzer for a full recorded interview + single-question practice
79
  │
80
+ Supabase Postgres (data) + Supabase Storage or a private HF dataset (recordings, PDFs)
81
  ```
82
 
83
  ### Configuration (Space secrets)
 
91
  | `SUPABASE_SERVICE_ROLE_KEY` | Server-side storage access |
92
  | `PINECONE_API_KEY` | Optional; a built-in vector index is used otherwise |
93
 
94
+ Large file storage (optional): set the variable `HF_STORAGE_REPO` (a private dataset, e.g. `EvalBot/evalbot-storage`) and the secret `HF_TOKEN` (a fine-grained token with write access to that dataset) to keep recordings and PDFs on Hugging Face instead of Supabase Storage. `/health` reports the active storage backend and database status; a scheduled ping to it keeps the Space and the free Supabase project awake.
95
+
96
  Optional: `GEMINI_MODEL` (default `gemini-3.5-flash-lite`, with automatic fallback to other Flash models), `STORAGE_BUCKET`.
97
 
98
  ### Run locally
app.py CHANGED
@@ -152,7 +152,14 @@ def _run(local_path: str, user_id: str, progress=None, lang: str = "en") -> Dict
152
 
153
  @app.get("/health")
154
  def health():
155
- return {"status": "ok", "services": pipeline.validate_env_vars()}
 
 
 
 
 
 
 
156
 
157
 
158
  @app.post("/process-audio", response_model=ProcessResponse)
 
152
 
153
  @app.get("/health")
154
  def health():
155
+ core = platform_core
156
+ platform = {"database": False, "storage": core.storage_backend()}
157
+ if core.enabled():
158
+ try: # a real query, so the scheduled ping also keeps the free Supabase project active
159
+ platform["database"] = core.q1("select 1 as ok")["ok"] == 1
160
+ except Exception as e:
161
+ platform["database_error"] = type(e).__name__
162
+ return {"status": "ok", "services": pipeline.validate_env_vars(), "platform": platform}
163
 
164
 
165
  @app.post("/process-audio", response_model=ProcessResponse)
platform_app/core.py CHANGED
@@ -145,7 +145,32 @@ def _use_supabase_storage() -> bool:
145
  return bool(SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY)
146
 
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  def ensure_bucket() -> None:
 
 
 
 
 
149
  if not _use_supabase_storage():
150
  os.makedirs(LOCAL_STORAGE_DIR, exist_ok=True)
151
  return
@@ -157,6 +182,14 @@ def ensure_bucket() -> None:
157
 
158
 
159
  def put_file(path: str, data: bytes, content_type: str) -> str:
 
 
 
 
 
 
 
 
160
  if not _use_supabase_storage():
161
  full = os.path.join(LOCAL_STORAGE_DIR, path)
162
  os.makedirs(os.path.dirname(full), exist_ok=True)
@@ -171,6 +204,17 @@ def put_file(path: str, data: bytes, content_type: str) -> str:
171
 
172
 
173
  def get_file(path: str) -> bytes:
 
 
 
 
 
 
 
 
 
 
 
174
  if not _use_supabase_storage():
175
  with open(os.path.join(LOCAL_STORAGE_DIR, path), "rb") as f:
176
  return f.read()
 
145
  return bool(SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY)
146
 
147
 
148
+ # Optional: keep recordings and PDFs in a private Hugging Face dataset (100 GB+ free) instead of
149
+ # Supabase Storage (1 GB free). Enabled when HF_STORAGE_REPO and HF_TOKEN are both set.
150
+ HF_STORAGE_REPO = os.getenv("HF_STORAGE_REPO", "")
151
+ HF_TOKEN = os.getenv("HF_TOKEN", "")
152
+ _hf_cache_dir = os.path.join(LOCAL_STORAGE_DIR, ".hf_cache")
153
+
154
+
155
+ def _use_hf_storage() -> bool:
156
+ return bool(HF_STORAGE_REPO and HF_TOKEN)
157
+
158
+
159
+ def storage_backend() -> str:
160
+ return "huggingface" if _use_hf_storage() else "supabase" if _use_supabase_storage() else "local"
161
+
162
+
163
+ def _hf_api():
164
+ from huggingface_hub import HfApi
165
+ return HfApi(token=HF_TOKEN)
166
+
167
+
168
  def ensure_bucket() -> None:
169
+ if _use_hf_storage():
170
+ _hf_api().create_repo(HF_STORAGE_REPO, repo_type="dataset", private=True, exist_ok=True)
171
+ os.makedirs(_hf_cache_dir, exist_ok=True)
172
+ logger.info("File storage: private HF dataset %s", HF_STORAGE_REPO)
173
+ return
174
  if not _use_supabase_storage():
175
  os.makedirs(LOCAL_STORAGE_DIR, exist_ok=True)
176
  return
 
182
 
183
 
184
  def put_file(path: str, data: bytes, content_type: str) -> str:
185
+ if _use_hf_storage():
186
+ _hf_api().upload_file(path_or_fileobj=data, path_in_repo=path, repo_id=HF_STORAGE_REPO,
187
+ repo_type="dataset", commit_message=f"add {path}")
188
+ full = os.path.join(_hf_cache_dir, path) # keep a local copy so reads are instant
189
+ os.makedirs(os.path.dirname(full), exist_ok=True)
190
+ with open(full, "wb") as f:
191
+ f.write(data)
192
+ return path
193
  if not _use_supabase_storage():
194
  full = os.path.join(LOCAL_STORAGE_DIR, path)
195
  os.makedirs(os.path.dirname(full), exist_ok=True)
 
204
 
205
 
206
  def get_file(path: str) -> bytes:
207
+ if _use_hf_storage():
208
+ full = os.path.join(_hf_cache_dir, path)
209
+ if not os.path.exists(full):
210
+ from huggingface_hub import hf_hub_download
211
+ try:
212
+ return open(hf_hub_download(HF_STORAGE_REPO, path, repo_type="dataset", token=HF_TOKEN,
213
+ local_dir=_hf_cache_dir), "rb").read()
214
+ except Exception as e:
215
+ raise RuntimeError(f"Storage download failed: {e}")
216
+ with open(full, "rb") as f:
217
+ return f.read()
218
  if not _use_supabase_storage():
219
  with open(os.path.join(LOCAL_STORAGE_DIR, path), "rb") as f:
220
  return f.read()