webhook / gradio_webhooks.py
Wauplin's picture
Wauplin HF staff
New UI to register/unregister spaces on demand
d8053fc
raw
history blame
3.77 kB
import os
from typing import Literal, Optional, Set
import gradio as gr
from fastapi import Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel
class GradioWebhookApp:
"""
```py
from gradio_webhooks import GradioWebhookApp
app = GradioWebhookApp()
@app.add_webhook("/test_webhook")
async def hello():
return {"in_gradio": True}
app.ready()
```
"""
def __init__(
self,
ui: gr.Blocks,
webhook_secret: Optional[str] = None,
) -> None:
# Launch gradio app:
# - as non-blocking so that webhooks can be added afterwards
# - as shared if launch locally (to receive webhooks)
app, _, _ = ui.launch(prevent_thread_lock=True, share=not ui.is_space)
self.gradio_app = ui
self.fastapi_app = app
self.webhook_paths: Set[str] = set()
# Add auth middleware to check the "X-Webhook-Secret" header
self._webhook_secret = webhook_secret or os.getenv("WEBHOOK_SECRET")
if self._webhook_secret is None:
print(
"\nWebhook secret is not defined. This means your webhook endpoints will be open to everyone."
)
print(
"To add a secret, set `WEBHOOK_SECRET` as environment variable or pass it at initialization: "
"\n\t`app = GradioWebhookApp(webhook_secret='my_secret', ...)`"
)
print(
"For more details about Webhook secrets, please refer to https://huggingface.co/docs/hub/webhooks#webhook-secret."
)
else:
print("\nWebhook secret is correctly defined.")
app.middleware("http")(self._webhook_secret_middleware)
def add_webhook(self, path: str):
"""Decorator to add a webhook to the server app."""
self.webhook_paths.add(path)
return self.fastapi_app.post(path)
def ready(self) -> None:
"""Set the app as "ready" and block main thread to keep it running."""
url = (
self.gradio_app.share_url
if self.gradio_app.share_url is not None
else self.gradio_app.local_url
).strip("/")
print("\nWebhooks are correctly setup and ready to use:")
print("\n".join(f" - POST {url}{webhook}" for webhook in self.webhook_paths))
print("Go to https://huggingface.co/settings/webhooks to setup your webhooks.")
self.gradio_app.block_thread()
async def _webhook_secret_middleware(self, request: Request, call_next) -> None:
"""Middleware to check "X-Webhook-Secret" header on every webhook request."""
if request.url.path in self.webhook_paths:
if self._webhook_secret is not None:
request_secret = request.headers.get("x-webhook-secret")
if request_secret is None:
return JSONResponse(
{"error": "x-webhook-secret header not set."}, status_code=401
)
if request_secret != self._webhook_secret:
return JSONResponse(
{"error": "Invalid webhook secret."}, status_code=403
)
return await call_next(request)
class WebhookPayloadEvent(BaseModel):
action: Literal["create", "update", "delete"]
scope: str
class WebhookPayloadRepo(BaseModel):
type: Literal["dataset", "model", "space"]
name: str
private: bool
class WebhookPayloadDiscussion(BaseModel):
num: int
isPullRequest: bool
status: Literal["open", "closed", "merged"]
class WebhookPayload(BaseModel):
event: WebhookPayloadEvent
repo: WebhookPayloadRepo
discussion: Optional[WebhookPayloadDiscussion]