adf0d17497
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
322 lines
11 KiB
Python
322 lines
11 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
from typing import Annotated
|
|
|
|
import huggingface_hub
|
|
from rich import print
|
|
from typer import Option
|
|
|
|
import gradio as gr
|
|
|
|
repo_directory = os.getcwd()
|
|
readme_file = os.path.join(repo_directory, "README.md")
|
|
github_action_template = os.path.join(
|
|
os.path.dirname(__file__), "deploy_space_action.yaml"
|
|
)
|
|
|
|
|
|
def add_configuration_to_readme(
|
|
title: str | None,
|
|
app_file: str | None,
|
|
) -> dict:
|
|
configuration = {}
|
|
|
|
dir_name = os.path.basename(repo_directory)
|
|
if title is None:
|
|
title = input(f"Enter Spaces app title [{dir_name}]: ") or dir_name
|
|
formatted_title = format_title(title)
|
|
if formatted_title != title:
|
|
print(f"Formatted to {formatted_title}. ")
|
|
configuration["title"] = formatted_title
|
|
|
|
if app_file is None:
|
|
for file in os.listdir(repo_directory):
|
|
file_path = os.path.join(repo_directory, file)
|
|
if not os.path.isfile(file_path) or not file.endswith(".py"):
|
|
continue
|
|
|
|
with open(file_path, encoding="utf-8", errors="ignore") as f:
|
|
content = f.read()
|
|
if "import gradio" in content:
|
|
app_file = file
|
|
break
|
|
|
|
app_file = (
|
|
input(f"Enter Gradio app file {f'[{app_file}]' if app_file else ''}: ")
|
|
or app_file
|
|
)
|
|
if not app_file or not os.path.exists(app_file):
|
|
raise FileNotFoundError("Failed to find Gradio app file.")
|
|
configuration["app_file"] = app_file
|
|
|
|
configuration["sdk"] = "gradio"
|
|
configuration["sdk_version"] = gr.__version__
|
|
huggingface_hub.metadata_save(readme_file, configuration)
|
|
|
|
configuration["hardware"] = (
|
|
input(
|
|
f"Enter Spaces hardware ({', '.join(hardware.value for hardware in huggingface_hub.SpaceHardware)}) [cpu-basic]: "
|
|
)
|
|
or "cpu-basic"
|
|
)
|
|
|
|
secrets = {}
|
|
if input("Any Spaces secrets (y/n) [n]: ") == "y":
|
|
while True:
|
|
secret_name = input("Enter secret name (leave blank to end): ")
|
|
if not secret_name:
|
|
break
|
|
secret_value = input(f"Enter secret value for {secret_name}: ")
|
|
secrets[secret_name] = secret_value
|
|
configuration["secrets"] = secrets
|
|
|
|
requirements_file = os.path.join(repo_directory, "requirements.txt")
|
|
if (
|
|
not os.path.exists(requirements_file)
|
|
and input("Create requirements.txt file? (y/n) [n]: ").lower() == "y"
|
|
):
|
|
while True:
|
|
requirement = input("Enter a dependency (leave blank to end): ")
|
|
if not requirement:
|
|
break
|
|
with open(requirements_file, "a", encoding="utf-8") as f:
|
|
f.write(requirement + "\n")
|
|
|
|
if (
|
|
input(
|
|
"Create Github Action to automatically update Space on 'git push'? [n]: "
|
|
).lower()
|
|
== "y"
|
|
):
|
|
track_branch = input("Enter branch to track [main]: ") or "main"
|
|
github_action_file = os.path.join(
|
|
repo_directory, ".github/workflows/update_space.yml"
|
|
)
|
|
os.makedirs(os.path.dirname(github_action_file), exist_ok=True)
|
|
with open(github_action_template, encoding="utf-8") as f:
|
|
github_action_content = f.read()
|
|
github_action_content = github_action_content.replace("$branch", track_branch)
|
|
with open(github_action_file, "w", encoding="utf-8") as f:
|
|
f.write(github_action_content)
|
|
|
|
print(
|
|
"Github Action created. Add your Hugging Face write token (from https://huggingface.co/settings/tokens) as an Actions Secret named 'hf_token' to your GitHub repository. This can be set in your repository's settings page."
|
|
)
|
|
|
|
return configuration
|
|
|
|
|
|
def format_title(title: str):
|
|
title = title.replace(" ", "_")
|
|
title = re.sub(r"[^a-zA-Z0-9\-._]", "", title)
|
|
title = re.sub("-+", "-", title)
|
|
while title.startswith("."):
|
|
title = title[1:]
|
|
return title
|
|
|
|
|
|
def check_gcloud_auth():
|
|
"""Check if user is logged in to Google Cloud and has a project selected."""
|
|
try:
|
|
auth_result = subprocess.run(
|
|
["gcloud", "auth", "list", "--filter=status:ACTIVE"],
|
|
check=True,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
|
|
if not auth_result.stdout.strip():
|
|
print("[bold yellow]You are not logged in to Google Cloud.[/bold yellow]")
|
|
print("Running 'gcloud init' to set up authentication...")
|
|
subprocess.run(["gcloud", "init"], check=True)
|
|
|
|
project_result = subprocess.run(
|
|
["gcloud", "config", "get-value", "project"],
|
|
check=True,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
|
|
if not project_result.stdout.strip():
|
|
print("[bold yellow]No Google Cloud project is selected.[/bold yellow]")
|
|
project_id = input("Enter your Google Cloud project ID: ")
|
|
if not project_id:
|
|
print("[red]Project ID is required for deployment.[/red]")
|
|
return None
|
|
return project_id
|
|
|
|
print(f"[green]✓ Authenticated as: {auth_result.stdout.strip()}[/green]")
|
|
print(f"[green]✓ Project: {project_result.stdout.strip()}[/green]")
|
|
return project_result.stdout.strip()
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"[bold red]Error checking Google Cloud configuration: {e}[/bold red]")
|
|
print("Running 'gcloud init' to set up configuration...")
|
|
try:
|
|
subprocess.run(["gcloud", "init"], check=True)
|
|
except subprocess.CalledProcessError as init_error:
|
|
print(f"[red]Failed to run 'gcloud init': {init_error}[/red]")
|
|
return False
|
|
except FileNotFoundError:
|
|
print(
|
|
"[bold red]gcloud CLI not found. Please install Google Cloud SDK.[/bold red]"
|
|
)
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def deploy_to_gcloud():
|
|
"""Deploy a Gradio app to Google Cloud Run. Always uses app.py as the entry point."""
|
|
if not shutil.which("gcloud"):
|
|
print(
|
|
"[bold red]gcloud CLI is not installed.[/bold red]\n"
|
|
"Please install the Google Cloud SDK from: "
|
|
"[link]https://cloud.google.com/sdk/docs/install[/link]"
|
|
)
|
|
return
|
|
|
|
project_id = check_gcloud_auth()
|
|
if not project_id:
|
|
print(
|
|
"[bold red]Google Cloud configuration failed. Please run 'gcloud init' manually.[/bold red]"
|
|
)
|
|
return
|
|
|
|
if not os.path.exists("app.py") and not os.path.exists("main.py"):
|
|
print(
|
|
"[bold red]Error:[/bold red] app.py and main.py not found. Google Cloud Run deployment requires app.py or main.py as the entry point."
|
|
)
|
|
return
|
|
|
|
requirements_file = "requirements.txt"
|
|
if not os.path.exists(requirements_file):
|
|
if (
|
|
input(
|
|
f"A requirements.txt file is necessary for Google Cloud Run deployment. Create requirements.txt with gradio=={gr.__version__}? (y/n) [y]: "
|
|
).lower()
|
|
!= "n"
|
|
):
|
|
with open(requirements_file, "w", encoding="utf-8") as f:
|
|
f.write(f"gradio=={gr.__version__}\n")
|
|
print(f"Created requirements.txt with gradio=={gr.__version__}")
|
|
else:
|
|
print("\n[yellow]Deployment cancelled.[/yellow]")
|
|
return
|
|
else:
|
|
with open(requirements_file, encoding="utf-8") as f:
|
|
requirements_content = f.read()
|
|
|
|
if "gradio" not in requirements_content and (
|
|
input(
|
|
f"Add gradio=={gr.__version__} to requirements.txt? (y/n) [y]: "
|
|
).lower()
|
|
!= "n"
|
|
):
|
|
with open(requirements_file, "a", encoding="utf-8") as f:
|
|
f.write(f"gradio=={gr.__version__}\n")
|
|
print(f"Added gradio=={gr.__version__} to requirements.txt")
|
|
|
|
print("[bold]Deploying to Google Cloud Run...[/bold]")
|
|
|
|
try:
|
|
deploy_command = [
|
|
"gcloud",
|
|
"run",
|
|
"deploy",
|
|
"--source=.",
|
|
"--labels=created-by=gradio",
|
|
]
|
|
if project_id:
|
|
deploy_command.extend(["--project", project_id])
|
|
|
|
subprocess.run(
|
|
deploy_command,
|
|
check=True,
|
|
text=True,
|
|
capture_output=False,
|
|
)
|
|
print("[green]✓ Deployment complete![/green]")
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"[red]Deployment failed: {e}[/red]")
|
|
return
|
|
except KeyboardInterrupt:
|
|
print("\n[yellow]Deployment cancelled.[/yellow]")
|
|
return
|
|
|
|
|
|
def deploy(
|
|
title: Annotated[str | None, Option(help="Spaces app title")] = None,
|
|
app_file: Annotated[
|
|
str | None, Option(help="File containing the Gradio app")
|
|
] = None,
|
|
provider: Annotated[
|
|
str | None, Option(help="Deployment provider (spaces or gcloud)")
|
|
] = "spaces",
|
|
):
|
|
if provider == "gcloud":
|
|
if app_file and app_file != "app.py":
|
|
print(
|
|
"[yellow]Warning:[/yellow] --app-file is ignored for Google Cloud Run deployment. Using app.py as entry point."
|
|
)
|
|
deploy_to_gcloud()
|
|
return
|
|
|
|
if provider != "spaces":
|
|
print(f"[red]Unknown provider: {provider}. Use 'spaces' or 'gcloud'.[/red]")
|
|
return
|
|
|
|
if os.getenv("SYSTEM") == "spaces":
|
|
return
|
|
|
|
hf_api = huggingface_hub.HfApi()
|
|
whoami = None
|
|
login = False
|
|
try:
|
|
whoami = hf_api.whoami()
|
|
if whoami["auth"]["accessToken"]["role"] != "write":
|
|
login = True
|
|
except OSError:
|
|
login = True
|
|
if login:
|
|
print("Need 'write' access token to create a Spaces repo.")
|
|
huggingface_hub.login(add_to_git_credential=False)
|
|
whoami = hf_api.whoami()
|
|
|
|
configuration: None | dict = None
|
|
if os.path.exists(readme_file):
|
|
try:
|
|
configuration = huggingface_hub.metadata_load(readme_file)
|
|
except ValueError:
|
|
pass
|
|
|
|
if configuration is None:
|
|
print(
|
|
f"Creating new Spaces Repo in '{repo_directory}'. Collecting metadata, press Enter to accept default value."
|
|
)
|
|
configuration = add_configuration_to_readme(
|
|
title,
|
|
app_file,
|
|
)
|
|
|
|
space_id = hf_api.create_repo(
|
|
configuration["title"],
|
|
space_sdk="gradio",
|
|
repo_type="space",
|
|
exist_ok=True,
|
|
space_hardware=configuration.get("hardware"),
|
|
).repo_id
|
|
hf_api.upload_folder(
|
|
repo_id=space_id,
|
|
repo_type="space",
|
|
folder_path=repo_directory,
|
|
)
|
|
if configuration.get("secrets"):
|
|
for secret_name, secret_value in configuration["secrets"].items():
|
|
hf_api.add_space_secret(space_id, secret_name, secret_value)
|
|
print(f"Space available at https://huggingface.co/spaces/{space_id}")
|