Wauplin HF staff commited on
Commit
efc7a8e
β€’
1 Parent(s): 9a9f2de

First gradio webhooks try

Browse files
Files changed (6) hide show
  1. Dockerfile +0 -16
  2. README.md +5 -3
  3. app.py +19 -23
  4. gradio_webhooks.py +57 -0
  5. home.html +0 -18
  6. requirements.txt +2 -4
Dockerfile DELETED
@@ -1,16 +0,0 @@
1
- FROM python:3.10
2
-
3
- RUN useradd -m -u 1000 user
4
- USER user
5
- ENV HOME=/home/user \
6
- PATH=/home/user/.local/bin:$PATH
7
-
8
- WORKDIR $HOME/app
9
-
10
- COPY --chown=user requirements.txt requirements.txt
11
- RUN pip install --no-cache-dir --upgrade -r requirements.txt
12
-
13
- COPY --chown=user . .
14
-
15
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--log-level", "info"]
16
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -3,10 +3,12 @@ title: Spaces Ci Bot
3
  emoji: 😻
4
  colorFrom: pink
5
  colorTo: green
6
- sdk: docker
 
 
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
11
 
12
- Project structure taken from https://huggingface.co/spaces/huggingface-projects/auto-retrain.
 
3
  emoji: 😻
4
  colorFrom: pink
5
  colorTo: green
6
+ sdk: gradio
7
+ sdk_version: 3.17.0
8
+ app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ ## Spaces CI webhook
13
 
14
+ This is a webhook space to build temporary Spaces when a PR is submitted.
app.py CHANGED
@@ -1,11 +1,9 @@
1
  # Taken from https://huggingface.co/spaces/huggingface-projects/auto-retrain
2
- import logging
3
  import os
4
  from pathlib import Path
5
  from typing import Literal, Optional
6
 
