Spaces:
Running
Running
import re | |
import time | |
import gradio as gr | |
from huggingface_hub import CommitOperationAdd, HfApi, RepoUrl, SpaceCard | |
IMPORTS_REGEX = re.compile(r"^(from|import) .*$", re.MULTILINE) | |
PR_DESCRIPTION = """ | |
This PR enables Space CI on your Space. **Gradio Space CI is a tool to create ephemeral Spaces for each PR opened on your Space repo.** The goal is to improve developer experience by making the review process as lean as possible. | |
### ⚙️ How it works: | |
- Listens to pull requests events: | |
- If PR is opened => starts an ephemeral Space | |
- If PR is updated => updates the Space | |
- If PR is closed => deleted the Space | |
- Checks PR author: | |
- If trusted author => ephemeral Space is configured with variables, secrets and hardware. | |
- If not a trusted author => ephemeral Space is started without configuration. | |
- Space owners are trusted by default. Additional "trusted authors" can be configuration manually. | |
### ⚠️ Before merging: | |
1. Check that the configuration is correct. By default the Space is configured to run ephemeral Spaces on a (free) CPU instance without any secrets. | |
2. You must set `HF_TOKEN` as a secret in your Space settings. Token must have 'write' permission. You can create a new one in your [User settings](https://huggingface.co/settings/token). | |
--- | |
This is an automated PR created with https://huggingface.co/spaces/Wauplin/gradio-space-ci. | |
For more details about Space CI, checkout [this page]](https://huggingface.co/spaces/Wauplin/gradio-space-ci/blob/main/README.md). | |
If you find any issues, please report here: https://huggingface.co/spaces/Wauplin/gradio-space-ci/discussions | |
Feel free to ignore this PR. | |
""" | |
SUCCESS_MESSAGE = """ | |
### Success 🔥 | |
Yay! A PR has been open to enable Space CI on {space_id}. Check it out here: [{pr_url}]({pr_url}). | |
You can contact the Space owner to let them know about this PR. | |
""" | |
def open_pr(space_id_or_url: str, _1, _2, oauth_token: gr.OAuthToken | None) -> str: | |
if oauth_token is None: | |
raise gr.Error("You must be logged in to open a PR. Click on 'Sign in with Huggingface' first.") | |
token = oauth_token.token | |
if oauth_token.expires_at > time.time(): | |
raise gr.Error("Token expired. Logout and try again.") | |
api = HfApi(token=token) | |
space_id = ( | |
RepoUrl(space_id_or_url).repo_id if "https://huggingface.co/spaces/" in space_id_or_url else space_id_or_url | |
) | |
# 0. Check token + repo existence | |
try: | |
user = api.whoami() | |
if user["type"] != "user": | |
raise gr.Error( | |
"You must use a user token, not an organization token. Go to https://huggingface.co/settings/token to create a new token." | |
) | |
except Exception as e: | |
raise gr.Error("Invalid token: {e}") from e | |
if not api.repo_exists(space_id, repo_type="space"): | |
raise gr.Error(f"Space does not exist: 'https://huggingface.co/spaces/{space_id}'.") | |
# 1. Add to requirements.txt | |
if api.file_exists(repo_id=space_id, repo_type="space", filename="requirements.txt"): | |
requirements_file = api.hf_hub_download(repo_id=space_id, repo_type="space", filename="requirements.txt") | |
with open(requirements_file) as f: | |
requirements = f.read() | |
else: | |
requirements = "" | |
if "gradio-space-ci" not in requirements: | |
requirements += "\ngradio-space-ci @ git+https://huggingface.co/spaces/Wauplin/gradio-space-ci@0.2.1\n" | |
# 2. Configure CI in README.md | |
card = SpaceCard.load(api.hf_hub_download(repo_id=space_id, repo_type="space", filename="README.md")) | |
card.data["space_ci"] = { | |
"trusted_authors": [], | |
"secrets": [], | |
"hardware": "cpu-basic", | |
"storage": None, | |
} | |
if card.data.sdk != "gradio": | |
raise gr.Error(f"Space must be a Gradio Space, not '{card.data.sdk}'.") | |
app_file = card.data.app_file | |
if app_file is None: | |
raise gr.Error("Space must have an app_file defined their README.") | |
if not api.file_exists(repo_id=space_id, repo_type="space", filename=app_file): | |
raise gr.Error(f"Could not find app file '{app_file}' in Space repo.") | |
# 3. Enable CI in app.py | |
with open(api.hf_hub_download(repo_id=space_id, repo_type="space", filename=app_file)) as f: | |
app = f.read() | |
if "enable_space_ci()" in app: | |
raise gr.Error("Space CI is already enabled.") | |
all_imports = list(IMPORTS_REGEX.finditer(app)) | |
if len(all_imports) == 0: | |
raise gr.Error("Could not find any imports in app.py.") | |
last_import = all_imports[-1] | |
app = ( | |
app[: last_import.end()] | |
+ "\n\n" | |
+ "from gradio_space_ci import enable_space_ci" | |
+ "\n\n" | |
+ "enable_space_ci()" | |
+ "\n\n" | |
+ app[last_import.end() :] | |
) | |
# 4. Push changes as a PR | |
commit = api.create_commit( | |
repo_id=space_id, | |
repo_type="space", | |
operations=[ | |
CommitOperationAdd(path_in_repo="README.md", path_or_fileobj=str(card).encode()), | |
CommitOperationAdd(path_in_repo="requirements.txt", path_or_fileobj=requirements.encode()), | |
CommitOperationAdd(path_in_repo=app_file, path_or_fileobj=app.encode()), | |
], | |
commit_message="Enable Space CI", | |
commit_description=PR_DESCRIPTION, | |
create_pr=True, | |
) | |
assert commit.pr_url is not None # since `create_pr=True` | |
return SUCCESS_MESSAGE.format(space_id=space_id, pr_url=commit.pr_url) | |