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, 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) if not space_id_or_url: raise gr.Error("You must input a Space ID or URL.") 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 repo existence 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)