Spaces:
Running
Running
Create hf_launch.py
Browse files- hf_launch.py +119 -0
hf_launch.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
hf_launch.py β starts any of the three apps on Hugging Face behind the admin.gamify.pt gate,
|
| 3 |
+
WITHOUT changing the app's own code.
|
| 4 |
+
|
| 5 |
+
Video Processor β APP_MODULE=app (app.py builds a FastAPI "app")
|
| 6 |
+
Thumbnail Editor β APP_MODULE=server (server.py builds a FastAPI "app")
|
| 7 |
+
Upload Center β APP_MODULE=app (app.py builds Gradio Blocks and calls .launch())
|
| 8 |
+
|
| 9 |
+
How:
|
| 10 |
+
β’ If the module calls Blocks.launch(), that call is caught (Gradio's own server is NOT started)
|
| 11 |
+
and the Blocks are served here instead, with the same theme / css the app passed to launch().
|
| 12 |
+
β’ If the module already builds a FastAPI/Starlette app, that app is used as it is.
|
| 13 |
+
β’ The GamifyGate (gamify_gate.py) is put in front β unless the app already added it itself.
|
| 14 |
+
β’ GAMIFY_GATE=off opens the app to everyone; if gamify_gate.py can't be loaded, everything is
|
| 15 |
+
blocked (the app is never opened by accident).
|
| 16 |
+
|
| 17 |
+
Dockerfile: CMD ["python", "hf_launch.py"]
|
| 18 |
+
"""
|
| 19 |
+
import importlib
|
| 20 |
+
import inspect
|
| 21 |
+
import logging
|
| 22 |
+
import os
|
| 23 |
+
|
| 24 |
+
import gradio as gr
|
| 25 |
+
|
| 26 |
+
logging.basicConfig(level=logging.INFO)
|
| 27 |
+
PORT = int(os.environ.get("PORT", 7860))
|
| 28 |
+
MODULE = os.environ.get("APP_MODULE", "app")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _params(fn):
|
| 32 |
+
try:
|
| 33 |
+
return inspect.signature(fn).parameters
|
| 34 |
+
except (TypeError, ValueError):
|
| 35 |
+
return {}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# ---- 1) load the app; catch Blocks.launch() so it doesn't start its own (ungated) server ----
|
| 39 |
+
_captured = {}
|
| 40 |
+
_original_launch = gr.Blocks.launch
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def _capture_launch(self, *args, **kwargs):
|
| 44 |
+
_captured["blocks"], _captured["kwargs"] = self, kwargs
|
| 45 |
+
logging.info("hf_launch: the app called launch() β it will be served behind the gate instead")
|
| 46 |
+
return None
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
gr.Blocks.launch = _capture_launch
|
| 50 |
+
try:
|
| 51 |
+
module = importlib.import_module(MODULE)
|
| 52 |
+
finally:
|
| 53 |
+
gr.Blocks.launch = _original_launch
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _is_asgi_app(obj):
|
| 57 |
+
"""A FastAPI / Starlette application (has add_middleware and is callable)."""
|
| 58 |
+
return obj is not None and not isinstance(obj, gr.Blocks) and callable(obj) and hasattr(obj, "add_middleware")
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# ---- 2) the web app to serve ----
|
| 62 |
+
asgi = next((getattr(module, n) for n in ("app", "server", "application") if _is_asgi_app(getattr(module, n, None))),
|
| 63 |
+
None)
|
| 64 |
+
if asgi is None:
|
| 65 |
+
from fastapi import FastAPI
|
| 66 |
+
blocks = _captured.get("blocks") or next(
|
| 67 |
+
(getattr(module, n) for n in ("app", "demo", "interface") if isinstance(getattr(module, n, None), gr.Blocks)),
|
| 68 |
+
None)
|
| 69 |
+
if blocks is None:
|
| 70 |
+
raise RuntimeError(f"hf_launch: no Gradio Blocks or FastAPI app found in {MODULE}.py")
|
| 71 |
+
launch_kwargs = _captured.get("kwargs", {})
|
| 72 |
+
mount_params = _params(gr.mount_gradio_app)
|
| 73 |
+
extra = {k: v for k, v in launch_kwargs.items() if k in mount_params and k not in ("app", "blocks", "path")}
|
| 74 |
+
if "css" in launch_kwargs and "css" not in mount_params and hasattr(blocks, "css"):
|
| 75 |
+
blocks.css = launch_kwargs["css"] # older Gradio: css lives on the Blocks
|
| 76 |
+
asgi = gr.mount_gradio_app(FastAPI(), blocks, path="/", **extra)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# ---- 3) the access gate ----
|
| 80 |
+
def _has_gate(app_):
|
| 81 |
+
for m in getattr(app_, "user_middleware", []) or []:
|
| 82 |
+
cls = getattr(m, "cls", None)
|
| 83 |
+
if getattr(cls, "__name__", "") == "GamifyGate":
|
| 84 |
+
return True
|
| 85 |
+
return False
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class _BlockEverything:
|
| 89 |
+
"""Gate wanted but gamify_gate.py missing: nobody gets in."""
|
| 90 |
+
def __init__(self, app_):
|
| 91 |
+
self.app = app_
|
| 92 |
+
|
| 93 |
+
async def __call__(self, scope, receive, send):
|
| 94 |
+
if scope.get("type") != "http" or scope.get("path") == "/healthz":
|
| 95 |
+
return await self.app(scope, receive, send)
|
| 96 |
+
await send({"type": "http.response.start", "status": 503,
|
| 97 |
+
"headers": [(b"content-type", b"text/plain; charset=utf-8")]})
|
| 98 |
+
await send({"type": "http.response.body",
|
| 99 |
+
"body": b"Access blocked: gamify_gate.py is missing from this Space."})
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
if os.environ.get("GAMIFY_GATE", "on").strip().lower() == "off":
|
| 103 |
+
logging.warning("hf_launch: GAMIFY_GATE=off β the app is OPEN to everyone")
|
| 104 |
+
elif _has_gate(asgi):
|
| 105 |
+
logging.info("hf_launch: the app already has the access gate")
|
| 106 |
+
else:
|
| 107 |
+
try:
|
| 108 |
+
from gamify_gate import GamifyGate
|
| 109 |
+
asgi.add_middleware(GamifyGate)
|
| 110 |
+
logging.info("hf_launch: access gate ON β only through admin.gamify.pt")
|
| 111 |
+
except Exception as e:
|
| 112 |
+
asgi.add_middleware(_BlockEverything)
|
| 113 |
+
logging.error(f"hf_launch: gamify_gate could not be loaded ({e}) β blocking all access")
|
| 114 |
+
|
| 115 |
+
app = asgi
|
| 116 |
+
|
| 117 |
+
if __name__ == "__main__":
|
| 118 |
+
import uvicorn
|
| 119 |
+
uvicorn.run(app, host="0.0.0.0", port=PORT)
|