kink-discovery / scripts /e2e_deploy_kink_catalog.py
Perplexed7675's picture
Sync from kink_cli (Docker Space)
6ff91d6 verified
Raw
History Blame Contribute Delete
11.3 kB
#!/usr/bin/env python3
"""One-shot end-to-end: shrink DB → (optional B2 bucket) → catalog to B2 or Hub → HF Space secrets/vars → publish + verify.
**Requires**
``export HF_SPACE_REPO=owner/slug`` (or pass ``--space-repo``).
Reads ``parser`` ``secrets.toml`` for ``[huggingface].token`` (and ``[b2]`` unless overridden).
Optional ``--b2-secrets-toml`` or ``KINK_B2_SECRETS_TOML`` supplies ``[b2]`` only (e.g. a new B2 account).
**Default bootstrap mode** (``--bootstrap auto``):
1. Build ``data/store_slim_b2.db`` from ``data/store_slim.db`` (unless ``--skip-shrink``).
2. Try **B2**: upload + push ``B2_*`` secrets + ``KINK_B2_OBJECT_KEY`` + ``verify_b2_catalog_http``.
3. If B2 upload fails (e.g. storage cap), **Hub fallback**: remove B2 Space secrets so bootstrap does not stop on B2,
upload to Hub dataset + set ``KINK_HF_DATASET_*`` variables, then continue.
4. **Publish** the Space repo and **verify** live (same as ``publish_hf_space.py --verify``).
Does not print tokens or application keys.
Example::
export HF_SPACE_REPO=ronheichman/kink-discovery
python scripts/e2e_deploy_kink_catalog.py
# Optional: create bucket first (needs writeBuckets on key or --admin master key env)
python scripts/e2e_deploy_kink_catalog.py --create-bucket kink-catalog-myname
# B2 only (no Hub fallback)
python scripts/e2e_deploy_kink_catalog.py --bootstrap b2
# Hub only (skip B2)
python scripts/e2e_deploy_kink_catalog.py --bootstrap hub
"""
from __future__ import annotations
import argparse
import os
import subprocess
import sys
import tomllib
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
DEFAULT_SECRETS = Path(os.environ.get("KINK_PARSER_SECRETS_TOML", Path.home() / "PycharmProjects/parser/secrets.toml"))
def _run(py_args: list[str], env: dict | None = None) -> None:
cmd = [sys.executable, str(ROOT / "scripts" / py_args[0])] + py_args[1:]
e = {**os.environ, **(env or {})}
print(f"e2e_deploy: {' '.join(cmd)}", file=sys.stderr, flush=True)
r = subprocess.run(cmd, cwd=str(ROOT), env=e)
if r.returncode != 0:
raise SystemExit(r.returncode)
def _load_hf_token(secrets_path: Path) -> str:
with secrets_path.open("rb") as f:
data = tomllib.load(f)
t = ((data.get("huggingface") or {}).get("token") or "").strip()
if not t:
raise SystemExit(f"e2e_deploy: missing [huggingface].token in {secrets_path}")
return t
def _delete_b2_space_config(api, repo_id: str, hf_token: str) -> None:
"""Remove B2 secrets/vars so Space bootstrap can fall through to Hub."""
from huggingface_hub.utils import HfHubHTTPError
for key in ("B2_KEY_ID", "B2_APPLICATION_KEY", "B2_BUCKET", "B2_REGION"):
try:
api.delete_space_secret(repo_id, key, token=hf_token)
print(f"e2e_deploy: removed Space secret {key}", file=sys.stderr)
except HfHubHTTPError as exc:
if getattr(exc, "response", None) is not None and exc.response.status_code in (400, 404):
continue
print(f"e2e_deploy: delete_space_secret {key}: {exc}", file=sys.stderr)
except Exception as exc:
print(f"e2e_deploy: delete_space_secret {key}: {exc}", file=sys.stderr)
try:
api.delete_space_variable(repo_id, "KINK_B2_OBJECT_KEY", token=hf_token)
print("e2e_deploy: removed Space variable KINK_B2_OBJECT_KEY", file=sys.stderr)
except Exception as exc:
print(f"e2e_deploy: delete_space_variable KINK_B2_OBJECT_KEY: {exc}", file=sys.stderr)
def _hub_fallback(
secrets_path: Path,
space_repo: str,
local_db: Path,
hub_repo: str,
remote_filename: str,
hf_token: str,
) -> None:
from huggingface_hub import HfApi
api = HfApi(token=hf_token)
_delete_b2_space_config(api, space_repo, hf_token)
api.create_repo(hub_repo, repo_type="dataset", exist_ok=True)
print(
f"e2e_deploy: Hub fallback — uploading {local_db}{hub_repo}/{remote_filename}",
file=sys.stderr,
)
api.upload_file(
path_or_fileobj=str(local_db),
path_in_repo=remote_filename,
repo_id=hub_repo,
repo_type="dataset",
)
api.add_space_variable(
space_repo,
"KINK_HF_DATASET_FILENAME",
remote_filename,
description="Bootstrap SQLite (Hub) from e2e_deploy_kink_catalog",
token=hf_token,
)
api.add_space_variable(
space_repo,
"KINK_HF_DATASET_REPO",
hub_repo,
description="Hub dataset for catalog",
token=hf_token,
)
print("e2e_deploy: Hub fallback Space variables set.", file=sys.stderr)
def _try_b2_chain(
secrets_path: Path,
space_repo: str,
local_db: Path,
b2_object_key: str,
b2_secrets_toml: Path | None,
) -> bool:
"""Return True if B2 upload + HF secrets + verify succeeded."""
env = {
**os.environ,
"HF_SPACE_REPO": space_repo,
"KINK_PARSER_SECRETS_TOML": str(secrets_path),
}
if b2_secrets_toml is not None:
env["KINK_B2_SECRETS_TOML"] = str(b2_secrets_toml.resolve())
cmd = [
sys.executable,
str(ROOT / "scripts" / "sync_hf_space_b2_catalog.py"),
"--secrets-toml",
str(secrets_path),
"--local-path",
str(local_db),
"--key",
b2_object_key,
]
if b2_secrets_toml is not None:
cmd.extend(["--b2-secrets-toml", str(b2_secrets_toml.resolve())])
print(f"e2e_deploy: {' '.join(cmd)}", file=sys.stderr, flush=True)
r = subprocess.run(cmd, cwd=str(ROOT), env=env)
if r.returncode != 0:
return False
verify_args = [
"verify_b2_catalog_http.py",
"--secrets-toml",
str(secrets_path),
"--key",
b2_object_key,
]
if b2_secrets_toml is not None:
verify_args.extend(["--b2-secrets-toml", str(b2_secrets_toml.resolve())])
_run(verify_args)
return True
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--secrets-toml", type=Path, default=DEFAULT_SECRETS)
ap.add_argument(
"--b2-secrets-toml",
type=Path,
default=None,
help="TOML with only [b2] (also set KINK_B2_SECRETS_TOML); parser --secrets-toml still used for HF token",
)
ap.add_argument("--space-repo", default=os.environ.get("HF_SPACE_REPO", "").strip(), help="HF Space repo id")
ap.add_argument("--source", type=Path, default=ROOT / "data" / "store_slim.db")
ap.add_argument("--shrink-dest", type=Path, default=ROOT / "data" / "store_slim_b2.db")
ap.add_argument("--skip-shrink", action="store_true")
ap.add_argument(
"--create-bucket",
metavar="NAME",
default="",
help="If set, run b2_create_bucket.py --name NAME first (uses [b2] or B2_ADMIN_* env with --admin-bucket-create)",
)
ap.add_argument(
"--admin-bucket-create",
action="store_true",
help="With --create-bucket, pass --admin to b2_create_bucket (needs B2_ADMIN_KEY_ID / B2_ADMIN_APPLICATION_KEY)",
)
ap.add_argument(
"--bootstrap",
choices=("auto", "b2", "hub"),
default="auto",
help="auto: try B2 then Hub on failure; b2: B2 only; hub: Hub only",
)
ap.add_argument("--b2-object-key", default="kink/catalog/store_slim_bootstrap.db")
ap.add_argument("--hub-repo", default="ronheichman/kink-catalog-slim")
ap.add_argument("--hub-filename", default="store_slim_bootstrap.db")
ap.add_argument("--no-publish", action="store_true", help="Skip publish_hf_space.py (still shrink + catalog)")
ap.add_argument(
"--verify-min-kinks",
type=int,
default=90_000,
help="HF_VERIFY_MIN_KINKS for verify_hf_stack (default 90000)",
)
args = ap.parse_args()
space_repo = (args.space_repo or "").strip()
if not space_repo:
print("e2e_deploy: set --space-repo or HF_SPACE_REPO", file=sys.stderr)
return 1
secrets_path = args.secrets_toml.resolve()
if not secrets_path.is_file():
print(f"e2e_deploy: secrets not found: {secrets_path}", file=sys.stderr)
return 1
if args.b2_secrets_toml is not None and not args.b2_secrets_toml.is_file():
print(f"e2e_deploy: --b2-secrets-toml not found: {args.b2_secrets_toml}", file=sys.stderr)
return 1
hf_token = _load_hf_token(secrets_path)
os.environ["HF_TOKEN"] = hf_token
os.environ["HF_SPACE_REPO"] = space_repo
if args.create_bucket:
bc = [
"b2_create_bucket.py",
"--secrets-toml",
str(secrets_path),
"--name",
args.create_bucket,
]
if args.admin_bucket_create:
bc.append("--admin")
if args.b2_secrets_toml is not None:
bc.extend(["--b2-secrets-toml", str(args.b2_secrets_toml.resolve())])
_run(bc)
if not args.skip_shrink:
src = args.source.resolve()
dst = args.shrink_dest.resolve()
if not src.is_file():
print(f"e2e_deploy: missing source DB {src}", file=sys.stderr)
return 1
_run(
[
"shrink_store_for_remote_bootstrap.py",
"--source",
str(src),
"--dest",
str(dst),
"--force",
]
)
local_db = args.shrink_dest.resolve()
if not local_db.is_file():
print(f"e2e_deploy: missing catalog file {local_db}", file=sys.stderr)
return 1
used_b2 = False
if args.bootstrap in ("auto", "b2"):
ok = _try_b2_chain(secrets_path, space_repo, local_db, args.b2_object_key, args.b2_secrets_toml)
if ok:
used_b2 = True
print("e2e_deploy: B2 path OK.", file=sys.stderr)
elif args.bootstrap == "b2":
print("e2e_deploy: B2 path failed and --bootstrap b2 (no Hub fallback).", file=sys.stderr)
return 1
else:
print("e2e_deploy: B2 failed — Hub fallback.", file=sys.stderr)
_hub_fallback(
secrets_path,
space_repo,
local_db,
args.hub_repo,
args.hub_filename,
hf_token,
)
else:
_hub_fallback(
secrets_path,
space_repo,
local_db,
args.hub_repo,
args.hub_filename,
hf_token,
)
if not used_b2:
_run(
[
"prove_hub_bootstrap_local.py",
"--secrets-toml",
str(secrets_path),
"--repo-id",
args.hub_repo,
"--filename",
args.hub_filename,
]
)
if args.no_publish:
print("e2e_deploy: --no-publish — done (no Space code push).", file=sys.stderr)
return 0
env_verify = {
**os.environ,
"HF_TOKEN": hf_token,
"HF_SPACE_REPO": space_repo,
"HF_VERIFY_MIN_KINKS": str(args.verify_min_kinks),
}
_run(["publish_hf_space.py", "--verify"], env=env_verify)
print("e2e_deploy: complete.", file=sys.stderr)
return 0
if __name__ == "__main__":
raise SystemExit(main())