cheekeong2025 commited on
Commit
020a6b1
·
verified ·
1 Parent(s): ae17e86

Update loader.py

Browse files
Files changed (1) hide show
  1. loader.py +51 -9
loader.py CHANGED
@@ -1,7 +1,8 @@
1
- # loader.py — Public proxy to a PRIVATE Static Space (auth via resolve/raw endpoints)
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
@@ -10,8 +11,8 @@ app = FastAPI()
10
 
11
  # ===== Config (from Space Secrets) =====
12
  SPACE_ID = os.getenv("PRIVATE_SPACE_ID") # e.g., "cheekeong2025/IIP_Grading"
13
- HF_TOKEN = os.getenv("HF_TOKEN") # token with READ access to that private Space
14
- REVISION = os.getenv("REVISION", "main") # branch/tag/commit; default "main"
15
  # ======================================
16
 
17
  if not SPACE_ID:
@@ -23,6 +24,45 @@ 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(base: str, path: str) -> str:
27
  return urljoin(base, path.lstrip("/"))
28
 
@@ -34,7 +74,6 @@ def _looks_file(path: str) -> bool:
34
  return "." in path.split("/")[-1]
35
 
36
  def _is_html_path(path: str) -> bool:
37
- # treat root/dirs and *.html as HTML routes
38
  return (not _looks_file(path)) or path.lower().endswith(".html") or path == ""
39
 
40
  def _mime(path: str, default="application/octet-stream") -> str:
@@ -50,7 +89,7 @@ async def _get(client: httpx.AsyncClient, url: str) -> httpx.Response:
50
 
51
  async def _fetch(client: httpx.AsyncClient, path: str, req: Request):
