cheekeong2025 commited on
Commit
3a5de32
·
verified ·
1 Parent(s): 8dcb15f

Update loader.py

Browse files
Files changed (1) hide show
  1. loader.py +112 -55
loader.py CHANGED
@@ -1,74 +1,131 @@
1
- # loader.py — Public proxy loader for private static Hugging Face Space
2
  import os
 
 
3
  from fastapi import FastAPI, Request
4
- from fastapi.responses import Response, HTMLResponse, PlainTextResponse
5
  import httpx
6
 
7
  app = FastAPI()
8
 
9
- # ========= CONFIG =========
10
- PRIVATE_STATIC_URL = os.getenv(
11
- "PRIVATE_STATIC_URL",
12
- "https://cheekeong2025-iip-grading.static.hf.space" # ✅ fallback URL
13
- )
14
- PRIVATE_SPACE_ID = os.getenv("PRIVATE_SPACE_ID")
15
- HF_TOKEN = os.getenv("HF_TOKEN")
16
-
17
- if not PRIVATE_SPACE_ID:
18
- raise RuntimeError(
19
- "❌ PRIVATE_SPACE_ID is not set.\n"
20
- "Go to your Hugging Face Space → Settings → Repository secrets\n"
21
- "and add PRIVATE_SPACE_ID=cheekeong2025/iip-grading"
22
- )
23
-
24
- if not PRIVATE_STATIC_URL:
25
- raise RuntimeError(
26
- "❌ PRIVATE_STATIC_URL is not set.\n"
27
- "Go to your Hugging Face Space → Settings → Repository secrets\n"
28
- "and add PRIVATE_STATIC_URL=https://<your-private-static-space>.hf.space"
29
- )
30
 
 
 
31
  if not HF_TOKEN:
32
- print("[loader] ⚠️ Warning: HF_TOKEN not set attempting anonymous access.")
33
 
34
- HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
35
- # ===========================
 
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  @app.get("/")
39
- async def root():
40
- """Serve the index.html from the private static Space."""
41
- print(f"[loader] Fetching root from {PRIVATE_STATIC_URL}/index.html")
42
- async with httpx.AsyncClient(timeout=None) as client:
43
- try:
44
- r = await client.get(f"{PRIVATE_STATIC_URL}/index.html", headers=HEADERS)
45
- r.raise_for_status()
46
- except Exception as e:
47
- return PlainTextResponse(f"Error fetching root: {e}", status_code=500)
48
- return HTMLResponse(r.text, status_code=r.status_code)
 
49
 
 
 
 
 
50
 
51
  @app.get("/{path:path}")
52
  async def proxy(path: str, request: Request):
53
- """Proxy all static assets (CSS, JS, images, etc.) from the private Space."""
54
- url = f"{PRIVATE_STATIC_URL.rstrip('/')}/{path}"
55
- print(f"[loader] Fetching asset: {url}")
56
  try:
57
- async with httpx.AsyncClient(timeout=None) as client:
58
- r = await client.get(url, headers=HEADERS)
59
- r.raise_for_status()
60
  except Exception as e:
61
- return PlainTextResponse(f"Error fetching {url}: {e}", status_code=500)
62
 
63
- content_type = r.headers.get("content-type", "text/plain")
64
- return Response(content=r.content, media_type=content_type, status_code=r.status_code)
65
-
66
-
67
- @app.get("/health")
68
- async def health():
69
- """Simple health check."""
70
- return {
71
- "status": "ok",
72
- "source": PRIVATE_STATIC_URL,
73
- "space_id": PRIVATE_SPACE_ID
74
- }
 
1
+ # loader.py — public proxy for PRIVATE Static Space via huggingface.co/resolve & raw
2
  import os
3
+ import mimetypes
4
+ from urllib.parse import urljoin
5
  from fastapi import FastAPI, Request
6
+ from fastapi.responses import Response, HTMLResponse, PlainTextResponse, JSONResponse
7
  import httpx
8
 
9
  app = FastAPI()
10
 
11
+ # --- Config from Secrets ---
12
+ SPACE_ID = os.getenv("PRIVATE_SPACE_ID") # e.g. "cheekeong2025/IIP_Grading"
13
+ HF_TOKEN = os.getenv("HF_TOKEN") # read-access token for that Space
14
+ REVISION = os.getenv("REVISION", "main") # branch/tag/commit
15
+ # ---------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ if not SPACE_ID:
18
+ raise RuntimeError("Set PRIVATE_SPACE_ID (e.g. 'cheekeong2025/IIP_Grading') in Settings → Repository secrets.")
19
  if not HF_TOKEN:
20
+ raise RuntimeError("Set HF_TOKEN (read access to the private Space) in Settings → Repository secrets.")
21
 
