GGSheng's picture
feat: deploy Gemma 4 to hf space
08c964e verified
# Copyright 2026 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Contains commands to manage webhooks on the Hugging Face Hub.
Usage:
# list all webhooks
hf webhooks ls
# show details of a single webhook
hf webhooks info <webhook_id>
# create a new webhook
hf webhooks create --url https://example.com/hook --watch model:bert-base-uncased
# create a webhook watching multiple items and domains
hf webhooks create --url https://example.com/hook --watch org:HuggingFace --watch model:gpt2 --domain repo
# update a webhook
hf webhooks update <webhook_id> --url https://new-url.com/hook
# enable / disable a webhook
hf webhooks enable <webhook_id>
hf webhooks disable <webhook_id>
# delete a webhook
hf webhooks delete <webhook_id>
"""
import enum
import json
from typing import Annotated, get_args, get_type_hints
import typer
from huggingface_hub.constants import WEBHOOK_DOMAIN_T
from huggingface_hub.hf_api import WebhookWatchedItem
from ._cli_utils import (
FormatOpt,
OutputFormat,
QuietOpt,
TokenOpt,
api_object_to_dict,
get_hf_api,
print_list_output,
typer_factory,
)
# Build enums dynamically from Literal types to avoid duplication
_WATCHED_TYPES = get_args(get_type_hints(WebhookWatchedItem)["type"])
WatchedItemType = enum.Enum("WatchedItemType", {t: t for t in _WATCHED_TYPES}, type=str) # type: ignore[misc]
_DOMAIN_TYPES = get_args(WEBHOOK_DOMAIN_T)
WebhookDomain = enum.Enum("WebhookDomain", {d: d for d in _DOMAIN_TYPES}, type=str) # type: ignore[misc]
def _parse_watch(values: list[str]) -> list[WebhookWatchedItem]:
"""Parse 'type:name' strings into WebhookWatchedItem objects.
Args:
values: List of strings in the format 'type:name'
(e.g., 'model:bert-base-uncased', 'org:HuggingFace').
Returns:
List of WebhookWatchedItem objects.
Raises:
typer.BadParameter: If any value doesn't match the expected format.
"""
items = []
valid_types = tuple(_WATCHED_TYPES)
for v in values:
if ":" not in v:
raise typer.BadParameter(
f"Expected format 'type:name' (e.g. 'model:bert-base-uncased'), got '{v}'."
f" Valid types: {', '.join(valid_types)}."
)
kind, name = v.split(":", 1)
if kind not in valid_types:
raise typer.BadParameter(f"Invalid type '{kind}'. Valid types: {', '.join(valid_types)}.")
items.append(WebhookWatchedItem(type=kind, name=name)) # type: ignore
return items
webhooks_cli = typer_factory(help="Manage webhooks on the Hub.")
@webhooks_cli.command(
"list | ls",
examples=[
"hf webhooks ls",
"hf webhooks ls --format json",
"hf webhooks ls -q",
],
)
def webhooks_ls(
format: FormatOpt = OutputFormat.table,
quiet: QuietOpt = False,
token: TokenOpt = None,
) -> None:
"""List all webhooks for the current user."""
api = get_hf_api(token=token)
results = [api_object_to_dict(w) for w in api.list_webhooks()]
print_list_output(
results,
format=format,
quiet=quiet,
headers=["id", "url", "disabled", "domains", "watched"],
row_fn=lambda item: [
item.get("id", ""),
item.get("url") or "(job)",
str(item.get("disabled", False)),
", ".join(item.get("domains") or []),
", ".join(
f"{w['type']}:{w['name']}" if isinstance(w, dict) else str(w) for w in (item.get("watched") or [])
),
],
)
@webhooks_cli.command(
"info",
examples=[
"hf webhooks info abc123",
],
)
def webhooks_info(
webhook_id: Annotated[str, typer.Argument(help="The ID of the webhook.")],
token: TokenOpt = None,
) -> None:
"""Show full details for a single webhook as JSON."""
api = get_hf_api(token=token)
webhook = api.get_webhook(webhook_id)
print(json.dumps(api_object_to_dict(webhook), indent=2))
@webhooks_cli.command(
"create",
examples=[
"hf webhooks create --url https://example.com/hook --watch model:bert-base-uncased",
"hf webhooks create --url https://example.com/hook --watch org:HuggingFace --watch model:gpt2 --domain repo",
"hf webhooks create --job-id 687f911eaea852de79c4a50a --watch user:julien-c",
],
)
def webhooks_create(
watch: Annotated[
list[str],
typer.Option(
"--watch",
help="Item to watch, in 'type:name' format (e.g. 'model:bert-base-uncased'). Repeatable.",
),
],
url: Annotated[
str | None,
typer.Option(help="URL to send webhook payloads to. Mutually exclusive with --job-id."),
] = None,
job_id: Annotated[
str | None,
typer.Option(
"--job-id",
help="ID of a Job to trigger (from job.id) instead of pinging a URL. Mutually exclusive with --url.",
),
] = None,
domain: Annotated[
list[WebhookDomain] | None,
typer.Option(
"--domain",
help="Domain to watch: 'repo' or 'discussions'. Repeatable. Defaults to all domains.",
),
] = None,
secret: Annotated[
str | None,
typer.Option(help="Optional secret used to sign webhook payloads."),
] = None,
token: TokenOpt = None,
) -> None:
"""Create a new webhook.
Provide either --url (to ping a remote server) or --job-id (to trigger a Job), but not both.
"""
if url is not None and job_id is not None:
raise typer.BadParameter("Provide either --url or --job-id, not both.")
if url is None and job_id is None:
raise typer.BadParameter("Provide either --url or --job-id.")
api = get_hf_api(token=token)
watched_items = _parse_watch(watch)
domains = [d.value for d in domain] if domain else None
webhook = api.create_webhook(url=url, job_id=job_id, watched=watched_items, domains=domains, secret=secret) # type: ignore
print(f"Webhook created: {webhook.id}")
print(json.dumps(api_object_to_dict(webhook), indent=2))
@webhooks_cli.command(
"update",
examples=[
"hf webhooks update abc123 --url https://new-url.com/hook",
"hf webhooks update abc123 --watch model:gpt2 --domain repo",
"hf webhooks update abc123 --secret newsecret",
],
)
def webhooks_update(
webhook_id: Annotated[str, typer.Argument(help="The ID of the webhook to update.")],
url: Annotated[
str | None,
typer.Option(help="New URL to send webhook payloads to."),
] = None,
watch: Annotated[
list[str] | None,
typer.Option(
"--watch",
help=(
"New list of items to watch, in 'type:name' format. "
"Repeatable. Replaces the entire existing watched list."
),
),
] = None,
domain: Annotated[
list[WebhookDomain] | None,
typer.Option(
"--domain",
help="New list of domains to watch: 'repo' or 'discussions'. Repeatable.",
),
] = None,
secret: Annotated[
str | None,
typer.Option(help="New secret used to sign webhook payloads."),
] = None,
token: TokenOpt = None,
) -> None:
"""Update an existing webhook. Only provided options are changed."""
api = get_hf_api(token=token)
watched_items = _parse_watch(watch) if watch else None
domains = [d.value for d in domain] if domain else None
webhook = api.update_webhook(webhook_id, url=url, watched=watched_items, domains=domains, secret=secret) # type: ignore
print(f"Webhook updated: {webhook.id}")
print(json.dumps(api_object_to_dict(webhook), indent=2))
@webhooks_cli.command(
"enable",
examples=[
"hf webhooks enable abc123",
],
)
def webhooks_enable(
webhook_id: Annotated[str, typer.Argument(help="The ID of the webhook to enable.")],
token: TokenOpt = None,
) -> None:
"""Enable a disabled webhook."""
api = get_hf_api(token=token)
webhook = api.enable_webhook(webhook_id)
print(f"Webhook enabled: {webhook.id}")
@webhooks_cli.command(
"disable",
examples=[
"hf webhooks disable abc123",
],
)
def webhooks_disable(
webhook_id: Annotated[str, typer.Argument(help="The ID of the webhook to disable.")],
token: TokenOpt = None,
) -> None:
"""Disable an active webhook."""
api = get_hf_api(token=token)
webhook = api.disable_webhook(webhook_id)
print(f"Webhook disabled: {webhook.id}")
@webhooks_cli.command(
"delete",
examples=[
"hf webhooks delete abc123",
"hf webhooks delete abc123 --yes",
],
)
def webhooks_delete(
webhook_id: Annotated[str, typer.Argument(help="The ID of the webhook to delete.")],
yes: Annotated[
bool,
typer.Option(
"--yes",
"-y",
help="Skip confirmation prompt.",
),
] = False,
token: TokenOpt = None,
) -> None:
"""Delete a webhook permanently."""
if not yes:
confirm = typer.confirm(f"Are you sure you want to delete webhook '{webhook_id}'?")
if not confirm:
print("Aborted.")
raise typer.Abort()
api = get_hf_api(token=token)
api.delete_webhook(webhook_id)
print(f"Webhook deleted: {webhook_id}")