52
  """
53
- Robust lookup order (first 200 wins):
54
  resolve:path
55
  resolve:static/path
56
  (dir) resolve:path/index.html
@@ -93,7 +132,12 @@ async def _fetch(client: httpx.AsyncClient, path: str, req: Request):
93
 
94
  @app.get("/health")
95
  async def health():
96
- return {"status": "ok", "space": SPACE_ID, "revision": REVISION}
 
 
 
 
 
97
 
98
  @app.get("/_debug/fetch/{path:path}")
99
  async def debug_fetch(path: str, request: Request):
@@ -117,7 +161,7 @@ async def root(request: Request):
117
  except Exception as e:
118
  return PlainTextResponse(f"Error fetching root: {e}", status_code=500)
119
 
120
- # Force HTML for the root page (even if upstream says text/plain)
121
  return HTMLResponse(r.text, status_code=r.status_code, media_type="text/html; charset=utf-8")
122
 
123
  @app.get("/{path:path}")
@@ -130,9 +174,7 @@ async def proxy(path: str, request: Request):
130
  return PlainTextResponse(f"Error fetching {path}: {e}", status_code=500)
131
 
132
  if _is_html_path(path):
133
- # Ensure HTML renders even if upstream mislabels as text/plain
134
  return HTMLResponse(r.text, status_code=r.status_code, media_type="text/html; charset=utf-8")
135
 
136
- # Non-HTML: keep upstream/guessed MIME and bytes
137
  ctype = r.headers.get("content-type") or _mime(path)
138
  return Response(content=r.content, media_type=ctype, status_code=r.status_code)
 
1
+ # loader.py — Public proxy to a PRIVATE Static Space (auth via resolve/raw) + preflight checks
2
  import os
3
  import mimetypes
4
  from urllib.parse import urljoin
5
+
6
  from fastapi import FastAPI, Request
7
  from fastapi.responses import Response, HTMLResponse, PlainTextResponse, JSONResponse
8
  import httpx
 
11
 
12
  # ===== Config (from Space Secrets) =====
13
  SPACE_ID = os.getenv("PRIVATE_SPACE_ID") # e.g., "cheekeong2025/IIP_Grading"
14
+ HF_TOKEN = os.getenv("HF_TOKEN") # token with READ access
15
+ REVISION = os.getenv("REVISION", "main") # branch/tag/commit
16
  # ======================================
17
 
18
  if not SPACE_ID:
 
24
  BASE_RESOLVE = f"https://huggingface.co/spaces/{SPACE_ID}/resolve/{REVISION}/"
25
  BASE_RAW = f"https://huggingface.co/spaces/{SPACE_ID}/raw/{REVISION}/"
26
 
27
+ # ---------- Preflight: validate token and repo access ----------
28
+ async def _preflight():
29
+ async with httpx.AsyncClient(timeout=20) as client:
30
+ # 1) whoami (confirms token is valid)
31
+ w = await client.get("https://huggingface.co/api/whoami-v2", headers=HEADERS)
32
+ if w.status_code != 200:
33
+ raise RuntimeError(f"Token invalid: whoami {w.status_code} {w.text}")
34
+
35
+ ident = w.json().get("name") or w.json()
36
+ print(f"[preflight] whoami = {ident}")
37
+
38
+ # 2) repo info (confirms you can read the private Space)
39
+ ri = await client.get(f"https://huggingface.co/api/spaces/{SPACE_ID}", headers=HEADERS)
40
+ if ri.status_code != 200:
41
+ raise RuntimeError(
42
+ f"Cannot access space '{SPACE_ID}': {ri.status_code} {ri.text}. "
43
+ "Check PRIVATE_SPACE_ID spelling/case and that this token has access."
44
+ )
45
+
46
+ info = ri.json()
47
+ print(f"[preflight] space='{SPACE_ID}' private={info.get('private')} repo_owner={info.get('author')}")
48
+ # Optional: verify the revision exists by probing index.html under resolve
49
+ test_url = f"{BASE_RESOLVE}index.html"
50
+ tr = await client.get(test_url, headers=HEADERS)
51
+ print(f"[preflight] probe {test_url} -> {tr.status_code}")
52
+
53
+ # Run preflight once on startup
54
+ @app.on_event("startup")
55
+ async def _startup():
56
+ try:
57
+ await _preflight()
58
+ except Exception as e:
59
+ # Don't crash the server; surface the error in /health
60
+ global _preflight_error
61
+ _preflight_error = str(e)
62
+ print(f"[preflight] ERROR: {_preflight_error}")
63
+ else:
64
+ _preflight_error = None
65
+
66
  def _join(base: str, path: str) -> str:
67
  return urljoin(base, path.lstrip("/"))
68
 
 
74
  return "." in path.split("/")[-1]
75
 
76
  def _is_html_path(path: str) -> bool:
 
77
  return (not _looks_file(path)) or path.lower().endswith(".html") or path == ""
78
 
79
  def _mime(path: str, default="application/octet-stream") -> str:
 
89
 
90
  async def _fetch(client: httpx.AsyncClient, path: str, req: Request):
91
  """
92
+ Order (first 200 wins):
93
  resolve:path
94
  resolve:static/path
95
  (dir) resolve:path/index.html
 
132
 
133
  @app.get("/health")
134
  async def health():
135
+ return {
136
+ "status": "ok" if _preflight_error is None else "error",
137
+ "space": SPACE_ID,
138
+ "revision": REVISION,
139
+ "preflight_error": _preflight_error,
140
+ }
141
 
142
  @app.get("/_debug/fetch/{path:path}")
143
  async def debug_fetch(path: str, request: Request):
 
161
  except Exception as e:
162
  return PlainTextResponse(f"Error fetching root: {e}", status_code=500)
163
 
164
+ # Always render root as HTML
165
  return HTMLResponse(r.text, status_code=r.status_code, media_type="text/html; charset=utf-8")
166
 
167
  @app.get("/{path:path}")
 
174
  return PlainTextResponse(f"Error fetching {path}: {e}", status_code=500)
175
 
176
  if _is_html_path(path):
 
177
  return HTMLResponse(r.text, status_code=r.status_code, media_type="text/html; charset=utf-8")
178
 
 
179
  ctype = r.headers.get("content-type") or _mime(path)
180
  return Response(content=r.content, media_type=ctype, status_code=r.status_code)