22
+ HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"}
23
+ BASE_RESOLVE = f"https://huggingface.co/spaces/{SPACE_ID}/resolve/{REVISION}/"
24
+ BASE_RAW = f"https://huggingface.co/spaces/{SPACE_ID}/raw/{REVISION}/"
25
 
26
+ def _join(b: str, p: str) -> str:
27
+ return urljoin(b, p.lstrip("/"))
28
+
29
+ def _with_q(url: str, req: Request) -> str:
30
+ q = str(req.url.query or "")
31
+ return f"{url}?{q}" if q else url
32
+
33
+ def _looks_file(p: str) -> bool:
34
+ return "." in p.split("/")[-1]
35
+
36
+ def _mime(p: str, default="application/octet-stream"):
37
+ if p.endswith("/") or p == "":
38
+ return "text/html; charset=utf-8"
39
+ m, _ = mimetypes.guess_type(p)
40
+ return m or default
41
+
42
+ async def _get(client: httpx.AsyncClient, url: str) -> httpx.Response:
43
+ r = await client.get(url, headers=HEADERS)
44
+ print(f"[proxy] GET {url} -> {r.status_code}")
45
+ return r
46
+
47
+ async def _fetch(client: httpx.AsyncClient, path: str, req: Request) -> tuple[httpx.Response, str]:
48
+ """
49
+ Try in order (first 200 wins):
50
+ resolve:path
51
+ resolve:static/path
52
+ (dir) resolve:path/index.html
53
+ (dir) resolve:static/path/index.html
54
+ raw:path
55
+ raw:static/path
56
+ (dir) raw:path/index.html
57
+ (dir) raw:static/path/index.html
58
+ """
59
+ as_dir = not _looks_file(path)
60
+ tried = []
61
+ candidates = [
62
+ _join(BASE_RESOLVE, path),
63
+ _join(BASE_RESOLVE, f"static/{path.lstrip('/')}"),
64
+ ]
65
+ if as_dir:
66
+ candidates += [
67
+ _join(BASE_RESOLVE, path.rstrip("/") + "/index.html"),
68
+ _join(BASE_RESOLVE, f"static/{path.rstrip('/')}/index.html"),
69
+ ]
70
+ candidates += [
71
+ _join(BASE_RAW, path),
72
+ _join(BASE_RAW, f"static/{path.lstrip('/')}"),
73
+ ]
74
+ if as_dir:
75
+ candidates += [
76
+ _join(BASE_RAW, path.rstrip("/") + "/index.html"),
77
+ _join(BASE_RAW, f"static/{path.rstrip('/')}/index.html"),
78
+ ]
79
+
80
+ last = None
81
+ for u in candidates:
82
+ url = _with_q(u, req)
83
+ r = await _get(client, url)
84
+ tried.append(u)
85
+ if r.status_code == 200:
86
+ return r, " -> ".join(tried)
87
+ last = r
88
+ return last, " -> ".join(tried)
89
+
90
+ @app.get("/health")
91
+ async def health():
92
+ return {"status": "ok", "space": SPACE_ID, "revision": REVISION}
93
+
94
+ @app.get("/_debug/fetch/{path:path}")
95
+ async def debug_fetch(path: str, request: Request):
96
+ try:
97
+ async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
98
+ r, tried = await _fetch(client, path, request)
99
+ return JSONResponse({"status": r.status_code, "content_type": r.headers.get("content-type"), "tried": tried})
100
+ except Exception as e:
101
+ return JSONResponse({"error": str(e)}, status_code=500)
102
 
103
  @app.get("/")
104
+ async def root(request: Request):
105
+ try:
106
+ async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
107
+ r, tried = await _fetch(client, "index.html", request)
108
+ if r.status_code == 404:
109
+ r, tried2 = await _fetch(client, "", request)
110
+ print(f"[root] tried: {tried} || {tried2}")
111
+ else:
112
+ print(f"[root] tried: {tried}")
113
+ except Exception as e:
114
+ return PlainTextResponse(f"Error fetching root: {e}", status_code=500)
115
 
116
+ ctype = r.headers.get("content-type", "text/html; charset=utf-8")
117
+ if "text/html" in ctype:
118
+ return HTMLResponse(r.text, status_code=r.status_code, media_type=ctype)
119
+ return Response(content=r.content, status_code=r.status_code, media_type=ctype)
120
 
121
  @app.get("/{path:path}")
122
  async def proxy(path: str, request: Request):
 
 
 
123
  try:
124
+ async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
125
+ r, tried = await _fetch(client, path, request)
126
+ print(f"[proxy] path={path} status={r.status_code}")
127
  except Exception as e:
128
+ return PlainTextResponse(f"Error fetching {path}: {e}", status_code=500)
129
 
130
+ ctype = r.headers.get("content-type") or _mime(path)
131
+ return Response(content=r.content, media_type=ctype, status_code=r.status_code)