7
- from fastapi import BackgroundTasks, FastAPI, Header, HTTPException
8
- from fastapi.responses import FileResponse
9
  from huggingface_hub import (
10
  CommitOperationAdd,
11
  CommitOperationDelete,
@@ -21,7 +19,7 @@ from huggingface_hub.repocard import RepoCard
21
  from pydantic import BaseModel
22
  from requests import HTTPError
23
 
24
- logger = logging.getLogger(__file__)
25
 
26
  WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET")
27
  HF_TOKEN = os.getenv("HF_TOKEN")
@@ -50,30 +48,25 @@ class WebhookPayload(BaseModel):
50
  discussion: Optional[WebhookPayloadDiscussion]
51
 
52
 
53
- app = FastAPI()
54
 
55
 
56
- @app.get("/")
57
- async def home():
58
- return FileResponse("home.html")
59
-
60
-
61
- @app.post("/webhook")
62
  async def post_webhook(
63
  payload: WebhookPayload,
64
  task_queue: BackgroundTasks,
65
  x_webhook_secret: Optional[str] = Header(default=None),
66
  ):
67
- logger.info("Received new hook!")
68
  if x_webhook_secret is None:
69
- logger.warning("HTTP 401: No webhook secret")
70
  raise HTTPException(401)
71
  if x_webhook_secret != WEBHOOK_SECRET:
72
- logger.warning("HTTP 403: wrong webhook secret")
73
  raise HTTPException(403)
74
 
75
  if payload.repo.type != "space":
76
- logger.warning("HTTP 400: not a space")
77
  raise HTTPException(400, f"Must be a Space, not {payload.repo.type}")
78
 
79
  space_id = payload.repo.name
@@ -93,9 +86,9 @@ async def post_webhook(
93
  pr_num=payload.discussion.num,
94
  private=payload.repo.private,
95
  )
96
- logger.info("New PR! Sync task scheduled")
97
  else:
98
- logger.info("New comment on PR but CI space already synced")
99
  elif (
100
  payload.event.scope.startswith("discussion")
101
  and payload.event.action == "update"
@@ -112,14 +105,14 @@ async def post_webhook(
112
  space_id=space_id,
113
  pr_num=payload.discussion.num,
114
  )
115
- logger.info("PR is merged (or closed)! Delete task scheduled")
116
  elif (
117
  payload.event.scope.startswith("repo.content")
118
  and payload.event.action == "update"
119
  ):
120
  # New repo change. Is it a commit on a PR?
121
  # => loop through all PRs and check if new changes happened
122
- logger.info("New repo content update. Checking PRs state.")
123
  for discussion in get_repo_discussions(
124
  repo_id=space_id, repo_type="space", token=HF_TOKEN
125
  ):
@@ -131,12 +124,12 @@ async def post_webhook(
131
  pr_num=discussion.num,
132
  private=payload.repo.private,
133
  )
134
- logger.info(f"Scheduled update for PR {discussion.num}.")
135
- logger.info(f"Done looping over PRs.")
136
  else:
137
- logger.info(f"Webhook ignored.")
138
 
139
- logger.info(f"Done.")
140
  return {"processed": True}
141
 
142
 
@@ -278,3 +271,6 @@ PR is now merged/closed. The temporary test Space has been deleted.
278
 
279
  (This is an automated message)
280
  """
 
 
 
 
1
  # Taken from https://huggingface.co/spaces/huggingface-projects/auto-retrain
 
2
  import os
3
  from pathlib import Path
4
  from typing import Literal, Optional
5
 
6
+ from fastapi import BackgroundTasks, Header, HTTPException
 
7
  from huggingface_hub import (
8
  CommitOperationAdd,
9
  CommitOperationDelete,
 
19
  from pydantic import BaseModel
20
  from requests import HTTPError
21
 
22
+ from gradio_webhooks import WebhookGradioApp
23
 
24
  WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET")
25
  HF_TOKEN = os.getenv("HF_TOKEN")
 
48
  discussion: Optional[WebhookPayloadDiscussion]
49
 
50
 
51
+ app = WebhookGradioApp()
52
 
53
 
54
+ @app.add_webhook("/webhook")
 
 
 
 
 
55
  async def post_webhook(
56
  payload: WebhookPayload,
57
  task_queue: BackgroundTasks,
58
  x_webhook_secret: Optional[str] = Header(default=None),
59
  ):
60
+ print("Received new hook!")
61
  if x_webhook_secret is None:
62
+ print("HTTP 401: No webhook secret")
63
  raise HTTPException(401)
64
  if x_webhook_secret != WEBHOOK_SECRET:
65
+ print("HTTP 403: wrong webhook secret")
66
  raise HTTPException(403)
67
 
68
  if payload.repo.type != "space":
69
+ print("HTTP 400: not a space")
70
  raise HTTPException(400, f"Must be a Space, not {payload.repo.type}")
71
 
72
  space_id = payload.repo.name
 
86
  pr_num=payload.discussion.num,
87
  private=payload.repo.private,
88
  )
89
+ print("New PR! Sync task scheduled")
90
  else:
91
+ print("New comment on PR but CI space already synced")
92
  elif (
93
  payload.event.scope.startswith("discussion")
94
  and payload.event.action == "update"
 
105
  space_id=space_id,
106
  pr_num=payload.discussion.num,
107
  )
108
+ print("PR is merged (or closed)! Delete task scheduled")
109
  elif (
110
  payload.event.scope.startswith("repo.content")
111
  and payload.event.action == "update"
112
  ):
113
  # New repo change. Is it a commit on a PR?
114
  # => loop through all PRs and check if new changes happened
115
+ print("New repo content update. Checking PRs state.")
116
  for discussion in get_repo_discussions(
117
  repo_id=space_id, repo_type="space", token=HF_TOKEN
118
  ):
 
124
  pr_num=discussion.num,
125
  private=payload.repo.private,
126
  )
127
+ print(f"Scheduled update for PR {discussion.num}.")
128
+ print(f"Done looping over PRs.")
129
  else:
130
+ print(f"Webhook ignored.")
131
 
132
+ print(f"Done.")
133
  return {"processed": True}
134
 
135
 
 
271
 
272
  (This is an automated message)
273
  """
274
+
275
+
276
+ app.block_thread()
gradio_webhooks.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ from typing import Set, Union
3
+
4
+ import gradio as gr
5
+
6
+
7
+ class WebhookGradioApp:
8
+ """
9
+ ```py
10
+ from gradio_webhooks import WebhookGradioApp
11
+
12
+ app = WebhookGradioApp()
13
+
14
+
15
+ @app.add_webhook("/test_webhook")
16
+ async def hello():
17
+ return {"in_gradio": True}
18
+
19
+
20
+ app.block_thread()
21
+ ```
22
+ """
23
+
24
+ def __init__(self, landing_path: Union[str, Path] = "README.md") -> None:
25
+ # Use README.md as landing page or provide any markdown file
26
+ landing_path = Path(landing_path)
27
+ landing_content = landing_path.read_text()
28
+ if landing_path.name == "README.md":
29
+ landing_content = landing_content.split("---")[-1].strip()
30
+
31
+ # Simple gradio app with landing content
32
+ block = gr.Blocks()
33
+ with block:
34
+ gr.Markdown(landing_content)
35
+
36
+ # Launch gradio app:
37
+ # - as non-blocking so that webhooks can be added afterwards
38
+ # - as shared if launch locally (to receive webhooks)
39
+ app, _, _ = block.launch(prevent_thread_lock=True, share=not block.is_space)
40
+ self.gradio_app = block
41
+ self.fastapi_app = app
42
+ self.webhook_paths: Set[str] = set()
43
+
44
+ def add_webhook(self, path: str):
45
+ self.webhook_paths.add(path)
46
+ return self.fastapi_app.post(path)
47
+
48
+ def block_thread(self) -> None:
49
+ url = (
50
+ self.gradio_app.share_url
51
+ if self.gradio_app.share_url is not None
52
+ else self.gradio_app.local_url
53
+ ).strip("/")
54
+ print("\nWebhooks are correctly setup and ready to use:")
55
+ print("\n".join(f" - POST {url}{webhook}" for webhook in self.webhook_paths))
56
+ print(f"Checkout {url}/docs for more details.")
57
+ self.gradio_app.block_thread()
home.html DELETED
@@ -1,18 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>Spaces CI bot</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Spaces CI webhook</h1>
12
-
13
- <p>This is a webhook space to build temporary Spaces when a PR is submitted.</p>
14
-
15
- <!-- <p>Check out the guide <a href="https://huggingface.co/docs/hub/webhooks-guide-auto-retrain" target="_blank">here</a>!</p> -->
16
- </div>
17
- </body>
18
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,4 +1,2 @@
1
- fastapi==0.74.*
2
- requests==2.27.*
3
- huggingface_hub==0.12.*
4
- uvicorn[standard]==0.17.*
 
1
+ fastapi
2
+ huggingface_hub==0.12.*