chatbot / gradio /cli /commands /deploy_space.py
siddharth24m's picture
Upload 3001 files
69ba728 verified
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}")