DrValera commited on
Commit
4965d8c
·
verified ·
1 Parent(s): dd51f82

Added access to logs

Browse files
Files changed (1) hide show
  1. main.py +92 -0
main.py CHANGED
@@ -1,4 +1,5 @@
1
  import os, asyncio, httpx
 
2
  from fastapi import FastAPI, Request, HTTPException, UploadFile, File, Form
3
  from fastapi.responses import JSONResponse, Response, StreamingResponse
4
  from fastapi.middleware.cors import CORSMiddleware
@@ -13,6 +14,8 @@ DEMO_FORWARD_URL = os.getenv("DEMO_FORWARD_URL", "").rstrip("/") # url to acc
13
  DATFID_DEMO_TOKEN = os.getenv("DATFID_DEMO_TOKEN", "") # token to access demo space
14
  # This Space's public URL (used to ping self while waiting so HF does not put this Space to sleep). Override with SELF_URL env if different.
15
  SELF_URL = os.getenv("SELF_URL", "https://datfid-org-datfid-master.hf.space").rstrip("/")
 
 
16
 
17
  if not HF_TOKEN:
18
  raise RuntimeError("Missing secret 'hf_token' in public Space.")
@@ -478,6 +481,95 @@ async def secure_ping(req: Request):
478
  raise HTTPException(status_code=401, detail="Missing Authorization Bearer token (dt+...)")
479
  return await _forward("/secure-ping/", "GET", user_token=user_token)
480
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  @app.post("/modelfit/")
482
  async def modelfit(req: Request):
483
  user_token = _extract_user_token(req)
 
1
  import os, asyncio, httpx
2
+ from urllib.parse import urlparse
3
  from fastapi import FastAPI, Request, HTTPException, UploadFile, File, Form
4
  from fastapi.responses import JSONResponse, Response, StreamingResponse
5
  from fastapi.middleware.cors import CORSMiddleware
 
14
  DATFID_DEMO_TOKEN = os.getenv("DATFID_DEMO_TOKEN", "") # token to access demo space
15
  # This Space's public URL (used to ping self while waiting so HF does not put this Space to sleep). Override with SELF_URL env if different.
16
  SELF_URL = os.getenv("SELF_URL", "https://datfid-org-datfid-master.hf.space").rstrip("/")
17
+ # Admin-only endpoints: require this secret (send as X-Admin-Pass header). Secret name in HF: Admin_pass
18
+ Admin_pass = os.getenv("Admin_pass", "")
19
 
20
  if not HF_TOKEN:
21
  raise RuntimeError("Missing secret 'hf_token' in public Space.")
 
481
  raise HTTPException(status_code=401, detail="Missing Authorization Bearer token (dt+...)")
482
  return await _forward("/secure-ping/", "GET", user_token=user_token)
483
 
484
+
485
+ def _require_admin_token(request: Request) -> None:
486
+ """Raise 401 if Admin_pass is not set or X-Admin-Pass does not match."""
487
+ if not Admin_pass:
488
+ raise HTTPException(status_code=503, detail="Admin endpoints not configured (set Admin_pass in secrets).")
489
+ token = request.headers.get("X-Admin-Pass", "").strip()
490
+ if not token or token != Admin_pass:
491
+ raise HTTPException(status_code=401, detail="Invalid or missing X-Admin-Pass.")
492
+
493
+
494
+ def _hf_space_url_to_repo(url: str) -> str | None:
495
+ """Derive HF repo (org/space) from a Space URL (e.g. https://....hf.space). Returns None if not derivable."""
496
+ if not url or ".hf.space" not in url:
497
+ return None
498
+ try:
499
+ host = urlparse(url).netloc
500
+ base = host.replace(".hf.space", "").split(".")[0]
501
+ parts = base.split("-")
502
+ if len(parts) >= 4:
503
+ return f"{parts[0]}-{parts[1]}/{parts[2]}-{parts[3]}"
504
+ if len(parts) >= 2:
505
+ return f"{parts[0]}/{parts[1]}"
506
+ except Exception:
507
+ pass
508
+ return None
509
+
510
+
511
+ def _resolve_space_repo(space: str) -> str:
512
+ """Resolve space key to HF repo (org/space). space must be 'upstream'|'demo'|'self'. Uses existing URLs, no names in code."""
513
+ # Optional env overrides for repo (e.g. HF_LOGS_REPO_UPSTREAM=org/space_name)
514
+ repo = os.getenv(f"HF_LOGS_REPO_{space.upper()}") if space else ""
515
+ if repo:
516
+ return repo.strip()
517
+ url_map = {"upstream": UPSTREAM_URL, "demo": DEMO_FORWARD_URL, "self": SELF_URL}
518
+ if not space or space not in url_map:
519
+ raise HTTPException(
520
+ status_code=400,
521
+ detail="Invalid or missing 'space'. Use: upstream, demo, or self.",
522
+ )
523
+ url = url_map[space]
524
+ if not url and space == "demo":
525
+ raise HTTPException(status_code=400, detail="Demo not configured (DEMO_FORWARD_URL).")
526
+ if not url:
527
+ raise HTTPException(status_code=400, detail="Missing URL for this space key.")
528
+ repo = _hf_space_url_to_repo(url)
529
+ if not repo:
530
+ raise HTTPException(
531
+ status_code=400,
532
+ detail=f"Could not derive repo from URL. Set HF_LOGS_REPO_{space.upper()} in env.",
533
+ )
534
+ return repo
535
+
536
+
537
+ async def _stream_hf_logs(log_type: str, space_repo: str):
538
+ """Stream logs from HF API (run or build). space_repo e.g. DATFID-org/datfid_api."""
539
+ url = f"https://huggingface.co/api/spaces/{space_repo}/logs/{log_type}"
540
+ headers = {"Authorization": f"Bearer {HF_TOKEN}"}
541
+ client = httpx.AsyncClient(timeout=60.0)
542
+ stream_ctx = client.stream("GET", url, headers=headers)
543
+
544
+ async def body_iter():
545
+ try:
546
+ async with stream_ctx as resp:
547
+ if resp.status_code != 200:
548
+ body = await resp.aread()
549
+ raise HTTPException(status_code=resp.status_code, detail=body[:500].decode(errors="replace"))
550
+ async for chunk in resp.aiter_bytes():
551
+ yield chunk
552
+ finally:
553
+ await client.aclose()
554
+
555
+ return StreamingResponse(body_iter(), media_type="text/plain; charset=utf-8")
556
+
557
+
558
+ @app.get("/admin/logs/run")
559
+ async def admin_logs_run(req: Request, space: str = "upstream"):
560
+ """Admin only: stream run logs. Requires X-Admin-Pass header. Query: space=upstream|demo|self (keys for hf_url, DEMO_FORWARD_URL, SELF_URL)."""
561
+ _require_admin_token(req)
562
+ space_repo = _resolve_space_repo(space)
563
+ return await _stream_hf_logs("run", space_repo)
564
+
565
+
566
+ @app.get("/admin/logs/build")
567
+ async def admin_logs_build(req: Request, space: str = "upstream"):
568
+ """Admin only: stream build logs. Requires X-Admin-Pass header. Query: space=upstream|demo|self."""
569
+ _require_admin_token(req)
570
+ space_repo = _resolve_space_repo(space)
571
+ return await _stream_hf_logs("build", space_repo)
572
+
573
  @app.post("/modelfit/")
574
  async def modelfit(req: Request):
575
  user_token = _extract_user_token(req)